2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
9 static char sccsid
[] = "$Id: vs_refresh.c,v 8.42 1993/12/22 15:01:14 bostic Exp $ (Berkeley) $Date: 1993/12/22 15:01:14 $";
12 #include <sys/types.h>
20 #include "svi_screen.h"
21 #include "sex/sex_screen.h"
23 static int svi_modeline
__P((SCR
*, EXF
*));
24 static int svi_msgflush
__P((SCR
*));
35 * 1: Resize the screen.
37 * Notice that a resize is requested, and set up everything so that
38 * the file gets reinitialized. Done here, instead of in the vi loop
39 * because there may be other initialization that other screens need
40 * to do. The actual changing of the row/column values was done by
41 * calling the ex options code which put them into the environment,
42 * which is used by curses. Stupid, but ugly.
44 if (F_ISSET(sp
, S_RESIZE
)) {
45 /* Reinitialize curses. */
46 if (svi_curses_end(sp
) || svi_curses_init(sp
))
49 /* Lose any svi_screens() cached information. */
50 SVP(sp
)->ss_lno
= OOBLNO
;
53 * Fill the map, incidentally losing any svi_line()
56 if (sp
->s_fill(sp
, ep
, sp
->lno
, P_FILL
))
58 F_CLR(sp
, S_RESIZE
| S_REFORMAT
);
65 * If S_REFRESH is set in the current screen, repaint everything
68 if (F_ISSET(sp
, S_REFRESH
))
69 for (tsp
= sp
->gp
->dq
.cqh_first
;
70 tsp
!= (void *)&sp
->gp
->dq
; tsp
= tsp
->q
.cqe_next
)
74 * 3: Related or dirtied screens, or screens with messages.
76 * If related screens share a view into a file, they may have been
77 * modified as well. Refresh any screens with paint or dirty bits
78 * set, or where messages are waiting.
80 paintbits
= S_REDRAW
| S_REFORMAT
| S_REFRESH
;
81 if (O_ISSET(sp
, O_NUMBER
))
82 paintbits
|= S_RENUMBER
;
83 for (tsp
= sp
->gp
->dq
.cqh_first
;
84 tsp
!= (void *)&sp
->gp
->dq
; tsp
= tsp
->q
.cqe_next
)
86 (F_ISSET(tsp
, paintbits
) ||
87 F_ISSET(SVP(tsp
), SVI_SCREENDIRTY
) ||
88 tsp
->msgq
.lh_first
!= NULL
&&
89 !F_ISSET(tsp
->msgq
.lh_first
, M_EMPTY
))) {
90 (void)svi_paint(tsp
, tsp
->ep
);
91 F_CLR(SVP(tsp
), SVI_SCREENDIRTY
);
95 * 4: Refresh the current screen.
97 * Always refresh the current screen, it may be a cursor movement.
98 * Also, always do it last -- that way, S_REFRESH can be set in
99 * the current screen only, and the screen won't flash.
101 F_CLR(sp
, SVI_SCREENDIRTY
);
102 return (svi_paint(sp
, ep
));
107 * This is the guts of the vi curses screen code. The idea is that
108 * the SCR structure passed in contains the new coordinates of the
109 * screen. What makes this hard is that we don't know how big
110 * characters are, doing input can put the cursor in illegal places,
111 * and we're frantically trying to avoid repainting unless it's
112 * absolutely necessary. If you change this code, you'd better know
113 * what you're doing. It's subtle and quick to anger.
123 recno_t lastline
, lcnt
;
124 size_t cwtotal
, cnt
, len
, x
, y
;
129 #define OLNO svp->olno
131 #define OCNO svp->ocno
132 #define SCNO svp->sc_col
138 * 1: Reformat the lines.
140 * If the lines themselves have changed (:set list, for example),
141 * fill in the map from scratch. Adjust the screen that's being
142 * displayed if the leftright flag is set.
144 if (F_ISSET(sp
, S_REFORMAT
)) {
145 /* Toss svi_screens() cached information. */
146 SVP(sp
)->ss_lno
= OOBLNO
;
148 /* Toss svi_line() cached information. */
149 if (svi_sm_fill(sp
, ep
, HMAP
->lno
, P_TOP
))
151 if (O_ISSET(sp
, O_LEFTRIGHT
) &&
152 (cnt
= svi_screens(sp
, ep
, LNO
, &CNO
)) != 1)
153 for (smp
= HMAP
; smp
<= TMAP
; ++smp
)
155 F_CLR(sp
, S_REFORMAT
);
162 * Line changes can cause the top line to change as well. As
163 * before, if the movement is large, the screen is repainted.
167 * Tiny screens cannot be permitted into the "scrolling" parts of
168 * the smap code for two reasons. If the screen size is 1 line,
169 * HMAP == TMAP and the code will quickly drop core. If the screen
170 * size is 2, none of the divisions by 2 will work, and scrolling
171 * won't work. In fact, because no line change will be less than
172 * HALFTEXT(sp), we always ending up "filling" the map, with a
173 * P_MIDDLE flag, which isn't what the user wanted. Tiny screens
174 * can go into the "fill" portions of the smap code, however.
176 if (sp
->t_rows
<= 2) {
177 if (LNO
< HMAP
->lno
) {
178 if (svi_sm_fill(sp
, ep
, LNO
, P_TOP
))
180 } else if (LNO
> TMAP
->lno
)
181 if (svi_sm_fill(sp
, ep
, LNO
, P_BOTTOM
))
183 if (sp
->t_rows
== 1) {
184 HMAP
->off
= svi_screens(sp
, ep
, LNO
, &CNO
);
194 * Users can use the window, w300, w1200 and w9600 options to make
195 * the screen artificially small. The behavior of these options
196 * in the historic vi wasn't all that consistent, and, in fact, it
197 * was never documented how various screen movements affected the
198 * screen size. Generally, one of three things would happen:
199 * 1: The screen would expand in size, showing the line
200 * 2: The screen would scroll, showing the line
201 * 3: The screen would compress to its smallest size and
203 * In general, scrolling didn't cause compression (200^D was handled
204 * the same as ^D), movement to a specific line would (:N where N
205 * was 1 line below the screen caused a screen compress), and cursor
206 * movement would scroll if it was 11 lines or less, and compress if
207 * it was more than 11 lines. (And, no, I have no idea where the 11
210 * What we do is try and figure out if the line is less than half of
211 * a full screen away. If it is, we expand the screen if there's
212 * room, and then scroll as necessary. The alternative is to compress
216 * This code is a special case from beginning to end. Unfortunately,
217 * home modems are still slow enough that it's worth having.
220 * If the line a really long one, i.e. part of the line is on the
221 * screen but the column offset is not, we'll end up in the adjust
222 * code, when we should probably have compressed the screen.
224 if (ISSMALLSCREEN(sp
))
225 if (LNO
< HMAP
->lno
) {
226 lcnt
= svi_sm_nlines(sp
, ep
, HMAP
, LNO
, sp
->t_maxrows
);
227 if (lcnt
<= HALFSCREEN(sp
))
228 for (; lcnt
&& sp
->t_rows
!= sp
->t_maxrows
;
229 --lcnt
, ++sp
->t_rows
) {
231 if (svi_sm_1down(sp
, ep
))
236 } else if (LNO
> TMAP
->lno
) {
237 lcnt
= svi_sm_nlines(sp
, ep
, TMAP
, LNO
, sp
->t_maxrows
);
238 if (lcnt
<= HALFSCREEN(sp
))
239 for (; lcnt
&& sp
->t_rows
!= sp
->t_maxrows
;
240 --lcnt
, ++sp
->t_rows
) {
241 if (svi_sm_next(sp
, ep
, TMAP
, TMAP
+ 1))
244 if (svi_line(sp
, ep
, TMAP
, NULL
, NULL
))
248 small_fill
: MOVE(sp
, INFOLINE(sp
), 0);
250 for (; sp
->t_rows
> sp
->t_minrows
;
251 --sp
->t_rows
, --TMAP
) {
252 MOVE(sp
, TMAP
- HMAP
, 0);
255 if (svi_sm_fill(sp
, ep
, LNO
, P_FILL
))
265 if (LNO
>= HMAP
->lno
) {
266 if (LNO
<= TMAP
->lno
)
270 * If less than half a screen away, scroll down until the
271 * line is on the screen.
273 lcnt
= svi_sm_nlines(sp
, ep
, TMAP
, LNO
, HALFTEXT(sp
));
274 if (lcnt
< HALFTEXT(sp
)) {
276 if (svi_sm_1up(sp
, ep
))
282 * If less than a full screen from the bottom of the file, put
283 * the last line of the file on the bottom of the screen. The
284 * calculation is safe because we know there's at least one
285 * full screen of lines, otherwise couldn't have gotten here.
287 if (file_lline(sp
, ep
, &lastline
))
291 lcnt
= svi_sm_nlines(sp
, ep
, &tmp
, lastline
, sp
->t_rows
);
292 if (lcnt
< sp
->t_rows
) {
293 if (svi_sm_fill(sp
, ep
, lastline
, P_BOTTOM
))
300 * If more than a full screen from the last line of the file,
301 * put the new line in the middle of the screen.
309 * If less than half a screen away, scroll up until the line is
310 * the first line on the screen.
312 lcnt
= svi_sm_nlines(sp
, ep
, HMAP
, LNO
, HALFTEXT(sp
));
313 if (lcnt
< HALFTEXT(sp
)) {
315 if (svi_sm_1down(sp
, ep
))
321 * If less than half a screen from the top of the file, put the first
322 * line of the file at the top of the screen. Otherwise, put the line
323 * in the middle of the screen.
327 lcnt
= svi_sm_nlines(sp
, ep
, &tmp
, LNO
, HALFTEXT(sp
));
328 if (lcnt
< HALFTEXT(sp
)) {
329 if (svi_sm_fill(sp
, ep
, 1, P_TOP
))
332 middle
: if (svi_sm_fill(sp
, ep
, LNO
, P_MIDDLE
))
337 * At this point we know part of the line is on the screen. Since
338 * scrolling is done using logical lines, not physical, all of the
339 * line may not be on the screen. While that's not necessarily bad,
340 * if the part the cursor is on isn't there, we're going to lose.
341 * This can be tricky; if the line covers the entire screen, lno
342 * may be the same as both ends of the map, that's why we test BOTH
343 * the top and the bottom of the map. This isn't a problem for
344 * left-right scrolling, the cursor movement code handles the problem.
346 * There's a performance issue here if editing *really* long lines.
347 * This gets to the right spot by scrolling, and, in a binary, by
348 * scrolling hundreds of lines. If the adjustment looks like it's
349 * going to be a serious problem, refill the screen and repaint.
351 adjust
: if (!O_ISSET(sp
, O_LEFTRIGHT
) &&
352 (LNO
== HMAP
->lno
|| LNO
== TMAP
->lno
)) {
353 cnt
= svi_screens(sp
, ep
, LNO
, &CNO
);
354 if (LNO
== HMAP
->lno
&& cnt
< HMAP
->off
)
355 if ((HMAP
->off
- cnt
) > HALFTEXT(sp
)) {
357 svi_sm_fill(sp
, ep
, OOBLNO
, P_TOP
);
360 while (cnt
< HMAP
->off
)
361 if (svi_sm_1down(sp
, ep
))
363 if (LNO
== TMAP
->lno
&& cnt
> TMAP
->off
)
364 if ((cnt
- TMAP
->off
) > HALFTEXT(sp
)) {
366 svi_sm_fill(sp
, ep
, OOBLNO
, P_BOTTOM
);
369 while (cnt
> TMAP
->off
)
370 if (svi_sm_1up(sp
, ep
))
374 /* If the screen needs to be repainted, skip cursor optimization. */
375 if (F_ISSET(sp
, S_REDRAW
))
379 * 4: Cursor movements.
381 * Decide cursor position. If the line has changed, the cursor has
382 * moved over a tab, or don't know where the cursor was, reparse the
383 * line. Note, if we think that the cursor "hasn't moved", reparse
384 * the line. This is 'cause if it hasn't moved, we've almost always
387 * Otherwise, we've just moved over fixed-width characters, and can
388 * calculate the left/right scrolling and cursor movement without
389 * reparsing the line. Note that we don't know which (if any) of
390 * the characters between the old and new cursor positions changed.
393 * With some work, it should be possible to handle tabs quickly, at
394 * least in obvious situations, like moving right and encountering
395 * a tab, without reparsing the whole line.
398 /* If the line we're working with has changed, reparse. */
399 if (F_ISSET(SVP(sp
), SVI_CUR_INVALID
) || LNO
!= OLNO
) {
400 F_CLR(SVP(sp
), SVI_CUR_INVALID
);
404 /* Otherwise, if nothing's changed, go fast. */
409 * Get the current line. If this fails, we either have an empty
410 * file and can just repaint, or there's a real problem. This
411 * isn't a performance issue because there aren't any ways to get
414 if ((p
= file_gline(sp
, ep
, LNO
, &len
)) == NULL
) {
415 if (file_lline(sp
, ep
, &lastline
))
419 GETLINE_ERR(sp
, LNO
);
424 /* This is just a test. */
425 if (CNO
>= len
&& len
!= 0) {
426 msgq(sp
, M_ERR
, "Error: %s/%d: cno (%u) >= len (%u)",
427 tail(__FILE__
), __LINE__
, CNO
, len
);
432 * The basic scheme here is to look at the characters in between
433 * the old and new positions and decide how big they are on the
434 * screen, and therefore, how many screen positions to move.
436 cname
= sp
->gp
->cname
;
439 * 4a: Cursor moved left.
441 * Point to the old character. The old cursor position can
442 * be past EOL if, for example, we just deleted the rest of
443 * the line. In this case, since we don't know the width of
444 * the characters we traversed, we have to do it slowly.
447 cnt
= (OCNO
- CNO
) + 1;
452 * Quit sanity check -- it's hard to figure out exactly when
453 * we cross a screen boundary as we do in the cursor right
454 * movement. If cnt is so large that we're going to cross the
455 * boundary no matter what, stop now.
457 if (SCNO
+ 1 + MAX_CHARACTER_COLUMNS
< cnt
)
461 * Count up the widths of the characters. If it's a tab
462 * character, go do it the the slow way.
464 for (cwtotal
= 0; cnt
--; cwtotal
+= cname
[ch
].len
)
465 if ((ch
= *(u_char
*)p
--) == '\t')
469 * Decrement the screen cursor by the total width of the
470 * characters minus 1.
475 * If we're moving left, and there's a wide character in the
476 * current position, go to the end of the character.
478 if (cname
[ch
].len
> 1)
479 cwtotal
-= cname
[ch
].len
- 1;
482 * If the new column moved us out of the current screen,
483 * calculate a new screen.
485 if (SCNO
< cwtotal
) {
486 lscreen
: if (O_ISSET(sp
, O_LEFTRIGHT
)) {
487 for (smp
= HMAP
; smp
<= TMAP
; ++smp
)
496 * 4b: Cursor moved right.
498 * Point to the first character to the right.
504 * Count up the widths of the characters. If it's a tab
505 * character, go do it the the slow way. If we cross a
506 * screen boundary, we can quit.
508 for (cwtotal
= SCNO
; cnt
--;) {
509 if ((ch
= *(u_char
*)p
++) == '\t')
511 if ((cwtotal
+= cname
[ch
].len
) >= SCREEN_COLS(sp
))
516 * Increment the screen cursor by the total width of the
522 * If the new column moved us out of the current screen,
523 * calculate a new screen.
525 if (SCNO
>= SCREEN_COLS(sp
)) {
526 if (O_ISSET(sp
, O_LEFTRIGHT
)) {
527 SCNO
-= SCREEN_COLS(sp
);
528 for (smp
= HMAP
; smp
<= TMAP
; ++smp
)
537 * 4c: Fast cursor update.
539 * Retrieve the current cursor position, and correct it
542 fast
: getyx(stdscr
, y
, x
);
547 * 4d: Slow cursor update.
549 * Walk through the map and find the current line. If doing left-right
550 * scrolling and the cursor movement has changed the screen displayed,
551 * scroll the screen left or right, unless we're updating the info line
552 * in which case we just scroll that one line. Then update the screen
553 * lines for this file line until we have a new screen cursor position.
555 slow
: for (smp
= HMAP
; smp
->lno
!= LNO
; ++smp
);
556 if (O_ISSET(sp
, O_LEFTRIGHT
)) {
557 cnt
= svi_screens(sp
, ep
, LNO
, &CNO
) % SCREEN_COLS(sp
);
558 if (cnt
!= HMAP
->off
) {
559 if (ISINFOLINE(sp
, smp
))
562 for (smp
= HMAP
; smp
<= TMAP
; ++smp
)
567 for (y
= -1; smp
<= TMAP
&& smp
->lno
== LNO
; ++smp
) {
568 if (svi_line(sp
, ep
, smp
, &y
, &SCNO
))
576 * 5: Repaint the entire screen.
578 * Lost big, do what you have to do. We flush the cache as S_REDRAW
579 * gets set when the screen isn't worth fixing, and it's simpler to
580 * repaint. So, don't trust anything that we think we know about it.
582 paint
: for (smp
= HMAP
; smp
<= TMAP
; ++smp
)
584 for (smp
= HMAP
; smp
<= TMAP
; ++smp
)
585 if (svi_line(sp
, ep
, smp
, &y
, &SCNO
))
588 * If it's a small screen and we're redrawing, clear the unused lines,
589 * ex may have overwritten them.
591 if (F_ISSET(sp
, S_REDRAW
)) {
592 if (ISSMALLSCREEN(sp
))
593 for (cnt
= sp
->t_rows
; cnt
<= sp
->t_maxrows
; ++cnt
) {
603 * 6: Repaint the line numbers.
605 * If O_NUMBER is set and the S_RENUMBER bit is set, and we didn't
606 * repaint the screen, repaint all of the line numbers, they've
609 number
: if (O_ISSET(sp
, O_NUMBER
) && F_ISSET(sp
, S_RENUMBER
) && !didpaint
) {
610 if (svi_number(sp
, ep
))
612 F_CLR(sp
, S_RENUMBER
);
616 * 7: Refresh the screen.
618 * If the screen was corrupted, refresh it.
620 if (F_ISSET(sp
, S_REFRESH
)) {
622 F_CLR(sp
, S_REFRESH
);
625 if (F_ISSET(sp
, S_BELLSCHED
))
628 * If the bottom line isn't in use by the colon command:
630 * Display any messages. Don't test S_UPDATE_MODE. The
631 * message printing routine set it to avoid anyone else
632 * destroying the message we're about to display.
634 * If the bottom line isn't in use by anyone, put out the
635 * standard status line.
637 if (!F_ISSET(SVP(sp
), SVI_INFOLINE
))
638 if (sp
->msgq
.lh_first
!= NULL
&&
639 !F_ISSET(sp
->msgq
.lh_first
, M_EMPTY
))
641 else if (!F_ISSET(sp
, S_UPDATE_MODE
))
642 svi_modeline(sp
, ep
);
644 /* Update saved information. */
648 /* Place the cursor. */
651 /* Flush it all out. */
659 * Flush any accumulated messages.
672 #define MCONTMSG " [More ...]"
674 /* Display the messages. */
675 cname
= sp
->gp
->cname
;
676 for (mp
= sp
->msgq
.lh_first
, p
= NULL
;
677 mp
!= NULL
&& !F_ISSET(mp
, M_EMPTY
); mp
= mp
->q
.le_next
) {
680 lcont
: /* Move to the message line and clear it. */
681 MOVE(sp
, INFOLINE(sp
), 0);
685 * Turn on standout mode if requested, or, if we've split
686 * the screen and need a divider.
688 if (F_ISSET(mp
, M_INV_VIDEO
) ||
689 sp
->q
.cqe_next
!= (void *)&sp
->gp
->dq
)
693 * Print up to the "more" message. Avoid the last character
694 * in the last line, some hardware doesn't like it.
696 if (svi_ncols(sp
, p
, mp
->len
, NULL
) < sp
->cols
- 1)
699 len
= (sp
->cols
- sizeof(MCONTMSG
)) - 1;
704 chlen
= cname
[ch
].len
;
709 ADDNSTR(cname
[ch
].name
, chlen
);
713 * If more, print continue message. If user key fails,
714 * keep showing the messages anyway.
716 if (mp
->len
|| (mp
->q
.le_next
!= NULL
&&
717 !F_ISSET(mp
->q
.le_next
, M_EMPTY
))) {
718 ADDNSTR(MCONTMSG
, sizeof(MCONTMSG
) - 1);
721 if (term_user_key(sp
, &ikey
) != INP_OK
)
723 if (ikey
.value
== K_CR
||
724 ikey
.value
== K_NL
|| ikey
.ch
== ' ')
730 /* Turn off standout mode. */
731 if (F_ISSET(mp
, M_INV_VIDEO
) ||
732 sp
->q
.cqe_next
!= (void *)&sp
->gp
->dq
)
745 #define MODESIZE (RULERSIZE + 15)
749 * Update the mode line.
756 char *s
, buf
[RULERSIZE
];
758 MOVE(sp
, INFOLINE(sp
), 0);
761 /* Display a dividing line if not the bottom screen. */
762 if (sp
->q
.cqe_next
!= (void *)&sp
->gp
->dq
)
765 /* Display the ruler. */
766 if (O_ISSET(sp
, O_RULER
) && sp
->cols
> RULERSIZE
+ 2) {
767 MOVE(sp
, INFOLINE(sp
), sp
->cols
/ 2 - RULERSIZE
/ 2);
770 sizeof(buf
), "%lu,%lu", sp
->lno
, sp
->cno
+ 1);
774 /* Show the modified bit. */
775 if (O_ISSET(sp
, O_SHOWDIRTY
) &&
776 F_ISSET(ep
, F_MODIFIED
) && sp
->cols
> MODESIZE
) {
777 MOVE(sp
, INFOLINE(sp
), sp
->cols
- 9);
782 * Show the mode. Leave the last character blank, in case it's a
783 * really dumb terminal with hardware scroll. Second, don't try
784 * to *paint* the last character, SunOS 4.1.1 and Ultrix 4.2 curses
785 * won't let you paint the last character in the screen.
787 if (O_ISSET(sp
, O_SHOWMODE
) && sp
->cols
> MODESIZE
) {
788 MOVE(sp
, INFOLINE(sp
), sp
->cols
- 8);
789 s
= F_ISSET(sp
, S_INPUT
) ? " Input" : "Command";
798 * Draw a dividing line between the screens.
806 #define DIVIDESTR "+=+=+=+=+=+=+=+"
807 len
= sizeof(DIVIDESTR
) - 1 > sp
->cols
?
808 sp
->cols
: sizeof(DIVIDESTR
) - 1;
809 ADDNSTR(DIVIDESTR
, len
);