]> pd.if.org Git - pd_readline/blob - mg/display.c
Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg...
[pd_readline] / mg / display.c
1 /*      $OpenBSD: display.c,v 1.37 2009/06/04 02:23:37 kjell Exp $      */
2
3 /* This file is in the public domain. */
4
5 /*
6  * The functions in this file handle redisplay. The
7  * redisplay system knows almost nothing about the editing
8  * process; the editing functions do, however, set some
9  * hints to eliminate a lot of the grinding. There is more
10  * that can be done; the "vtputc" interface is a real
11  * pig.
12  */
13 #include "def.h"
14 #include "kbd.h"
15
16 #include <ctype.h>
17
18 /*
19  * You can change these back to the types
20  * implied by the name if you get tight for space. If you
21  * make both of them "int" you get better code on the VAX.
22  * They do nothing if this is not Gosling redisplay, except
23  * for change the size of a structure that isn't used.
24  * A bit of a cheat.
25  */
26 #define XCHAR   int
27 #define XSHORT  int
28
29 #ifdef  STANDOUT_GLITCH
30 #include <term.h>
31 #endif
32
33 /*
34  * A video structure always holds
35  * an array of characters whose length is equal to
36  * the longest line possible. v_text is allocated
37  * dynamically to fit the screen width.
38  */
39 struct video {
40         short   v_hash;         /* Hash code, for compares.      */
41         short   v_flag;         /* Flag word.                    */
42         short   v_color;        /* Color of the line.            */
43         XSHORT  v_cost;         /* Cost of display.              */
44         char    *v_text;        /* The actual characters.        */
45 };
46
47 #define VFCHG   0x0001                  /* Changed.                      */
48 #define VFHBAD  0x0002                  /* Hash and cost are bad.        */
49 #define VFEXT   0x0004                  /* extended line (beond ncol)    */
50
51 /*
52  * SCORE structures hold the optimal
53  * trace trajectory, and the cost of redisplay, when
54  * the dynamic programming redisplay code is used.
55  * If no fancy redisplay, this isn't used. The trace index
56  * fields can be "char", and the cost a "short", but
57  * this makes the code worse on the VAX.
58  */
59 struct score {
60         XCHAR   s_itrace;       /* "i" index for track back.     */
61         XCHAR   s_jtrace;       /* "j" index for trace back.     */
62         XSHORT  s_cost;         /* Display cost.                 */
63 };
64
65 void    vtmove(int, int);
66 void    vtputc(int);
67 void    vtpute(int);
68 int     vtputs(const char *);
69 void    vteeol(void);
70 void    updext(int, int);
71 void    modeline(struct mgwin *);
72 void    setscores(int, int);
73 void    traceback(int, int, int, int);
74 void    ucopy(struct video *, struct video *);
75 void    uline(int, struct video *, struct video *);
76 void    hash(struct video *);
77
78
79 int     sgarbf = TRUE;          /* TRUE if screen is garbage.    */
80 int     vtrow = HUGE;           /* Virtual cursor row.           */
81 int     vtcol = HUGE;           /* Virtual cursor column.        */
82 int     tthue = CNONE;          /* Current color.                */
83 int     ttrow = HUGE;           /* Physical cursor row.          */
84 int     ttcol = HUGE;           /* Physical cursor column.       */
85 int     tttop = HUGE;           /* Top of scroll region.         */
86 int     ttbot = HUGE;           /* Bottom of scroll region.      */
87 int     lbound = 0;             /* leftmost bound of the current */
88                                 /* line being displayed          */
89
90 struct video    **vscreen;              /* Edge vector, virtual.         */
91 struct video    **pscreen;              /* Edge vector, physical.        */
92 struct video     *video;                /* Actual screen data.           */
93 struct video      blanks;               /* Blank line image.             */
94
95 /*
96  * This matrix is written as an array because
97  * we do funny things in the "setscores" routine, which
98  * is very compute intensive, to make the subscripts go away.
99  * It would be "SCORE   score[NROW][NROW]" in old speak.
100  * Look at "setscores" to understand what is up.
101  */
102 struct score *score;                    /* [NROW * NROW] */
103
104 #ifndef LINENOMODE
105 #define LINENOMODE TRUE
106 #endif /* !LINENOMODE */
107 static int       linenos = LINENOMODE;
108
109 /* Is macro recording enabled? */
110 extern int macrodef;
111 /* Is working directory global? */
112 extern int globalwd;
113
114 /*
115  * Since we don't have variables (we probably should) these are command
116  * processors for changing the values of mode flags.
117  */
118 /* ARGSUSED */
119 int
120 linenotoggle(int f, int n)
121 {
122         if (f & FFARG)
123                 linenos = n > 0;
124         else
125                 linenos = !linenos;
126
127         sgarbf = TRUE;
128
129         return (TRUE);
130 }
131
132 /*
133  * Reinit the display data structures, this is called when the terminal
134  * size changes.
135  */
136 int
137 vtresize(int force, int newrow, int newcol)
138 {
139         int      i;
140         int      rowchanged, colchanged;
141         static   int first_run = 1;
142         struct video    *vp;
143
144         if (newrow < 1 || newcol < 1)
145                 return (FALSE);
146
147         rowchanged = (newrow != nrow);
148         colchanged = (newcol != ncol);
149
150 #define TRYREALLOC(a, n) do {                                   \
151                 void *tmp;                                      \
152                 if ((tmp = realloc((a), (n))) == NULL) {        \
153                         panic("out of memory in display code"); \
154                 }                                               \
155                 (a) = tmp;                                      \
156         } while (0)
157
158         /* No update needed */
159         if (!first_run && !force && !rowchanged && !colchanged)
160                 return (TRUE);
161
162         if (first_run)
163                 memset(&blanks, 0, sizeof(blanks));
164
165         if (rowchanged || first_run) {
166                 int vidstart;
167
168                 /*
169                  * This is not pretty.
170                  */
171                 if (nrow == 0)
172                         vidstart = 0;
173                 else
174                         vidstart = 2 * (nrow - 1);
175
176                 /*
177                  * We're shrinking, free some internal data.
178                  */
179                 if (newrow < nrow) {
180                         for (i = 2 * (newrow - 1); i < 2 * (nrow - 1); i++) {
181                                 free(video[i].v_text);
182                                 video[i].v_text = NULL;
183                         }
184                 }
185
186                 TRYREALLOC(score, newrow * newrow * sizeof(struct score));
187                 TRYREALLOC(vscreen, (newrow - 1) * sizeof(struct video *));
188                 TRYREALLOC(pscreen, (newrow - 1) * sizeof(struct video *));
189                 TRYREALLOC(video, (2 * (newrow - 1)) * sizeof(struct video));
190
191                 /*
192                  * Zero-out the entries we just allocated.
193                  */
194                 for (i = vidstart; i < 2 * (newrow - 1); i++)
195                         memset(&video[i], 0, sizeof(struct video));
196
197                 /*
198                  * Reinitialize vscreen and pscreen arrays completely.
199                  */
200                 vp = &video[0];
201                 for (i = 0; i < newrow - 1; ++i) {
202                         vscreen[i] = vp;
203                         ++vp;
204                         pscreen[i] = vp;
205                         ++vp;
206                 }
207         }
208         if (rowchanged || colchanged || first_run) {
209                 for (i = 0; i < 2 * (newrow - 1); i++)
210                         TRYREALLOC(video[i].v_text, newcol * sizeof(char));
211                 TRYREALLOC(blanks.v_text, newcol * sizeof(char));
212         }
213
214         nrow = newrow;
215         ncol = newcol;
216
217         if (ttrow > nrow)
218                 ttrow = nrow;
219         if (ttcol > ncol)
220                 ttcol = ncol;
221
222         first_run = 0;
223         return (TRUE);
224 }
225
226 #undef TRYREALLOC
227
228 /*
229  * Initialize the data structures used
230  * by the display code. The edge vectors used
231  * to access the screens are set up. The operating
232  * system's terminal I/O channel is set up. Fill the
233  * "blanks" array with ASCII blanks. The rest is done
234  * at compile time. The original window is marked
235  * as needing full update, and the physical screen
236  * is marked as garbage, so all the right stuff happens
237  * on the first call to redisplay.
238  */
239 void
240 vtinit(void)
241 {
242         int     i;
243
244         ttopen();
245         ttinit();
246
247         /*
248          * ttinit called ttresize(), which called vtresize(), so our data
249          * structures are setup correctly.
250          */
251
252         blanks.v_color = CTEXT;
253         for (i = 0; i < ncol; ++i)
254                 blanks.v_text[i] = ' ';
255 }
256
257 /*
258  * Tidy up the virtual display system
259  * in anticipation of a return back to the host
260  * operating system. Right now all we do is position
261  * the cursor to the last line, erase the line, and
262  * close the terminal channel.
263  */
264 void
265 vttidy(void)
266 {
267         ttcolor(CTEXT);
268         ttnowindow();           /* No scroll window.     */
269         ttmove(nrow - 1, 0);    /* Echo line.            */
270         tteeol();
271         tttidy();
272         ttflush();
273         ttclose();
274 }
275
276 /*
277  * Move the virtual cursor to an origin
278  * 0 spot on the virtual display screen. I could
279  * store the column as a character pointer to the spot
280  * on the line, which would make "vtputc" a little bit
281  * more efficient. No checking for errors.
282  */
283 void
284 vtmove(int row, int col)
285 {
286         vtrow = row;
287         vtcol = col;
288 }
289
290 /*
291  * Write a character to the virtual display,
292  * dealing with long lines and the display of unprintable
293  * things like control characters. Also expand tabs every 8
294  * columns. This code only puts printing characters into
295  * the virtual display image. Special care must be taken when
296  * expanding tabs. On a screen whose width is not a multiple
297  * of 8, it is possible for the virtual cursor to hit the
298  * right margin before the next tab stop is reached. This
299  * makes the tab code loop if you are not careful.
300  * Three guesses how we found this.
301  */
302 void
303 vtputc(int c)
304 {
305         struct video    *vp;
306
307         c &= 0xff;
308
309         vp = vscreen[vtrow];
310         if (vtcol >= ncol)
311                 vp->v_text[ncol - 1] = '$';
312         else if (c == '\t'
313 #ifdef  NOTAB
314             && !(curbp->b_flag & BFNOTAB)
315 #endif
316             ) {
317                 do {
318                         vtputc(' ');
319                 } while (vtcol < ncol && (vtcol & 0x07) != 0);
320         } else if (ISCTRL(c)) {
321                 vtputc('^');
322                 vtputc(CCHR(c));
323         } else if (isprint(c))
324                 vp->v_text[vtcol++] = c;
325         else {
326                 char bf[5];
327
328                 snprintf(bf, sizeof(bf), "\\%o", c);
329                 vtputs(bf);
330         }
331 }
332
333 /*
334  * Put a character to the virtual screen in an extended line.  If we are not
335  * yet on left edge, don't print it yet.  Check for overflow on the right
336  * margin.
337  */
338 void
339 vtpute(int c)
340 {
341         struct video *vp;
342
343         c &= 0xff;
344
345         vp = vscreen[vtrow];
346         if (vtcol >= ncol)
347                 vp->v_text[ncol - 1] = '$';
348         else if (c == '\t'
349 #ifdef  NOTAB
350             && !(curbp->b_flag & BFNOTAB)
351 #endif
352             ) {
353                 do {
354                         vtpute(' ');
355                 } while (((vtcol + lbound) & 0x07) != 0 && vtcol < ncol);
356         } else if (ISCTRL(c) != FALSE) {
357                 vtpute('^');
358                 vtpute(CCHR(c));
359         } else {
360                 if (vtcol >= 0)
361                         vp->v_text[vtcol] = c;
362                 ++vtcol;
363         }
364 }
365
366 /*
367  * Erase from the end of the software cursor to the end of the line on which
368  * the software cursor is located. The display routines will decide if a
369  * hardware erase to end of line command should be used to display this.
370  */
371 void
372 vteeol(void)
373 {
374         struct video *vp;
375
376         vp = vscreen[vtrow];
377         while (vtcol < ncol)
378                 vp->v_text[vtcol++] = ' ';
379 }
380
381 /*
382  * Make sure that the display is
383  * right. This is a three part process. First,
384  * scan through all of the windows looking for dirty
385  * ones. Check the framing, and refresh the screen.
386  * Second, make sure that "currow" and "curcol" are
387  * correct for the current window. Third, make the
388  * virtual and physical screens the same.
389  */
390 void
391 update(void)
392 {
393         struct line     *lp;
394         struct mgwin    *wp;
395         struct video    *vp1;
396         struct video    *vp2;
397         int      c, i, j;
398         int      hflag;
399         int      currow, curcol;
400         int      offs, size;
401
402         if (charswaiting())
403                 return;
404         if (sgarbf) {           /* must update everything */
405                 wp = wheadp;
406                 while (wp != NULL) {
407                         wp->w_rflag |= WFMODE | WFFULL;
408                         wp = wp->w_wndp;
409                 }
410         }
411         if (linenos) {
412                 wp = wheadp;
413                 while (wp != NULL) {
414                         wp->w_rflag |= WFMODE;
415                         wp = wp->w_wndp;
416                 }
417         }
418         hflag = FALSE;                  /* Not hard. */
419         for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
420                 /*
421                  * Nothing to be done.
422                  */
423                 if (wp->w_rflag == 0)
424                         continue;
425
426                 if ((wp->w_rflag & WFFRAME) == 0) {
427                         lp = wp->w_linep;
428                         for (i = 0; i < wp->w_ntrows; ++i) {
429                                 if (lp == wp->w_dotp)
430                                         goto out;
431                                 if (lp == wp->w_bufp->b_headp)
432                                         break;
433                                 lp = lforw(lp);
434                         }
435                 }
436                 /*
437                  * Put the middle-line in place.
438                  */
439                 i = wp->w_frame;
440                 if (i > 0) {
441                         --i;
442                         if (i >= wp->w_ntrows)
443                                 i = wp->w_ntrows - 1;
444                 } else if (i < 0) {
445                         i += wp->w_ntrows;
446                         if (i < 0)
447                                 i = 0;
448                 } else
449                         i = wp->w_ntrows / 2; /* current center, no change */
450
451                 /*
452                  * Find the line.
453                  */
454                 lp = wp->w_dotp;
455                 while (i != 0 && lback(lp) != wp->w_bufp->b_headp) {
456                         --i;
457                         lp = lback(lp);
458                 }
459                 wp->w_linep = lp;
460                 wp->w_rflag |= WFFULL;  /* Force full.           */
461         out:
462                 lp = wp->w_linep;       /* Try reduced update.   */
463                 i = wp->w_toprow;
464                 if ((wp->w_rflag & ~WFMODE) == WFEDIT) {
465                         while (lp != wp->w_dotp) {
466                                 ++i;
467                                 lp = lforw(lp);
468                         }
469                         vscreen[i]->v_color = CTEXT;
470                         vscreen[i]->v_flag |= (VFCHG | VFHBAD);
471                         vtmove(i, 0);
472                         for (j = 0; j < llength(lp); ++j)
473                                 vtputc(lgetc(lp, j));
474                         vteeol();
475                 } else if ((wp->w_rflag & (WFEDIT | WFFULL)) != 0) {
476                         hflag = TRUE;
477                         while (i < wp->w_toprow + wp->w_ntrows) {
478                                 vscreen[i]->v_color = CTEXT;
479                                 vscreen[i]->v_flag |= (VFCHG | VFHBAD);
480                                 vtmove(i, 0);
481                                 if (lp != wp->w_bufp->b_headp) {
482                                         for (j = 0; j < llength(lp); ++j)
483                                                 vtputc(lgetc(lp, j));
484                                         lp = lforw(lp);
485                                 }
486                                 vteeol();
487                                 ++i;
488                         }
489                 }
490                 if ((wp->w_rflag & WFMODE) != 0)
491                         modeline(wp);
492                 wp->w_rflag = 0;
493                 wp->w_frame = 0;
494         }
495         lp = curwp->w_linep;    /* Cursor location. */
496         currow = curwp->w_toprow;
497         while (lp != curwp->w_dotp) {
498                 ++currow;
499                 lp = lforw(lp);
500         }
501         curcol = 0;
502         i = 0;
503         while (i < curwp->w_doto) {
504                 c = lgetc(lp, i++);
505                 if (c == '\t'
506 #ifdef  NOTAB
507                     && !(curbp->b_flag & BFNOTAB)
508 #endif
509                         ) {
510                         curcol |= 0x07;
511                         curcol++;
512                 } else if (ISCTRL(c) != FALSE)
513                         curcol += 2;
514                 else if (isprint(c))
515                         curcol++;
516                 else {
517                         char bf[5];
518
519                         snprintf(bf, sizeof(bf), "\\%o", c);
520                         curcol += strlen(bf);
521                 }
522         }
523         if (curcol >= ncol - 1) {       /* extended line. */
524                 /* flag we are extended and changed */
525                 vscreen[currow]->v_flag |= VFEXT | VFCHG;
526                 updext(currow, curcol); /* and output extended line */
527         } else
528                 lbound = 0;     /* not extended line */
529
530         /*
531          * Make sure no lines need to be de-extended because the cursor is no
532          * longer on them.
533          */
534         wp = wheadp;
535         while (wp != NULL) {
536                 lp = wp->w_linep;
537                 i = wp->w_toprow;
538                 while (i < wp->w_toprow + wp->w_ntrows) {
539                         if (vscreen[i]->v_flag & VFEXT) {
540                                 /* always flag extended lines as changed */
541                                 vscreen[i]->v_flag |= VFCHG;
542                                 if ((wp != curwp) || (lp != wp->w_dotp) ||
543                                     (curcol < ncol - 1)) {
544                                         vtmove(i, 0);
545                                         for (j = 0; j < llength(lp); ++j)
546                                                 vtputc(lgetc(lp, j));
547                                         vteeol();
548                                         /* this line no longer is extended */
549                                         vscreen[i]->v_flag &= ~VFEXT;
550                                 }
551                         }
552                         lp = lforw(lp);
553                         ++i;
554                 }
555                 /* if garbaged then fix up mode lines */
556                 if (sgarbf != FALSE)
557                         vscreen[i]->v_flag |= VFCHG;
558                 /* and onward to the next window */
559                 wp = wp->w_wndp;
560         }
561
562         if (sgarbf != FALSE) {  /* Screen is garbage.    */
563                 sgarbf = FALSE; /* Erase-page clears.    */
564                 epresf = FALSE; /* The message area.     */
565                 tttop = HUGE;   /* Forget where you set. */
566                 ttbot = HUGE;   /* scroll region.        */
567                 tthue = CNONE;  /* Color unknown.        */
568                 ttmove(0, 0);
569                 tteeop();
570                 for (i = 0; i < nrow - 1; ++i) {
571                         uline(i, vscreen[i], &blanks);
572                         ucopy(vscreen[i], pscreen[i]);
573                 }
574                 ttmove(currow, curcol - lbound);
575                 ttflush();
576                 return;
577         }
578         if (hflag != FALSE) {                   /* Hard update?         */
579                 for (i = 0; i < nrow - 1; ++i) {/* Compute hash data.   */
580                         hash(vscreen[i]);
581                         hash(pscreen[i]);
582                 }
583                 offs = 0;                       /* Get top match.       */
584                 while (offs != nrow - 1) {
585                         vp1 = vscreen[offs];
586                         vp2 = pscreen[offs];
587                         if (vp1->v_color != vp2->v_color
588                             || vp1->v_hash != vp2->v_hash)
589                                 break;
590                         uline(offs, vp1, vp2);
591                         ucopy(vp1, vp2);
592                         ++offs;
593                 }
594                 if (offs == nrow - 1) {         /* Might get it all.    */
595                         ttmove(currow, curcol - lbound);
596                         ttflush();
597                         return;
598                 }
599                 size = nrow - 1;                /* Get bottom match.    */
600                 while (size != offs) {
601                         vp1 = vscreen[size - 1];
602                         vp2 = pscreen[size - 1];
603                         if (vp1->v_color != vp2->v_color
604                             || vp1->v_hash != vp2->v_hash)
605                                 break;
606                         uline(size - 1, vp1, vp2);
607                         ucopy(vp1, vp2);
608                         --size;
609                 }
610                 if ((size -= offs) == 0)        /* Get screen size.     */
611                         panic("Illegal screen size in update");
612                 setscores(offs, size);          /* Do hard update.      */
613                 traceback(offs, size, size, size);
614                 for (i = 0; i < size; ++i)
615                         ucopy(vscreen[offs + i], pscreen[offs + i]);
616                 ttmove(currow, curcol - lbound);
617                 ttflush();
618                 return;
619         }
620         for (i = 0; i < nrow - 1; ++i) {        /* Easy update.         */
621                 vp1 = vscreen[i];
622                 vp2 = pscreen[i];
623                 if ((vp1->v_flag & VFCHG) != 0) {
624                         uline(i, vp1, vp2);
625                         ucopy(vp1, vp2);
626                 }
627         }
628         ttmove(currow, curcol - lbound);
629         ttflush();
630 }
631
632 /*
633  * Update a saved copy of a line,
634  * kept in a video structure. The "vvp" is
635  * the one in the "vscreen". The "pvp" is the one
636  * in the "pscreen". This is called to make the
637  * virtual and physical screens the same when
638  * display has done an update.
639  */
640 void
641 ucopy(struct video *vvp, struct video *pvp)
642 {
643         vvp->v_flag &= ~VFCHG;          /* Changes done.         */
644         pvp->v_flag = vvp->v_flag;      /* Update model.         */
645         pvp->v_hash = vvp->v_hash;
646         pvp->v_cost = vvp->v_cost;
647         pvp->v_color = vvp->v_color;
648         bcopy(vvp->v_text, pvp->v_text, ncol);
649 }
650
651 /*
652  * updext: update the extended line which the cursor is currently on at a
653  * column greater than the terminal width. The line will be scrolled right or
654  * left to let the user see where the cursor is.
655  */
656 void
657 updext(int currow, int curcol)
658 {
659         struct line     *lp;                    /* pointer to current line */
660         int      j;                     /* index into line */
661
662         if (ncol < 2)
663                 return;
664
665         /*
666          * calculate what column the left bound should be
667          * (force cursor into middle half of screen)
668          */
669         lbound = curcol - (curcol % (ncol >> 1)) - (ncol >> 2);
670
671         /*
672          * scan through the line outputing characters to the virtual screen
673          * once we reach the left edge
674          */
675         vtmove(currow, -lbound);                /* start scanning offscreen */
676         lp = curwp->w_dotp;                     /* line to output */
677         for (j = 0; j < llength(lp); ++j)       /* until the end-of-line */
678                 vtpute(lgetc(lp, j));
679         vteeol();                               /* truncate the virtual line */
680         vscreen[currow]->v_text[0] = '$';       /* and put a '$' in column 1 */
681 }
682
683 /*
684  * Update a single line. This routine only
685  * uses basic functionality (no insert and delete character,
686  * but erase to end of line). The "vvp" points at the video
687  * structure for the line on the virtual screen, and the "pvp"
688  * is the same for the physical screen. Avoid erase to end of
689  * line when updating CMODE color lines, because of the way that
690  * reverse video works on most terminals.
691  */
692 void
693 uline(int row, struct video *vvp, struct video *pvp)
694 {
695         char  *cp1;
696         char  *cp2;
697         char  *cp3;
698         char  *cp4;
699         char  *cp5;
700         int    nbflag;
701
702         if (vvp->v_color != pvp->v_color) {     /* Wrong color, do a     */
703                 ttmove(row, 0);                 /* full redraw.          */
704 #ifdef  STANDOUT_GLITCH
705                 if (pvp->v_color != CTEXT && magic_cookie_glitch >= 0)
706                         tteeol();
707 #endif
708                 ttcolor(vvp->v_color);
709 #ifdef  STANDOUT_GLITCH
710                 cp1 = &vvp->v_text[magic_cookie_glitch > 0 ? magic_cookie_glitch : 0];
711                 /*
712                  * The odd code for magic_cookie_glitch==0 is to avoid
713                  * putting the invisible glitch character on the next line.
714                  * (Hazeltine executive 80 model 30)
715                  */
716                 cp2 = &vvp->v_text[ncol - (magic_cookie_glitch >= 0 ?
717                     (magic_cookie_glitch != 0 ? magic_cookie_glitch : 1) : 0)];
718 #else
719                 cp1 = &vvp->v_text[0];
720                 cp2 = &vvp->v_text[ncol];
721 #endif
722                 while (cp1 != cp2) {
723                         ttputc(*cp1++);
724                         ++ttcol;
725                 }
726 #ifndef MOVE_STANDOUT
727                 ttcolor(CTEXT);
728 #endif
729                 return;
730         }
731         cp1 = &vvp->v_text[0];          /* Compute left match.   */
732         cp2 = &pvp->v_text[0];
733         while (cp1 != &vvp->v_text[ncol] && cp1[0] == cp2[0]) {
734                 ++cp1;
735                 ++cp2;
736         }
737         if (cp1 == &vvp->v_text[ncol])  /* All equal.            */
738                 return;
739         nbflag = FALSE;
740         cp3 = &vvp->v_text[ncol];       /* Compute right match.  */
741         cp4 = &pvp->v_text[ncol];
742         while (cp3[-1] == cp4[-1]) {
743                 --cp3;
744                 --cp4;
745                 if (cp3[0] != ' ')      /* Note non-blanks in    */
746                         nbflag = TRUE;  /* the right match.      */
747         }
748         cp5 = cp3;                      /* Is erase good?        */
749         if (nbflag == FALSE && vvp->v_color == CTEXT) {
750                 while (cp5 != cp1 && cp5[-1] == ' ')
751                         --cp5;
752                 /* Alcyon hack */
753                 if ((int) (cp3 - cp5) <= tceeol)
754                         cp5 = cp3;
755         }
756         /* Alcyon hack */
757         ttmove(row, (int) (cp1 - &vvp->v_text[0]));
758 #ifdef  STANDOUT_GLITCH
759         if (vvp->v_color != CTEXT && magic_cookie_glitch > 0) {
760                 if (cp1 < &vvp->v_text[magic_cookie_glitch])
761                         cp1 = &vvp->v_text[magic_cookie_glitch];
762                 if (cp5 > &vvp->v_text[ncol - magic_cookie_glitch])
763                         cp5 = &vvp->v_text[ncol - magic_cookie_glitch];
764         } else if (magic_cookie_glitch < 0)
765 #endif
766                 ttcolor(vvp->v_color);
767         while (cp1 != cp5) {
768                 ttputc(*cp1++);
769                 ++ttcol;
770         }
771         if (cp5 != cp3)                 /* Do erase.             */
772                 tteeol();
773 }
774
775 /*
776  * Redisplay the mode line for the window pointed to by the "wp".
777  * This is the only routine that has any idea of how the mode line is
778  * formatted. You can change the modeline format by hacking at this
779  * routine. Called by "update" any time there is a dirty window.  Note
780  * that if STANDOUT_GLITCH is defined, first and last magic_cookie_glitch
781  * characters may never be seen.
782  */
783 void
784 modeline(struct mgwin *wp)
785 {
786         int     n, md;
787         struct buffer *bp;
788         char sl[21];            /* Overkill. Space for 2^64 in base 10. */
789         int len;
790
791         n = wp->w_toprow + wp->w_ntrows;        /* Location.             */
792         vscreen[n]->v_color = CMODE;            /* Mode line color.      */
793         vscreen[n]->v_flag |= (VFCHG | VFHBAD); /* Recompute, display.   */
794         vtmove(n, 0);                           /* Seek to right line.   */
795         bp = wp->w_bufp;
796         vtputc('-');
797         vtputc('-');
798         if ((bp->b_flag & BFREADONLY) != 0) {
799                 vtputc('%');
800                 if ((bp->b_flag & BFCHG) != 0)
801                         vtputc('*');
802                 else
803                         vtputc('%');
804         } else if ((bp->b_flag & BFCHG) != 0) { /* "*" if changed.       */
805                 vtputc('*');
806                 vtputc('*');
807         } else {
808                 vtputc('-');
809                 vtputc('-');
810         }
811         vtputc('-');
812         n = 5;
813         n += vtputs("Mg: ");
814         if (bp->b_bname[0] != '\0')
815                 n += vtputs(&(bp->b_bname[0]));
816         while (n < 42) {                        /* Pad out with blanks.  */
817                 vtputc(' ');
818                 ++n;
819         }
820         vtputc('(');
821         ++n;
822         for (md = 0; ; ) {
823                 n += vtputs(bp->b_modes[md]->p_name);
824                 if (++md > bp->b_nmodes)
825                         break;
826                 vtputc('-');
827                 ++n;
828         }
829         /* XXX These should eventually move to a real mode */
830         if (macrodef == TRUE)
831                 n += vtputs("-def");
832         if (globalwd == TRUE)
833                 n += vtputs("-gwd");
834         vtputc(')');
835         ++n;
836
837         if (linenos) {
838                 len = snprintf(sl, sizeof(sl), "--L%d--C%d", wp->w_dotline,
839                     getcolpos());
840                 if (len < sizeof(sl) && len != -1)
841                         n += vtputs(sl);
842         }
843
844         while (n < ncol) {                      /* Pad out.              */
845                 vtputc('-');
846                 ++n;
847         }
848 }
849
850 /*
851  * Output a string to the mode line, report how long it was.
852  */
853 int
854 vtputs(const char *s)
855 {
856         int n = 0;
857
858         while (*s != '\0') {
859                 vtputc(*s++);
860                 ++n;
861         }
862         return (n);
863 }
864
865 /*
866  * Compute the hash code for the line pointed to by the "vp".
867  * Recompute it if necessary. Also set the approximate redisplay
868  * cost. The validity of the hash code is marked by a flag bit.
869  * The cost understand the advantages of erase to end of line.
870  * Tuned for the VAX by Bob McNamara; better than it used to be on
871  * just about any machine.
872  */
873 void
874 hash(struct video *vp)
875 {
876         int     i, n;
877         char   *s;
878
879         if ((vp->v_flag & VFHBAD) != 0) {       /* Hash bad.             */
880                 s = &vp->v_text[ncol - 1];
881                 for (i = ncol; i != 0; --i, --s)
882                         if (*s != ' ')
883                                 break;
884                 n = ncol - i;                   /* Erase cheaper?        */
885                 if (n > tceeol)
886                         n = tceeol;
887                 vp->v_cost = i + n;             /* Bytes + blanks.       */
888                 for (n = 0; i != 0; --i, --s)
889                         n = (n << 5) + n + *s;
890                 vp->v_hash = n;                 /* Hash code.            */
891                 vp->v_flag &= ~VFHBAD;          /* Flag as all done.     */
892         }
893 }
894
895 /*
896  * Compute the Insert-Delete
897  * cost matrix. The dynamic programming algorithm
898  * described by James Gosling is used. This code assumes
899  * that the line above the echo line is the last line involved
900  * in the scroll region. This is easy to arrange on the VT100
901  * because of the scrolling region. The "offs" is the origin 0
902  * offset of the first row in the virtual/physical screen that
903  * is being updated; the "size" is the length of the chunk of
904  * screen being updated. For a full screen update, use offs=0
905  * and size=nrow-1.
906  *
907  * Older versions of this code implemented the score matrix by
908  * a two dimensional array of SCORE nodes. This put all kinds of
909  * multiply instructions in the code! This version is written to
910  * use a linear array and pointers, and contains no multiplication
911  * at all. The code has been carefully looked at on the VAX, with
912  * only marginal checking on other machines for efficiency. In
913  * fact, this has been tuned twice! Bob McNamara tuned it even
914  * more for the VAX, which is a big issue for him because of
915  * the 66 line X displays.
916  *
917  * On some machines, replacing the "for (i=1; i<=size; ++i)" with
918  * i = 1; do { } while (++i <=size)" will make the code quite a
919  * bit better; but it looks ugly.
920  */
921 void
922 setscores(int offs, int size)
923 {
924         struct score     *sp;
925         struct score     *sp1;
926         struct video    **vp, **pp;
927         struct video    **vbase, **pbase;
928         int       tempcost;
929         int       bestcost;
930         int       j, i;
931
932         vbase = &vscreen[offs - 1];     /* By hand CSE's.        */
933         pbase = &pscreen[offs - 1];
934         score[0].s_itrace = 0;          /* [0, 0]                */
935         score[0].s_jtrace = 0;
936         score[0].s_cost = 0;
937         sp = &score[1];                 /* Row 0, inserts.       */
938         tempcost = 0;
939         vp = &vbase[1];
940         for (j = 1; j <= size; ++j) {
941                 sp->s_itrace = 0;
942                 sp->s_jtrace = j - 1;
943                 tempcost += tcinsl;
944                 tempcost += (*vp)->v_cost;
945                 sp->s_cost = tempcost;
946                 ++vp;
947                 ++sp;
948         }
949         sp = &score[nrow];              /* Column 0, deletes.    */
950         tempcost = 0;
951         for (i = 1; i <= size; ++i) {
952                 sp->s_itrace = i - 1;
953                 sp->s_jtrace = 0;
954                 tempcost += tcdell;
955                 sp->s_cost = tempcost;
956                 sp += nrow;
957         }
958         sp1 = &score[nrow + 1];         /* [1, 1].               */
959         pp = &pbase[1];
960         for (i = 1; i <= size; ++i) {
961                 sp = sp1;
962                 vp = &vbase[1];
963                 for (j = 1; j <= size; ++j) {
964                         sp->s_itrace = i - 1;
965                         sp->s_jtrace = j;
966                         bestcost = (sp - nrow)->s_cost;
967                         if (j != size)  /* Cd(A[i])=0 @ Dis.     */
968                                 bestcost += tcdell;
969                         tempcost = (sp - 1)->s_cost;
970                         tempcost += (*vp)->v_cost;
971                         if (i != size)  /* Ci(B[j])=0 @ Dsj.     */
972                                 tempcost += tcinsl;
973                         if (tempcost < bestcost) {
974                                 sp->s_itrace = i;
975                                 sp->s_jtrace = j - 1;
976                                 bestcost = tempcost;
977                         }
978                         tempcost = (sp - nrow - 1)->s_cost;
979                         if ((*pp)->v_color != (*vp)->v_color
980                             || (*pp)->v_hash != (*vp)->v_hash)
981                                 tempcost += (*vp)->v_cost;
982                         if (tempcost < bestcost) {
983                                 sp->s_itrace = i - 1;
984                                 sp->s_jtrace = j - 1;
985                                 bestcost = tempcost;
986                         }
987                         sp->s_cost = bestcost;
988                         ++sp;           /* Next column.          */
989                         ++vp;
990                 }
991                 ++pp;
992                 sp1 += nrow;            /* Next row.             */
993         }
994 }
995
996 /*
997  * Trace back through the dynamic programming cost
998  * matrix, and update the screen using an optimal sequence
999  * of redraws, insert lines, and delete lines. The "offs" is
1000  * the origin 0 offset of the chunk of the screen we are about to
1001  * update. The "i" and "j" are always started in the lower right
1002  * corner of the matrix, and imply the size of the screen.
1003  * A full screen traceback is called with offs=0 and i=j=nrow-1.
1004  * There is some do-it-yourself double subscripting here,
1005  * which is acceptable because this routine is much less compute
1006  * intensive then the code that builds the score matrix!
1007  */
1008 void
1009 traceback(int offs, int size, int i, int j)
1010 {
1011         int     itrace, jtrace;
1012         int     k;
1013         int     ninsl, ndraw, ndell;
1014
1015         if (i == 0 && j == 0)   /* End of update.        */
1016                 return;
1017         itrace = score[(nrow * i) + j].s_itrace;
1018         jtrace = score[(nrow * i) + j].s_jtrace;
1019         if (itrace == i) {      /* [i, j-1]              */
1020                 ninsl = 0;      /* Collect inserts.      */
1021                 if (i != size)
1022                         ninsl = 1;
1023                 ndraw = 1;
1024                 while (itrace != 0 || jtrace != 0) {
1025                         if (score[(nrow * itrace) + jtrace].s_itrace != itrace)
1026                                 break;
1027                         jtrace = score[(nrow * itrace) + jtrace].s_jtrace;
1028                         if (i != size)
1029                                 ++ninsl;
1030                         ++ndraw;
1031                 }
1032                 traceback(offs, size, itrace, jtrace);
1033                 if (ninsl != 0) {
1034                         ttcolor(CTEXT);
1035                         ttinsl(offs + j - ninsl, offs + size - 1, ninsl);
1036                 }
1037                 do {            /* B[j], A[j] blank.     */
1038                         k = offs + j - ndraw;
1039                         uline(k, vscreen[k], &blanks);
1040                 } while (--ndraw);
1041                 return;
1042         }
1043         if (jtrace == j) {      /* [i-1, j]              */
1044                 ndell = 0;      /* Collect deletes.      */
1045                 if (j != size)
1046                         ndell = 1;
1047                 while (itrace != 0 || jtrace != 0) {
1048                         if (score[(nrow * itrace) + jtrace].s_jtrace != jtrace)
1049                                 break;
1050                         itrace = score[(nrow * itrace) + jtrace].s_itrace;
1051                         if (j != size)
1052                                 ++ndell;
1053                 }
1054                 if (ndell != 0) {
1055                         ttcolor(CTEXT);
1056                         ttdell(offs + i - ndell, offs + size - 1, ndell);
1057                 }
1058                 traceback(offs, size, itrace, jtrace);
1059                 return;
1060         }
1061         traceback(offs, size, itrace, jtrace);
1062         k = offs + j - 1;
1063         uline(k, vscreen[k], pscreen[offs + i - 1]);
1064 }