display.c revision 24142
1/*
2 *  Top users/processes display for Unix
3 *  Version 3
4 *
5 *  This program may be freely redistributed,
6 *  but this entire comment MUST remain intact.
7 *
8 *  Copyright (c) 1984, 1989, William LeFebvre, Rice University
9 *  Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
10 */
11
12/*
13 *  This file contains the routines that display information on the screen.
14 *  Each section of the screen has two routines:  one for initially writing
15 *  all constant and dynamic text, and one for only updating the text that
16 *  changes.  The prefix "i_" is used on all the "initial" routines and the
17 *  prefix "u_" is used for all the "updating" routines.
18 *
19 *  ASSUMPTIONS:
20 *        None of the "i_" routines use any of the termcap capabilities.
21 *        In this way, those routines can be safely used on terminals that
22 *        have minimal (or nonexistant) terminal capabilities.
23 *
24 *        The routines are called in this order:  *_loadave, i_timeofday,
25 *        *_procstates, *_cpustates, *_memory, *_message, *_header,
26 *        *_process, u_endscreen.
27 */
28
29#include "os.h"
30#include <ctype.h>
31#include <time.h>
32
33#include "screen.h"		/* interface to screen package */
34#include "layout.h"		/* defines for screen position layout */
35#include "display.h"
36#include "top.h"
37#include "top.local.h"
38#include "boolean.h"
39#include "machine.h"		/* we should eliminate this!!! */
40#include "utils.h"
41
42#ifdef DEBUG
43FILE *debug;
44#endif
45
46/* imported from screen.c */
47extern int overstrike;
48
49static int lmpid = 0;
50static int last_hi = 0;		/* used in u_process and u_endscreen */
51static int lastline = 0;
52static int display_width = MAX_COLS;
53
54#define lineindex(l) ((l)*display_width)
55
56char *printable();
57
58/* things initialized by display_init and used thruout */
59
60/* buffer of proc information lines for display updating */
61char *screenbuf = NULL;
62
63static char **procstate_names;
64static char **cpustate_names;
65static char **memory_names;
66static char **swap_names;
67
68static int num_procstates;
69static int num_cpustates;
70static int num_memory;
71static int num_swap;
72
73static int *lprocstates;
74static int *lcpustates;
75static int *lmemory;
76static int *lswap;
77
78static int *cpustate_columns;
79static int cpustate_total_length;
80
81static enum { OFF, ON, ERASE } header_status = ON;
82
83static int string_count();
84static void summary_format();
85static void line_update();
86
87int display_resize()
88
89{
90    register int lines;
91
92    /* first, deallocate any previous buffer that may have been there */
93    if (screenbuf != NULL)
94    {
95	free(screenbuf);
96    }
97
98    /* calculate the current dimensions */
99    /* if operating in "dumb" mode, we only need one line */
100    lines = smart_terminal ? screen_length - Header_lines : 1;
101
102    /* we don't want more than MAX_COLS columns, since the machine-dependent
103       modules make static allocations based on MAX_COLS and we don't want
104       to run off the end of their buffers */
105    display_width = screen_width;
106    if (display_width >= MAX_COLS)
107    {
108	display_width = MAX_COLS - 1;
109    }
110
111    /* now, allocate space for the screen buffer */
112    screenbuf = (char *)malloc(lines * display_width);
113    if (screenbuf == (char *)NULL)
114    {
115	/* oops! */
116	return(-1);
117    }
118
119    /* return number of lines available */
120    /* for dumb terminals, pretend like we can show any amount */
121    return(smart_terminal ? lines : Largest);
122}
123
124int display_init(statics)
125
126struct statics *statics;
127
128{
129    register int lines;
130    register char **pp;
131    register int *ip;
132    register int i;
133
134    /* call resize to do the dirty work */
135    lines = display_resize();
136
137    /* only do the rest if we need to */
138    if (lines > -1)
139    {
140	/* save pointers and allocate space for names */
141	procstate_names = statics->procstate_names;
142	num_procstates = string_count(procstate_names);
143	lprocstates = (int *)malloc(num_procstates * sizeof(int));
144
145	cpustate_names = statics->cpustate_names;
146
147	swap_names = statics->swap_names;
148	num_swap = string_count(swap_names);
149	lswap = (int *)malloc(num_swap * sizeof(int));
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(lines);
174}
175
176i_loadave(mpid, avenrun)
177
178int mpid;
179double *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
204u_loadave(mpid, avenrun)
205
206int mpid;
207double *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
243i_timeofday(tod)
244
245time_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
278static int ltotal = 0;
279static 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
288i_procstates(total, brkdn)
289
290int total;
291int *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
315u_procstates(total, brkdn)
316
317int total;
318int *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
369static int cpustates_column;
370
371/* cpustates_tag() calculates the correct tag to use to label the line */
372
373char *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
397i_cpustates(states)
398
399register 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
431u_cpustates(states)
432
433register 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
478z_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
514char memory_buffer[MAX_COLS];
515
516i_memory(stats)
517
518int *stats;
519
520{
521    fputs("\nMem: ", 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
529u_memory(stats)
530
531int *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 *  *_swap(stats) - print "Swap: " followed by the swap summary string
543 *
544 *  Assumptions:  cursor is on "lastline"
545 *                for i_swap ONLY: cursor is on the previous line
546 */
547
548char swap_buffer[MAX_COLS];
549
550i_swap(stats)
551
552int *stats;
553
554{
555    fputs("\nSwap: ", stdout);
556    lastline++;
557
558    /* format and print the swap summary */
559    summary_format(swap_buffer, stats, swap_names);
560    fputs(swap_buffer, stdout);
561}
562
563u_swap(stats)
564
565int *stats;
566
567{
568    static char new[MAX_COLS];
569
570    /* format the new line */
571    summary_format(new, stats, swap_names);
572    line_update(swap_buffer, new, x_swap, y_swap);
573}
574
575/*
576 *  *_message() - print the next pending message line, or erase the one
577 *                that is there.
578 *
579 *  Note that u_message is (currently) the same as i_message.
580 *
581 *  Assumptions:  lastline is consistent
582 */
583
584/*
585 *  i_message is funny because it gets its message asynchronously (with
586 *	respect to screen updates).
587 */
588
589static char next_msg[MAX_COLS + 5];
590static int msglen = 0;
591/* Invariant: msglen is always the length of the message currently displayed
592   on the screen (even when next_msg doesn't contain that message). */
593
594i_message()
595
596{
597    while (lastline < y_message)
598    {
599	fputc('\n', stdout);
600	lastline++;
601    }
602    if (next_msg[0] != '\0')
603    {
604	standout(next_msg);
605	msglen = strlen(next_msg);
606	next_msg[0] = '\0';
607    }
608    else if (msglen > 0)
609    {
610	(void) clear_eol(msglen);
611	msglen = 0;
612    }
613}
614
615u_message()
616
617{
618    i_message();
619}
620
621static int header_length;
622
623/*
624 *  *_header(text) - print the header for the process area
625 *
626 *  Assumptions:  cursor is on the previous line and lastline is consistent
627 */
628
629i_header(text)
630
631char *text;
632
633{
634    header_length = strlen(text);
635    if (header_status == ON)
636    {
637	putchar('\n');
638	fputs(text, stdout);
639	lastline++;
640    }
641    else if (header_status == ERASE)
642    {
643	header_status = OFF;
644    }
645}
646
647/*ARGSUSED*/
648u_header(text)
649
650char *text;		/* ignored */
651
652{
653    if (header_status == ERASE)
654    {
655	putchar('\n');
656	lastline++;
657	clear_eol(header_length);
658	header_status = OFF;
659    }
660}
661
662/*
663 *  *_process(line, thisline) - print one process line
664 *
665 *  Assumptions:  lastline is consistent
666 */
667
668i_process(line, thisline)
669
670int line;
671char *thisline;
672
673{
674    register char *p;
675    register char *base;
676
677    /* make sure we are on the correct line */
678    while (lastline < y_procs + line)
679    {
680	putchar('\n');
681	lastline++;
682    }
683
684    /* truncate the line to conform to our current screen width */
685    thisline[display_width] = '\0';
686
687    /* write the line out */
688    fputs(thisline, stdout);
689
690    /* copy it in to our buffer */
691    base = smart_terminal ? screenbuf + lineindex(line) : screenbuf;
692    p = strecpy(base, thisline);
693
694    /* zero fill the rest of it */
695    memzero(p, display_width - (p - base));
696}
697
698u_process(line, newline)
699
700int line;
701char *newline;
702
703{
704    register char *optr;
705    register int screen_line = line + Header_lines;
706    register char *bufferline;
707
708    /* remember a pointer to the current line in the screen buffer */
709    bufferline = &screenbuf[lineindex(line)];
710
711    /* truncate the line to conform to our current screen width */
712    newline[display_width] = '\0';
713
714    /* is line higher than we went on the last display? */
715    if (line >= last_hi)
716    {
717	/* yes, just ignore screenbuf and write it out directly */
718	/* get positioned on the correct line */
719	if (screen_line - lastline == 1)
720	{
721	    putchar('\n');
722	    lastline++;
723	}
724	else
725	{
726	    Move_to(0, screen_line);
727	    lastline = screen_line;
728	}
729
730	/* now write the line */
731	fputs(newline, stdout);
732
733	/* copy it in to the buffer */
734	optr = strecpy(bufferline, newline);
735
736	/* zero fill the rest of it */
737	memzero(optr, display_width - (optr - bufferline));
738    }
739    else
740    {
741	line_update(bufferline, newline, 0, line + Header_lines);
742    }
743}
744
745u_endscreen(hi)
746
747register int hi;
748
749{
750    register int screen_line = hi + Header_lines;
751    register int i;
752
753    if (smart_terminal)
754    {
755	if (hi < last_hi)
756	{
757	    /* need to blank the remainder of the screen */
758	    /* but only if there is any screen left below this line */
759	    if (lastline + 1 < screen_length)
760	    {
761		/* efficiently move to the end of currently displayed info */
762		if (screen_line - lastline < 5)
763		{
764		    while (lastline < screen_line)
765		    {
766			putchar('\n');
767			lastline++;
768		    }
769		}
770		else
771		{
772		    Move_to(0, screen_line);
773		    lastline = screen_line;
774		}
775
776		if (clear_to_end)
777		{
778		    /* we can do this the easy way */
779		    putcap(clear_to_end);
780		}
781		else
782		{
783		    /* use clear_eol on each line */
784		    i = hi;
785		    while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi)
786		    {
787			putchar('\n');
788		    }
789		}
790	    }
791	}
792	last_hi = hi;
793
794	/* move the cursor to a pleasant place */
795	Move_to(x_idlecursor, y_idlecursor);
796	lastline = y_idlecursor;
797    }
798    else
799    {
800	/* separate this display from the next with some vertical room */
801	fputs("\n\n", stdout);
802    }
803}
804
805display_header(t)
806
807int t;
808
809{
810    if (t)
811    {
812	header_status = ON;
813    }
814    else if (header_status == ON)
815    {
816	header_status = ERASE;
817    }
818}
819
820/*VARARGS2*/
821new_message(type, msgfmt, a1, a2, a3)
822
823int type;
824char *msgfmt;
825caddr_t a1, a2, a3;
826
827{
828    register int i;
829
830    /* first, format the message */
831    (void) sprintf(next_msg, msgfmt, a1, a2, a3);
832
833    if (msglen > 0)
834    {
835	/* message there already -- can we clear it? */
836	if (!overstrike)
837	{
838	    /* yes -- write it and clear to end */
839	    i = strlen(next_msg);
840	    if ((type & MT_delayed) == 0)
841	    {
842		type & MT_standout ? standout(next_msg) :
843		                     fputs(next_msg, stdout);
844		(void) clear_eol(msglen - i);
845		msglen = i;
846		next_msg[0] = '\0';
847	    }
848	}
849    }
850    else
851    {
852	if ((type & MT_delayed) == 0)
853	{
854	    type & MT_standout ? standout(next_msg) : fputs(next_msg, stdout);
855	    msglen = strlen(next_msg);
856	    next_msg[0] = '\0';
857	}
858    }
859}
860
861clear_message()
862
863{
864    if (clear_eol(msglen) == 1)
865    {
866	putchar('\r');
867    }
868}
869
870readline(buffer, size, numeric)
871
872char *buffer;
873int  size;
874int  numeric;
875
876{
877    register char *ptr = buffer;
878    register char ch;
879    register char cnt = 0;
880    register char maxcnt = 0;
881
882    /* allow room for null terminator */
883    size -= 1;
884
885    /* read loop */
886    while ((fflush(stdout), read(0, ptr, 1) > 0))
887    {
888	/* newline means we are done */
889	if ((ch = *ptr) == '\n' || ch == '\r')
890	{
891	    break;
892	}
893
894	/* handle special editing characters */
895	if (ch == ch_kill)
896	{
897	    /* kill line -- account for overstriking */
898	    if (overstrike)
899	    {
900		msglen += maxcnt;
901	    }
902
903	    /* return null string */
904	    *buffer = '\0';
905	    putchar('\r');
906	    return(-1);
907	}
908	else if (ch == ch_erase)
909	{
910	    /* erase previous character */
911	    if (cnt <= 0)
912	    {
913		/* none to erase! */
914		putchar('\7');
915	    }
916	    else
917	    {
918		fputs("\b \b", stdout);
919		ptr--;
920		cnt--;
921	    }
922	}
923	/* check for character validity and buffer overflow */
924	else if (cnt == size || (numeric && !isdigit(ch)) ||
925		!isprint(ch))
926	{
927	    /* not legal */
928	    putchar('\7');
929	}
930	else
931	{
932	    /* echo it and store it in the buffer */
933	    putchar(ch);
934	    ptr++;
935	    cnt++;
936	    if (cnt > maxcnt)
937	    {
938		maxcnt = cnt;
939	    }
940	}
941    }
942
943    /* all done -- null terminate the string */
944    *ptr = '\0';
945
946    /* account for the extra characters in the message area */
947    /* (if terminal overstrikes, remember the furthest they went) */
948    msglen += overstrike ? maxcnt : cnt;
949
950    /* return either inputted number or string length */
951    putchar('\r');
952    return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
953}
954
955/* internal support routines */
956
957static int string_count(pp)
958
959register char **pp;
960
961{
962    register int cnt;
963
964    cnt = 0;
965    while (*pp++ != NULL)
966    {
967	cnt++;
968    }
969    return(cnt);
970}
971
972static void summary_format(str, numbers, names)
973
974char *str;
975int *numbers;
976register char **names;
977
978{
979    register char *p;
980    register int num;
981    register char *thisname;
982    register int useM = No;
983
984    /* format each number followed by its string */
985    p = str;
986    while ((thisname = *names++) != NULL)
987    {
988	/* get the number to format */
989	num = *numbers++;
990
991	/* display only non-zero numbers */
992	if (num > 0)
993	{
994	    /* is this number in kilobytes? */
995	    if (thisname[0] == 'K')
996	    {
997		/* yes: format it as a memory value */
998		p = strecpy(p, format_k(num));
999
1000		/* skip over the K, since it was included by format_k */
1001		p = strecpy(p, thisname+1);
1002	    }
1003	    else
1004	    {
1005		p = strecpy(p, itoa(num));
1006		p = strecpy(p, thisname);
1007	    }
1008	}
1009
1010	/* ignore negative numbers, but display corresponding string */
1011	else if (num < 0)
1012	{
1013	    p = strecpy(p, thisname);
1014	}
1015    }
1016
1017    /* if the last two characters in the string are ", ", delete them */
1018    p -= 2;
1019    if (p >= str && p[0] == ',' && p[1] == ' ')
1020    {
1021	*p = '\0';
1022    }
1023}
1024
1025static void line_update(old, new, start, line)
1026
1027register char *old;
1028register char *new;
1029int start;
1030int line;
1031
1032{
1033    register int ch;
1034    register int diff;
1035    register int newcol = start + 1;
1036    register int lastcol = start;
1037    char cursor_on_line = No;
1038    char *current;
1039
1040    /* compare the two strings and only rewrite what has changed */
1041    current = old;
1042#ifdef DEBUG
1043    fprintf(debug, "line_update, starting at %d\n", start);
1044    fputs(old, debug);
1045    fputc('\n', debug);
1046    fputs(new, debug);
1047    fputs("\n-\n", debug);
1048#endif
1049
1050    /* start things off on the right foot		    */
1051    /* this is to make sure the invariants get set up right */
1052    if ((ch = *new++) != *old)
1053    {
1054	if (line - lastline == 1 && start == 0)
1055	{
1056	    putchar('\n');
1057	}
1058	else
1059	{
1060	    Move_to(start, line);
1061	}
1062	cursor_on_line = Yes;
1063	putchar(ch);
1064	*old = ch;
1065	lastcol = 1;
1066    }
1067    old++;
1068
1069    /*
1070     *  main loop -- check each character.  If the old and new aren't the
1071     *	same, then update the display.  When the distance from the
1072     *	current cursor position to the new change is small enough,
1073     *	the characters that belong there are written to move the
1074     *	cursor over.
1075     *
1076     *	Invariants:
1077     *	    lastcol is the column where the cursor currently is sitting
1078     *		(always one beyond the end of the last mismatch).
1079     */
1080    do		/* yes, a do...while */
1081    {
1082	if ((ch = *new++) != *old)
1083	{
1084	    /* new character is different from old	  */
1085	    /* make sure the cursor is on top of this character */
1086	    diff = newcol - lastcol;
1087	    if (diff > 0)
1088	    {
1089		/* some motion is required--figure out which is shorter */
1090		if (diff < 6 && cursor_on_line)
1091		{
1092		    /* overwrite old stuff--get it out of the old buffer */
1093		    printf("%.*s", diff, &current[lastcol-start]);
1094		}
1095		else
1096		{
1097		    /* use cursor addressing */
1098		    Move_to(newcol, line);
1099		    cursor_on_line = Yes;
1100		}
1101		/* remember where the cursor is */
1102		lastcol = newcol + 1;
1103	    }
1104	    else
1105	    {
1106		/* already there, update position */
1107		lastcol++;
1108	    }
1109
1110	    /* write what we need to */
1111	    if (ch == '\0')
1112	    {
1113		/* at the end--terminate with a clear-to-end-of-line */
1114		(void) clear_eol(strlen(old));
1115	    }
1116	    else
1117	    {
1118		/* write the new character */
1119		putchar(ch);
1120	    }
1121	    /* put the new character in the screen buffer */
1122	    *old = ch;
1123	}
1124
1125	/* update working column and screen buffer pointer */
1126	newcol++;
1127	old++;
1128
1129    } while (ch != '\0');
1130
1131    /* zero out the rest of the line buffer -- MUST BE DONE! */
1132    diff = display_width - newcol;
1133    if (diff > 0)
1134    {
1135	memzero(old, diff);
1136    }
1137
1138    /* remember where the current line is */
1139    if (cursor_on_line)
1140    {
1141	lastline = line;
1142    }
1143}
1144
1145/*
1146 *  printable(str) - make the string pointed to by "str" into one that is
1147 *	printable (i.e.: all ascii), by converting all non-printable
1148 *	characters into '?'.  Replacements are done in place and a pointer
1149 *	to the original buffer is returned.
1150 */
1151
1152char *printable(str)
1153
1154char *str;
1155
1156{
1157    register char *ptr;
1158    register char ch;
1159
1160    ptr = str;
1161    while ((ch = *ptr) != '\0')
1162    {
1163	if (!isprint(ch))
1164	{
1165	    *ptr = '?';
1166	}
1167	ptr++;
1168    }
1169    return(str);
1170}
1171