xref: /openbsd/usr.bin/top/display.c (revision f2dfb0a4)
1 /*	$OpenBSD: display.c,v 1.2 1997/08/22 07:16:27 downsj Exp $	*/
2 
3 /*
4  *  Top users/processes display for Unix
5  *  Version 3
6  *
7  *  This program may be freely redistributed,
8  *  but this entire comment MUST remain intact.
9  *
10  *  Copyright (c) 1984, 1989, William LeFebvre, Rice University
11  *  Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
12  */
13 
14 /*
15  *  This file contains the routines that display information on the screen.
16  *  Each section of the screen has two routines:  one for initially writing
17  *  all constant and dynamic text, and one for only updating the text that
18  *  changes.  The prefix "i_" is used on all the "initial" routines and the
19  *  prefix "u_" is used for all the "updating" routines.
20  *
21  *  ASSUMPTIONS:
22  *        None of the "i_" routines use any of the termcap capabilities.
23  *        In this way, those routines can be safely used on terminals that
24  *        have minimal (or nonexistant) terminal capabilities.
25  *
26  *        The routines are called in this order:  *_loadave, i_timeofday,
27  *        *_procstates, *_cpustates, *_memory, *_message, *_header,
28  *        *_process, u_endscreen.
29  */
30 
31 #include <sys/types.h>
32 #include <stdio.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <term.h>
37 #include <time.h>
38 #include <unistd.h>
39 
40 #include "screen.h"		/* interface to screen package */
41 #include "layout.h"		/* defines for screen position layout */
42 #include "display.h"
43 #include "top.h"
44 #include "top.local.h"
45 #include "boolean.h"
46 #include "machine.h"		/* we should eliminate this!!! */
47 #include "utils.h"
48 
49 #ifdef DEBUG
50 FILE *debug;
51 #endif
52 
53 static int lmpid = 0;
54 static int last_hi = 0;		/* used in u_process and u_endscreen */
55 static int lastline = 0;
56 static int display_width = MAX_COLS;
57 
58 static char *cpustates_tag __P((void));
59 static int string_count __P((char **));
60 static void summary_format __P((char *, int *, char **));
61 static void line_update __P((char *, char *, int, int));
62 
63 #define lineindex(l) ((l)*display_width)
64 
65 /* things initialized by display_init and used thruout */
66 
67 /* buffer of proc information lines for display updating */
68 char *screenbuf = NULL;
69 
70 static char **procstate_names;
71 static char **cpustate_names;
72 static char **memory_names;
73 
74 static int num_procstates;
75 static int num_cpustates;
76 static int num_memory;
77 
78 static int *lprocstates;
79 static int *lcpustates;
80 static int *lmemory;
81 
82 static int *cpustate_columns;
83 static int cpustate_total_length;
84 
85 static enum { OFF, ON, ERASE } header_status = ON;
86 
87 static int string_count();
88 static void summary_format();
89 static void line_update();
90 
91 int display_resize()
92 
93 {
94     register int display_lines;
95 
96     /* first, deallocate any previous buffer that may have been there */
97     if (screenbuf != NULL)
98     {
99 	free(screenbuf);
100     }
101 
102     /* calculate the current dimensions */
103     /* if operating in "dumb" mode, we only need one line */
104     display_lines = smart_terminal ? screen_length - Header_lines : 1;
105 
106     /* we don't want more than MAX_COLS columns, since the machine-dependent
107        modules make static allocations based on MAX_COLS and we don't want
108        to run off the end of their buffers */
109     display_width = screen_width;
110     if (display_width >= MAX_COLS)
111     {
112 	display_width = MAX_COLS - 1;
113     }
114 
115     /* now, allocate space for the screen buffer */
116     screenbuf = (char *)malloc(display_lines * display_width);
117     if (screenbuf == (char *)NULL)
118     {
119 	/* oops! */
120 	return(-1);
121     }
122 
123     /* return number of lines available */
124     /* for dumb terminals, pretend like we can show any amount */
125     return(smart_terminal ? display_lines : Largest);
126 }
127 
128 int display_init(statics)
129 
130 struct statics *statics;
131 
132 {
133     register int display_lines;
134     register char **pp;
135     register int *ip;
136     register int i;
137 
138     /* call resize to do the dirty work */
139     display_lines = display_resize();
140 
141     /* only do the rest if we need to */
142     if (display_lines > -1)
143     {
144 	/* save pointers and allocate space for names */
145 	procstate_names = statics->procstate_names;
146 	num_procstates = string_count(procstate_names);
147 	lprocstates = (int *)malloc(num_procstates * sizeof(int));
148 
149 	cpustate_names = statics->cpustate_names;
150 	num_cpustates = string_count(cpustate_names);
151 	lcpustates = (int *)malloc(num_cpustates * sizeof(int));
152 	cpustate_columns = (int *)malloc(num_cpustates * sizeof(int));
153 
154 	memory_names = statics->memory_names;
155 	num_memory = string_count(memory_names);
156 	lmemory = (int *)malloc(num_memory * sizeof(int));
157 
158 	/* calculate starting columns where needed */
159 	cpustate_total_length = 0;
160 	pp = cpustate_names;
161 	ip = cpustate_columns;
162 	while (*pp != NULL)
163 	{
164 	    if ((i = strlen(*pp++)) > 0)
165 	    {
166 		*ip++ = cpustate_total_length;
167 		cpustate_total_length += i + 8;
168 	    }
169 	}
170     }
171 
172     /* return number of lines available */
173     return(display_lines);
174 }
175 
176 void i_loadave(mpid, avenrun)
177 
178 int mpid;
179 double *avenrun;
180 
181 {
182     register int i;
183 
184     /* i_loadave also clears the screen, since it is first */
185     clear();
186 
187     /* mpid == -1 implies this system doesn't have an _mpid */
188     if (mpid != -1)
189     {
190 	printf("last pid: %5d;  ", mpid);
191     }
192 
193     printf("load averages");
194 
195     for (i = 0; i < 3; i++)
196     {
197 	printf("%c %5.2f",
198 	    i == 0 ? ':' : ',',
199 	    avenrun[i]);
200     }
201     lmpid = mpid;
202 }
203 
204 void u_loadave(mpid, avenrun)
205 
206 int mpid;
207 double *avenrun;
208 
209 {
210     register int i;
211 
212     if (mpid != -1)
213     {
214 	/* change screen only when value has really changed */
215 	if (mpid != lmpid)
216 	{
217 	    Move_to(x_lastpid, y_lastpid);
218 	    printf("%5d", mpid);
219 	    lmpid = mpid;
220 	}
221 
222 	/* i remembers x coordinate to move to */
223 	i = x_loadave;
224     }
225     else
226     {
227 	i = x_loadave_nompid;
228     }
229 
230     /* move into position for load averages */
231     Move_to(i, y_loadave);
232 
233     /* display new load averages */
234     /* we should optimize this and only display changes */
235     for (i = 0; i < 3; i++)
236     {
237 	printf("%s%5.2f",
238 	    i == 0 ? "" : ", ",
239 	    avenrun[i]);
240     }
241 }
242 
243 void i_timeofday(tod)
244 
245 time_t *tod;
246 
247 {
248     /*
249      *  Display the current time.
250      *  "ctime" always returns a string that looks like this:
251      *
252      *	Sun Sep 16 01:03:52 1973
253      *      012345678901234567890123
254      *	          1         2
255      *
256      *  We want indices 11 thru 18 (length 8).
257      */
258 
259     if (smart_terminal)
260     {
261 	Move_to(screen_width - 8, 0);
262     }
263     else
264     {
265 	fputs("    ", stdout);
266     }
267 #ifdef DEBUG
268     {
269 	char *foo;
270 	foo = ctime(tod);
271 	fputs(foo, stdout);
272     }
273 #endif
274     printf("%-8.8s\n", &(ctime(tod)[11]));
275     lastline = 1;
276 }
277 
278 static int ltotal = 0;
279 static char procstates_buffer[128];
280 
281 /*
282  *  *_procstates(total, brkdn, names) - print the process summary line
283  *
284  *  Assumptions:  cursor is at the beginning of the line on entry
285  *		  lastline is valid
286  */
287 
288 void i_procstates(total, brkdn)
289 
290 int total;
291 int *brkdn;
292 
293 {
294     register int i;
295 
296     /* write current number of processes and remember the value */
297     printf("%d processes:", total);
298     ltotal = total;
299 
300     /* put out enough spaces to get to column 15 */
301     i = digits(total);
302     while (i++ < 4)
303     {
304 	putchar(' ');
305     }
306 
307     /* format and print the process state summary */
308     summary_format(procstates_buffer, brkdn, procstate_names);
309     fputs(procstates_buffer, stdout);
310 
311     /* save the numbers for next time */
312     memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
313 }
314 
315 void u_procstates(total, brkdn)
316 
317 int total;
318 int *brkdn;
319 
320 {
321     static char new[128];
322     register int i;
323 
324     /* update number of processes only if it has changed */
325     if (ltotal != total)
326     {
327 	/* move and overwrite */
328 #if (x_procstate == 0)
329 	Move_to(x_procstate, y_procstate);
330 #else
331 	/* cursor is already there...no motion needed */
332 	/* assert(lastline == 1); */
333 #endif
334 	printf("%d", total);
335 
336 	/* if number of digits differs, rewrite the label */
337 	if (digits(total) != digits(ltotal))
338 	{
339 	    fputs(" processes:", stdout);
340 	    /* put out enough spaces to get to column 15 */
341 	    i = digits(total);
342 	    while (i++ < 4)
343 	    {
344 		putchar(' ');
345 	    }
346 	    /* cursor may end up right where we want it!!! */
347 	}
348 
349 	/* save new total */
350 	ltotal = total;
351     }
352 
353     /* see if any of the state numbers has changed */
354     if (memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0)
355     {
356 	/* format and update the line */
357 	summary_format(new, brkdn, procstate_names);
358 	line_update(procstates_buffer, new, x_brkdn, y_brkdn);
359 	memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
360     }
361 }
362 
363 /*
364  *  *_cpustates(states, names) - print the cpu state percentages
365  *
366  *  Assumptions:  cursor is on the PREVIOUS line
367  */
368 
369 static int cpustates_column;
370 
371 /* cpustates_tag() calculates the correct tag to use to label the line */
372 
373 static char *cpustates_tag()
374 
375 {
376     register char *use;
377 
378     static char *short_tag = "CPU: ";
379     static char *long_tag = "CPU states: ";
380 
381     /* if length + strlen(long_tag) >= screen_width, then we have to
382        use the shorter tag (we subtract 2 to account for ": ") */
383     if (cpustate_total_length + (int)strlen(long_tag) - 2 >= screen_width)
384     {
385 	use = short_tag;
386     }
387     else
388     {
389 	use = long_tag;
390     }
391 
392     /* set cpustates_column accordingly then return result */
393     cpustates_column = strlen(use);
394     return(use);
395 }
396 
397 void i_cpustates(states)
398 
399 register int *states;
400 
401 {
402     register int i = 0;
403     register int value;
404     register char **names = cpustate_names;
405     register char *thisname;
406 
407     /* print tag and bump lastline */
408     printf("\n%s", cpustates_tag());
409     lastline++;
410 
411     /* now walk thru the names and print the line */
412     while ((thisname = *names++) != NULL)
413     {
414 	if (*thisname != '\0')
415 	{
416 	    /* retrieve the value and remember it */
417 	    value = *states++;
418 
419 	    /* if percentage is >= 1000, print it as 100% */
420 	    printf((value >= 1000 ? "%s%4.0f%% %s" : "%s%4.1f%% %s"),
421 		   i++ == 0 ? "" : ", ",
422 		   ((float)value)/10.,
423 		   thisname);
424 	}
425     }
426 
427     /* copy over values into "last" array */
428     memcpy(lcpustates, states, num_cpustates * sizeof(int));
429 }
430 
431 void u_cpustates(states)
432 
433 register int *states;
434 
435 {
436     register int value;
437     register char **names = cpustate_names;
438     register char *thisname;
439     register int *lp;
440     register int *colp;
441 
442     Move_to(cpustates_column, y_cpustates);
443     lastline = y_cpustates;
444     lp = lcpustates;
445     colp = cpustate_columns;
446 
447     /* we could be much more optimal about this */
448     while ((thisname = *names++) != NULL)
449     {
450 	if (*thisname != '\0')
451 	{
452 	    /* did the value change since last time? */
453 	    if (*lp != *states)
454 	    {
455 		/* yes, move and change */
456 		Move_to(cpustates_column + *colp, y_cpustates);
457 		lastline = y_cpustates;
458 
459 		/* retrieve value and remember it */
460 		value = *states;
461 
462 		/* if percentage is >= 1000, print it as 100% */
463 		printf((value >= 1000 ? "%4.0f" : "%4.1f"),
464 		       ((double)value)/10.);
465 
466 		/* remember it for next time */
467 		*lp = *states;
468 	    }
469 	}
470 
471 	/* increment and move on */
472 	lp++;
473 	states++;
474 	colp++;
475     }
476 }
477 
478 void z_cpustates()
479 
480 {
481     register int i = 0;
482     register char **names = cpustate_names;
483     register char *thisname;
484     register int *lp;
485 
486     /* show tag and bump lastline */
487     printf("\n%s", cpustates_tag());
488     lastline++;
489 
490     while ((thisname = *names++) != NULL)
491     {
492 	if (*thisname != '\0')
493 	{
494 	    printf("%s    %% %s", i++ == 0 ? "" : ", ", thisname);
495 	}
496     }
497 
498     /* fill the "last" array with all -1s, to insure correct updating */
499     lp = lcpustates;
500     i = num_cpustates;
501     while (--i >= 0)
502     {
503 	*lp++ = -1;
504     }
505 }
506 
507 /*
508  *  *_memory(stats) - print "Memory: " followed by the memory summary string
509  *
510  *  Assumptions:  cursor is on "lastline"
511  *                for i_memory ONLY: cursor is on the previous line
512  */
513 
514 static char memory_buffer[MAX_COLS];
515 
516 void i_memory(stats)
517 
518 int *stats;
519 
520 {
521     fputs("\nMemory: ", stdout);
522     lastline++;
523 
524     /* format and print the memory summary */
525     summary_format(memory_buffer, stats, memory_names);
526     fputs(memory_buffer, stdout);
527 }
528 
529 void u_memory(stats)
530 
531 int *stats;
532 
533 {
534     static char new[MAX_COLS];
535 
536     /* format the new line */
537     summary_format(new, stats, memory_names);
538     line_update(memory_buffer, new, x_mem, y_mem);
539 }
540 
541 /*
542  *  *_message() - print the next pending message line, or erase the one
543  *                that is there.
544  *
545  *  Note that u_message is (currently) the same as i_message.
546  *
547  *  Assumptions:  lastline is consistent
548  */
549 
550 /*
551  *  i_message is funny because it gets its message asynchronously (with
552  *	respect to screen updates).
553  */
554 
555 static char next_msg[MAX_COLS + 5];
556 static int msglen = 0;
557 /* Invariant: msglen is always the length of the message currently displayed
558    on the screen (even when next_msg doesn't contain that message). */
559 
560 void i_message()
561 
562 {
563     while (lastline < y_message)
564     {
565 	fputc('\n', stdout);
566 	lastline++;
567     }
568     if (next_msg[0] != '\0')
569     {
570 	standout(next_msg);
571 	msglen = strlen(next_msg);
572 	next_msg[0] = '\0';
573     }
574     else if (msglen > 0)
575     {
576 	(void) clear_eol(msglen);
577 	msglen = 0;
578     }
579 }
580 
581 void u_message()
582 
583 {
584     i_message();
585 }
586 
587 static int header_length;
588 
589 /*
590  *  *_header(text) - print the header for the process area
591  *
592  *  Assumptions:  cursor is on the previous line and lastline is consistent
593  */
594 
595 void i_header(text)
596 
597 char *text;
598 
599 {
600     header_length = strlen(text);
601     if (header_status == ON)
602     {
603 	putchar('\n');
604 	fputs(text, stdout);
605 	lastline++;
606     }
607     else if (header_status == ERASE)
608     {
609 	header_status = OFF;
610     }
611 }
612 
613 /*ARGSUSED*/
614 void u_header(text)
615 
616 char *text;		/* ignored */
617 
618 {
619     if (header_status == ERASE)
620     {
621 	putchar('\n');
622 	lastline++;
623 	clear_eol(header_length);
624 	header_status = OFF;
625     }
626 }
627 
628 /*
629  *  *_process(line, thisline) - print one process line
630  *
631  *  Assumptions:  lastline is consistent
632  */
633 
634 void i_process(line, thisline)
635 
636 int line;
637 char *thisline;
638 
639 {
640     register char *p;
641     register char *base;
642 
643     /* make sure we are on the correct line */
644     while (lastline < y_procs + line)
645     {
646 	putchar('\n');
647 	lastline++;
648     }
649 
650     /* truncate the line to conform to our current screen width */
651     thisline[display_width] = '\0';
652 
653     /* write the line out */
654     fputs(thisline, stdout);
655 
656     /* copy it in to our buffer */
657     base = smart_terminal ? screenbuf + lineindex(line) : screenbuf;
658     p = strecpy(base, thisline);
659 
660     /* zero fill the rest of it */
661     memset(p, 0, display_width - (p - base));
662 }
663 
664 void u_process(linenum, linebuf)
665 
666 int linenum;
667 char *linebuf;
668 
669 {
670     register char *optr;
671     register int screen_line = linenum + Header_lines;
672     register char *bufferline;
673 
674     /* remember a pointer to the current line in the screen buffer */
675     bufferline = &screenbuf[lineindex(linenum)];
676 
677     /* truncate the line to conform to our current screen width */
678     linebuf[display_width] = '\0';
679 
680     /* is line higher than we went on the last display? */
681     if (linenum >= last_hi)
682     {
683 	/* yes, just ignore screenbuf and write it out directly */
684 	/* get positioned on the correct line */
685 	if (screen_line - lastline == 1)
686 	{
687 	    putchar('\n');
688 	    lastline++;
689 	}
690 	else
691 	{
692 	    Move_to(0, screen_line);
693 	    lastline = screen_line;
694 	}
695 
696 	/* now write the line */
697 	fputs(linebuf, stdout);
698 
699 	/* copy it in to the buffer */
700 	optr = strecpy(bufferline, linebuf);
701 
702 	/* zero fill the rest of it */
703 	memset(optr, 0, display_width - (optr - bufferline));
704     }
705     else
706     {
707 	line_update(bufferline, linebuf, 0, linenum + Header_lines);
708     }
709 }
710 
711 void u_endscreen(hi)
712 
713 register int hi;
714 
715 {
716     register int screen_line = hi + Header_lines;
717     register int i;
718 
719     if (smart_terminal)
720     {
721 	if (hi < last_hi)
722 	{
723 	    /* need to blank the remainder of the screen */
724 	    /* but only if there is any screen left below this line */
725 	    if (lastline + 1 < screen_length)
726 	    {
727 		/* efficiently move to the end of currently displayed info */
728 		if (screen_line - lastline < 5)
729 		{
730 		    while (lastline < screen_line)
731 		    {
732 			putchar('\n');
733 			lastline++;
734 		    }
735 		}
736 		else
737 		{
738 		    Move_to(0, screen_line);
739 		    lastline = screen_line;
740 		}
741 
742 		if (clear_to_end)
743 		{
744 		    /* we can do this the easy way */
745 		    putcap(clear_to_end);
746 		}
747 		else
748 		{
749 		    /* use clear_eol on each line */
750 		    i = hi;
751 		    while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi)
752 		    {
753 			putchar('\n');
754 		    }
755 		}
756 	    }
757 	}
758 	last_hi = hi;
759 
760 	/* move the cursor to a pleasant place */
761 	Move_to(x_idlecursor, y_idlecursor);
762 	lastline = y_idlecursor;
763     }
764     else
765     {
766 	/* separate this display from the next with some vertical room */
767 	fputs("\n\n", stdout);
768     }
769 }
770 
771 void display_header(t)
772 
773 int t;
774 
775 {
776     if (t)
777     {
778 	header_status = ON;
779     }
780     else if (header_status == ON)
781     {
782 	header_status = ERASE;
783     }
784 }
785 
786 /*VARARGS2*/
787 void new_message(type, msgfmt, a1, a2, a3)
788 
789 int type;
790 char *msgfmt;
791 caddr_t a1, a2, a3;
792 
793 {
794     register int i;
795 
796     /* first, format the message */
797     (void) snprintf(next_msg, sizeof(next_msg), msgfmt, a1, a2, a3);
798 
799     if (msglen > 0)
800     {
801 	/* message there already -- can we clear it? */
802 	if (!overstrike)
803 	{
804 	    /* yes -- write it and clear to end */
805 	    i = strlen(next_msg);
806 	    if ((type & MT_delayed) == 0)
807 	    {
808 		type & MT_standout ? standout(next_msg) :
809 		                     fputs(next_msg, stdout);
810 		(void) clear_eol(msglen - i);
811 		msglen = i;
812 		next_msg[0] = '\0';
813 	    }
814 	}
815     }
816     else
817     {
818 	if ((type & MT_delayed) == 0)
819 	{
820 	    type & MT_standout ? standout(next_msg) : fputs(next_msg, stdout);
821 	    msglen = strlen(next_msg);
822 	    next_msg[0] = '\0';
823 	}
824     }
825 }
826 
827 void clear_message()
828 
829 {
830     if (clear_eol(msglen) == 1)
831     {
832 	putchar('\r');
833     }
834 }
835 
836 int readline(buffer, size, numeric)
837 
838 char *buffer;
839 int  size;
840 int  numeric;
841 
842 {
843     register char *ptr = buffer;
844     register char ch;
845     register char cnt = 0;
846     register char maxcnt = 0;
847 
848     /* allow room for null terminator */
849     size -= 1;
850 
851     /* read loop */
852     while ((fflush(stdout), read(0, ptr, 1) > 0))
853     {
854 	/* newline means we are done */
855 	if ((ch = *ptr) == '\n')
856 	{
857 	    break;
858 	}
859 
860 	/* handle special editing characters */
861 	if (ch == ch_kill)
862 	{
863 	    /* kill line -- account for overstriking */
864 	    if (overstrike)
865 	    {
866 		msglen += maxcnt;
867 	    }
868 
869 	    /* return null string */
870 	    *buffer = '\0';
871 	    putchar('\r');
872 	    return(-1);
873 	}
874 	else if (ch == ch_erase)
875 	{
876 	    /* erase previous character */
877 	    if (cnt <= 0)
878 	    {
879 		/* none to erase! */
880 		putchar('\7');
881 	    }
882 	    else
883 	    {
884 		fputs("\b \b", stdout);
885 		ptr--;
886 		cnt--;
887 	    }
888 	}
889 	/* check for character validity and buffer overflow */
890 	else if (cnt == size || (numeric && !isdigit(ch)) ||
891 		!isprint(ch))
892 	{
893 	    /* not legal */
894 	    putchar('\7');
895 	}
896 	else
897 	{
898 	    /* echo it and store it in the buffer */
899 	    putchar(ch);
900 	    ptr++;
901 	    cnt++;
902 	    if (cnt > maxcnt)
903 	    {
904 		maxcnt = cnt;
905 	    }
906 	}
907     }
908 
909     /* all done -- null terminate the string */
910     *ptr = '\0';
911 
912     /* account for the extra characters in the message area */
913     /* (if terminal overstrikes, remember the furthest they went) */
914     msglen += overstrike ? maxcnt : cnt;
915 
916     /* return either inputted number or string length */
917     putchar('\r');
918     return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
919 }
920 
921 /* internal support routines */
922 
923 static int string_count(pp)
924 
925 register char **pp;
926 
927 {
928     register int cnt;
929 
930     cnt = 0;
931     while (*pp++ != NULL)
932     {
933 	cnt++;
934     }
935     return(cnt);
936 }
937 
938 static void summary_format(str, numbers, names)
939 
940 char *str;
941 int *numbers;
942 register char **names;
943 
944 {
945     register char *p;
946     register int num;
947     register char *thisname;
948 
949     /* format each number followed by its string */
950     p = str;
951     while ((thisname = *names++) != NULL)
952     {
953 	/* get the number to format */
954 	num = *numbers++;
955 
956 	/* display only non-zero numbers */
957 	if (num > 0)
958 	{
959 	    /* is this number in kilobytes? */
960 	    if (thisname[0] == 'K')
961 	    {
962 		/* yes: format it as a memory value */
963 		p = strecpy(p, format_k(num));
964 
965 		/* skip over the K, since it was included by format_k */
966 		p = strecpy(p, thisname+1);
967 	    }
968 	    else
969 	    {
970 		p = strecpy(p, itoa(num));
971 		p = strecpy(p, thisname);
972 	    }
973 	}
974 
975 	/* ignore negative numbers, but display corresponding string */
976 	else if (num < 0)
977 	{
978 	    p = strecpy(p, thisname);
979 	}
980     }
981 
982     /* if the last two characters in the string are ", ", delete them */
983     p -= 2;
984     if (p >= str && p[0] == ',' && p[1] == ' ')
985     {
986 	*p = '\0';
987     }
988 }
989 
990 static void line_update(old, new, start, line)
991 
992 register char *old;
993 register char *new;
994 int start;
995 int line;
996 
997 {
998     register int ch;
999     register int diff;
1000     register int newcol = start + 1;
1001     register int lastcol = start;
1002     char cursor_on_line = No;
1003     char *current;
1004 
1005     /* compare the two strings and only rewrite what has changed */
1006     current = old;
1007 #ifdef DEBUG
1008     fprintf(debug, "line_update, starting at %d\n", start);
1009     fputs(old, debug);
1010     fputc('\n', debug);
1011     fputs(new, debug);
1012     fputs("\n-\n", debug);
1013 #endif
1014 
1015     /* start things off on the right foot		    */
1016     /* this is to make sure the invariants get set up right */
1017     if ((ch = *new++) != *old)
1018     {
1019 	if (line - lastline == 1 && start == 0)
1020 	{
1021 	    putchar('\n');
1022 	}
1023 	else
1024 	{
1025 	    Move_to(start, line);
1026 	}
1027 	cursor_on_line = Yes;
1028 	putchar(ch);
1029 	*old = ch;
1030 	lastcol = 1;
1031     }
1032     old++;
1033 
1034     /*
1035      *  main loop -- check each character.  If the old and new aren't the
1036      *	same, then update the display.  When the distance from the
1037      *	current cursor position to the new change is small enough,
1038      *	the characters that belong there are written to move the
1039      *	cursor over.
1040      *
1041      *	Invariants:
1042      *	    lastcol is the column where the cursor currently is sitting
1043      *		(always one beyond the end of the last mismatch).
1044      */
1045     do		/* yes, a do...while */
1046     {
1047 	if ((ch = *new++) != *old)
1048 	{
1049 	    /* new character is different from old	  */
1050 	    /* make sure the cursor is on top of this character */
1051 	    diff = newcol - lastcol;
1052 	    if (diff > 0)
1053 	    {
1054 		/* some motion is required--figure out which is shorter */
1055 		if (diff < 6 && cursor_on_line)
1056 		{
1057 		    /* overwrite old stuff--get it out of the old buffer */
1058 		    printf("%.*s", diff, &current[lastcol-start]);
1059 		}
1060 		else
1061 		{
1062 		    /* use cursor addressing */
1063 		    Move_to(newcol, line);
1064 		    cursor_on_line = Yes;
1065 		}
1066 		/* remember where the cursor is */
1067 		lastcol = newcol + 1;
1068 	    }
1069 	    else
1070 	    {
1071 		/* already there, update position */
1072 		lastcol++;
1073 	    }
1074 
1075 	    /* write what we need to */
1076 	    if (ch == '\0')
1077 	    {
1078 		/* at the end--terminate with a clear-to-end-of-line */
1079 		(void) clear_eol(strlen(old));
1080 	    }
1081 	    else
1082 	    {
1083 		/* write the new character */
1084 		putchar(ch);
1085 	    }
1086 	    /* put the new character in the screen buffer */
1087 	    *old = ch;
1088 	}
1089 
1090 	/* update working column and screen buffer pointer */
1091 	newcol++;
1092 	old++;
1093 
1094     } while (ch != '\0');
1095 
1096     /* zero out the rest of the line buffer -- MUST BE DONE! */
1097     diff = display_width - newcol;
1098     if (diff > 0)
1099     {
1100 	memset(old, 0, diff);
1101     }
1102 
1103     /* remember where the current line is */
1104     if (cursor_on_line)
1105     {
1106 	lastline = line;
1107     }
1108 }
1109 
1110 /*
1111  *  printable(str) - make the string pointed to by "str" into one that is
1112  *	printable (i.e.: all ascii), by converting all non-printable
1113  *	characters into '?'.  Replacements are done in place and a pointer
1114  *	to the original buffer is returned.
1115  */
1116 
1117 char *printable(str)
1118 
1119 char *str;
1120 
1121 {
1122     register char *ptr;
1123     register char ch;
1124 
1125     ptr = str;
1126     while ((ch = *ptr) != '\0')
1127     {
1128 	if (!isprint(ch))
1129 	{
1130 	    *ptr = '?';
1131 	}
1132 	ptr++;
1133     }
1134     return(str);
1135 }
1136