vs_refresh.c revision 1.10 1 1.10 rin /* $NetBSD: vs_refresh.c,v 1.10 2018/04/10 12:44:41 rin Exp $ */
2 1.1 christos /*-
3 1.1 christos * Copyright (c) 1992, 1993, 1994
4 1.1 christos * The Regents of the University of California. All rights reserved.
5 1.1 christos * Copyright (c) 1992, 1993, 1994, 1995, 1996
6 1.1 christos * Keith Bostic. All rights reserved.
7 1.1 christos *
8 1.1 christos * See the LICENSE file for redistribution information.
9 1.1 christos */
10 1.1 christos
11 1.1 christos #include "config.h"
12 1.1 christos
13 1.6 christos #include <sys/cdefs.h>
14 1.6 christos #if 0
15 1.1 christos #ifndef lint
16 1.1 christos static const char sccsid[] = "Id: vs_refresh.c,v 10.50 2001/06/25 15:19:37 skimo Exp (Berkeley) Date: 2001/06/25 15:19:37 ";
17 1.1 christos #endif /* not lint */
18 1.6 christos #else
19 1.10 rin __RCSID("$NetBSD: vs_refresh.c,v 1.10 2018/04/10 12:44:41 rin Exp $");
20 1.6 christos #endif
21 1.1 christos
22 1.1 christos #include <sys/types.h>
23 1.1 christos #include <sys/queue.h>
24 1.1 christos #include <sys/time.h>
25 1.1 christos
26 1.1 christos #include <bitstring.h>
27 1.1 christos #include <ctype.h>
28 1.1 christos #include <limits.h>
29 1.1 christos #include <stdio.h>
30 1.1 christos #include <stdlib.h>
31 1.1 christos #include <string.h>
32 1.1 christos
33 1.1 christos #include "../common/common.h"
34 1.1 christos #include "vi.h"
35 1.1 christos
36 1.1 christos #define UPDATE_CURSOR 0x01 /* Update the cursor. */
37 1.1 christos #define UPDATE_SCREEN 0x02 /* Flush to screen. */
38 1.1 christos
39 1.1 christos static void vs_modeline __P((SCR *));
40 1.1 christos static int vs_paint __P((SCR *, u_int));
41 1.1 christos
42 1.1 christos /*
43 1.1 christos * vs_refresh --
44 1.1 christos * Refresh all screens.
45 1.1 christos *
46 1.1 christos * PUBLIC: int vs_refresh __P((SCR *, int));
47 1.1 christos */
48 1.1 christos int
49 1.1 christos vs_refresh(SCR *sp, int forcepaint)
50 1.1 christos {
51 1.1 christos GS *gp;
52 1.1 christos SCR *tsp;
53 1.1 christos int need_refresh;
54 1.1 christos u_int priv_paint, pub_paint;
55 1.1 christos
56 1.1 christos gp = sp->gp;
57 1.1 christos
58 1.1 christos /*
59 1.1 christos * 1: Refresh the screen.
60 1.1 christos *
61 1.1 christos * If SC_SCR_REDRAW is set in the current screen, repaint everything
62 1.1 christos * that we can find, including status lines.
63 1.1 christos */
64 1.1 christos if (F_ISSET(sp, SC_SCR_REDRAW))
65 1.3 christos TAILQ_FOREACH(tsp, &sp->wp->scrq, q)
66 1.1 christos if (tsp != sp)
67 1.1 christos F_SET(tsp, SC_SCR_REDRAW | SC_STATUS);
68 1.1 christos
69 1.1 christos /*
70 1.1 christos * 2: Related or dirtied screens, or screens with messages.
71 1.1 christos *
72 1.1 christos * If related screens share a view into a file, they may have been
73 1.1 christos * modified as well. Refresh any screens that aren't exiting that
74 1.1 christos * have paint or dirty bits set. Always update their screens, we
75 1.1 christos * are not likely to get another chance. Finally, if we refresh any
76 1.1 christos * screens other than the current one, the cursor will be trashed.
77 1.1 christos */
78 1.1 christos pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW;
79 1.1 christos priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH;
80 1.1 christos if (O_ISSET(sp, O_NUMBER))
81 1.1 christos priv_paint |= VIP_N_RENUMBER;
82 1.3 christos TAILQ_FOREACH(tsp, &sp->wp->scrq, q)
83 1.1 christos if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) &&
84 1.1 christos (F_ISSET(tsp, pub_paint) ||
85 1.1 christos F_ISSET(VIP(tsp), priv_paint))) {
86 1.1 christos (void)vs_paint(tsp,
87 1.1 christos (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ?
88 1.1 christos UPDATE_CURSOR : 0) | UPDATE_SCREEN);
89 1.5 christos F_SET(VIP(sp), VIP_CUR_INVALID);
90 1.1 christos }
91 1.1 christos
92 1.1 christos /*
93 1.1 christos * 3: Refresh the current screen.
94 1.1 christos *
95 1.1 christos * Always refresh the current screen, it may be a cursor movement.
96 1.1 christos * Also, always do it last -- that way, SC_SCR_REDRAW can be set
97 1.1 christos * in the current screen only, and the screen won't flash.
98 1.1 christos */
99 1.1 christos if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint &&
100 1.1 christos F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN)))
101 1.1 christos return (1);
102 1.1 christos
103 1.1 christos /*
104 1.1 christos * 4: Paint any missing status lines.
105 1.1 christos *
106 1.1 christos * XXX
107 1.1 christos * This is fairly evil. Status lines are written using the vi message
108 1.1 christos * mechanism, since we have no idea how long they are. Since we may be
109 1.1 christos * painting screens other than the current one, we don't want to make
110 1.1 christos * the user wait. We depend heavily on there not being any other lines
111 1.1 christos * currently waiting to be displayed and the message truncation code in
112 1.1 christos * the msgq_status routine working.
113 1.1 christos *
114 1.1 christos * And, finally, if we updated any status lines, make sure the cursor
115 1.1 christos * gets back to where it belongs.
116 1.1 christos */
117 1.3 christos need_refresh = 0;
118 1.3 christos TAILQ_FOREACH(tsp, &sp->wp->scrq, q)
119 1.1 christos if (F_ISSET(tsp, SC_STATUS)) {
120 1.1 christos need_refresh = 1;
121 1.1 christos vs_resolve(tsp, sp, 0);
122 1.1 christos }
123 1.1 christos if (need_refresh)
124 1.1 christos (void)gp->scr_refresh(sp, 0);
125 1.1 christos
126 1.1 christos /*
127 1.1 christos * A side-effect of refreshing the screen is that it's now ready
128 1.1 christos * for everything else, i.e. messages.
129 1.1 christos */
130 1.1 christos F_SET(sp, SC_SCR_VI);
131 1.1 christos return (0);
132 1.1 christos }
133 1.1 christos
134 1.1 christos /*
135 1.1 christos * vs_paint --
136 1.1 christos * This is the guts of the vi curses screen code. The idea is that
137 1.1 christos * the SCR structure passed in contains the new coordinates of the
138 1.1 christos * screen. What makes this hard is that we don't know how big
139 1.1 christos * characters are, doing input can put the cursor in illegal places,
140 1.1 christos * and we're frantically trying to avoid repainting unless it's
141 1.1 christos * absolutely necessary. If you change this code, you'd better know
142 1.1 christos * what you're doing. It's subtle and quick to anger.
143 1.1 christos */
144 1.1 christos static int
145 1.1 christos vs_paint(SCR *sp, u_int flags)
146 1.1 christos {
147 1.1 christos GS *gp;
148 1.1 christos SMAP *smp, tmp;
149 1.1 christos VI_PRIVATE *vip;
150 1.1 christos db_recno_t lastline, lcnt;
151 1.7 rin size_t cwtotal, cnt, len, notused, off, y, chlen;
152 1.2 christos int ch = 0, didpaint, isempty, leftright_warp;
153 1.1 christos CHAR_T *p;
154 1.1 christos
155 1.1 christos #define LNO sp->lno /* Current file line. */
156 1.1 christos #define OLNO vip->olno /* Remembered file line. */
157 1.1 christos #define CNO sp->cno /* Current file column. */
158 1.1 christos #define OCNO vip->ocno /* Remembered file column. */
159 1.1 christos #define SCNO vip->sc_col /* Current screen column. */
160 1.1 christos
161 1.1 christos gp = sp->gp;
162 1.1 christos vip = VIP(sp);
163 1.4 christos if (vip == NULL)
164 1.4 christos return 0;
165 1.1 christos didpaint = leftright_warp = 0;
166 1.1 christos
167 1.1 christos /*
168 1.1 christos * 5: Reformat the lines.
169 1.1 christos *
170 1.1 christos * If the lines themselves have changed (:set list, for example),
171 1.1 christos * fill in the map from scratch. Adjust the screen that's being
172 1.1 christos * displayed if the leftright flag is set.
173 1.1 christos */
174 1.1 christos if (F_ISSET(sp, SC_SCR_REFORMAT)) {
175 1.1 christos /* Invalidate the line size cache. */
176 1.1 christos VI_SCR_CFLUSH(vip);
177 1.1 christos
178 1.1 christos /* Toss vs_line() cached information. */
179 1.1 christos if (F_ISSET(sp, SC_SCR_TOP)) {
180 1.1 christos if (vs_sm_fill(sp, LNO, P_TOP))
181 1.1 christos return (1);
182 1.1 christos }
183 1.1 christos else if (F_ISSET(sp, SC_SCR_CENTER)) {
184 1.1 christos if (vs_sm_fill(sp, LNO, P_MIDDLE))
185 1.1 christos return (1);
186 1.1 christos } else
187 1.1 christos if (vs_sm_fill(sp, OOBLNO, P_TOP))
188 1.1 christos return (1);
189 1.1 christos F_SET(sp, SC_SCR_REDRAW);
190 1.1 christos }
191 1.1 christos
192 1.1 christos /*
193 1.1 christos * 6: Line movement.
194 1.1 christos *
195 1.1 christos * Line changes can cause the top line to change as well. As
196 1.1 christos * before, if the movement is large, the screen is repainted.
197 1.1 christos *
198 1.1 christos * 6a: Small screens.
199 1.1 christos *
200 1.1 christos * Users can use the window, w300, w1200 and w9600 options to make
201 1.1 christos * the screen artificially small. The behavior of these options
202 1.1 christos * in the historic vi wasn't all that consistent, and, in fact, it
203 1.1 christos * was never documented how various screen movements affected the
204 1.1 christos * screen size. Generally, one of three things would happen:
205 1.1 christos * 1: The screen would expand in size, showing the line
206 1.1 christos * 2: The screen would scroll, showing the line
207 1.1 christos * 3: The screen would compress to its smallest size and
208 1.1 christos * repaint.
209 1.1 christos * In general, scrolling didn't cause compression (200^D was handled
210 1.1 christos * the same as ^D), movement to a specific line would (:N where N
211 1.1 christos * was 1 line below the screen caused a screen compress), and cursor
212 1.1 christos * movement would scroll if it was 11 lines or less, and compress if
213 1.1 christos * it was more than 11 lines. (And, no, I have no idea where the 11
214 1.1 christos * comes from.)
215 1.1 christos *
216 1.1 christos * What we do is try and figure out if the line is less than half of
217 1.1 christos * a full screen away. If it is, we expand the screen if there's
218 1.1 christos * room, and then scroll as necessary. The alternative is to compress
219 1.1 christos * and repaint.
220 1.1 christos *
221 1.1 christos * !!!
222 1.1 christos * This code is a special case from beginning to end. Unfortunately,
223 1.1 christos * home modems are still slow enough that it's worth having.
224 1.1 christos *
225 1.1 christos * XXX
226 1.1 christos * If the line a really long one, i.e. part of the line is on the
227 1.1 christos * screen but the column offset is not, we'll end up in the adjust
228 1.1 christos * code, when we should probably have compressed the screen.
229 1.1 christos */
230 1.2 christos if (IS_SMALL(sp)) {
231 1.1 christos if (LNO < HMAP->lno) {
232 1.1 christos lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows);
233 1.1 christos if (lcnt <= HALFSCREEN(sp))
234 1.1 christos for (; lcnt && sp->t_rows != sp->t_maxrows;
235 1.1 christos --lcnt, ++sp->t_rows) {
236 1.1 christos ++TMAP;
237 1.1 christos if (vs_sm_1down(sp))
238 1.1 christos return (1);
239 1.1 christos }
240 1.1 christos else
241 1.1 christos goto small_fill;
242 1.1 christos } else if (LNO > TMAP->lno) {
243 1.1 christos lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows);
244 1.1 christos if (lcnt <= HALFSCREEN(sp))
245 1.1 christos for (; lcnt && sp->t_rows != sp->t_maxrows;
246 1.1 christos --lcnt, ++sp->t_rows) {
247 1.1 christos if (vs_sm_next(sp, TMAP, TMAP + 1))
248 1.1 christos return (1);
249 1.1 christos ++TMAP;
250 1.1 christos if (vs_line(sp, TMAP, NULL, NULL))
251 1.1 christos return (1);
252 1.1 christos }
253 1.1 christos else {
254 1.1 christos small_fill: (void)gp->scr_move(sp, LASTLINE(sp), 0);
255 1.1 christos (void)gp->scr_clrtoeol(sp);
256 1.1 christos for (; sp->t_rows > sp->t_minrows;
257 1.1 christos --sp->t_rows, --TMAP) {
258 1.1 christos (void)gp->scr_move(sp, TMAP - HMAP, 0);
259 1.1 christos (void)gp->scr_clrtoeol(sp);
260 1.1 christos }
261 1.1 christos if (vs_sm_fill(sp, LNO, P_FILL))
262 1.1 christos return (1);
263 1.1 christos F_SET(sp, SC_SCR_REDRAW);
264 1.1 christos goto adjust;
265 1.1 christos }
266 1.1 christos }
267 1.2 christos }
268 1.1 christos
269 1.1 christos /*
270 1.1 christos * 6b: Line down, or current screen.
271 1.1 christos */
272 1.1 christos if (LNO >= HMAP->lno) {
273 1.1 christos /* Current screen. */
274 1.1 christos if (LNO <= TMAP->lno)
275 1.1 christos goto adjust;
276 1.1 christos if (F_ISSET(sp, SC_SCR_TOP))
277 1.1 christos goto top;
278 1.1 christos if (F_ISSET(sp, SC_SCR_CENTER))
279 1.1 christos goto middle;
280 1.1 christos
281 1.1 christos /*
282 1.1 christos * If less than half a screen above the line, scroll down
283 1.1 christos * until the line is on the screen.
284 1.1 christos */
285 1.1 christos lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp));
286 1.1 christos if (lcnt < HALFTEXT(sp)) {
287 1.1 christos while (lcnt--)
288 1.1 christos if (vs_sm_1up(sp))
289 1.1 christos return (1);
290 1.1 christos goto adjust;
291 1.1 christos }
292 1.1 christos goto bottom;
293 1.1 christos }
294 1.1 christos
295 1.1 christos /*
296 1.1 christos * 6c: If not on the current screen, may request center or top.
297 1.1 christos */
298 1.1 christos if (F_ISSET(sp, SC_SCR_TOP))
299 1.1 christos goto top;
300 1.1 christos if (F_ISSET(sp, SC_SCR_CENTER))
301 1.1 christos goto middle;
302 1.1 christos
303 1.1 christos /*
304 1.1 christos * 6d: Line up.
305 1.1 christos */
306 1.1 christos lcnt = vs_sm_nlines(sp, HMAP, LNO, HALFTEXT(sp));
307 1.1 christos if (lcnt < HALFTEXT(sp)) {
308 1.1 christos /*
309 1.1 christos * If less than half a screen below the line, scroll up until
310 1.1 christos * the line is the first line on the screen. Special check so
311 1.1 christos * that if the screen has been emptied, we refill it.
312 1.1 christos */
313 1.1 christos if (db_exist(sp, HMAP->lno)) {
314 1.1 christos while (lcnt--)
315 1.1 christos if (vs_sm_1down(sp))
316 1.1 christos return (1);
317 1.1 christos goto adjust;
318 1.1 christos }
319 1.1 christos
320 1.1 christos /*
321 1.1 christos * If less than a half screen from the bottom of the file,
322 1.1 christos * put the last line of the file on the bottom of the screen.
323 1.1 christos */
324 1.1 christos bottom: if (db_last(sp, &lastline))
325 1.1 christos return (1);
326 1.1 christos tmp.lno = LNO;
327 1.1 christos tmp.coff = HMAP->coff;
328 1.1 christos tmp.soff = 1;
329 1.1 christos lcnt = vs_sm_nlines(sp, &tmp, lastline+1, sp->t_rows);
330 1.1 christos if (lcnt < HALFTEXT(sp)) {
331 1.1 christos if (vs_sm_fill(sp, lastline, P_BOTTOM))
332 1.1 christos return (1);
333 1.1 christos F_SET(sp, SC_SCR_REDRAW);
334 1.1 christos goto adjust;
335 1.1 christos }
336 1.1 christos /* It's not close, just put the line in the middle. */
337 1.1 christos goto middle;
338 1.1 christos }
339 1.1 christos
340 1.1 christos /*
341 1.1 christos * If less than half a screen from the top of the file, put the first
342 1.1 christos * line of the file at the top of the screen. Otherwise, put the line
343 1.1 christos * in the middle of the screen.
344 1.1 christos */
345 1.1 christos tmp.lno = 1;
346 1.1 christos tmp.coff = HMAP->coff;
347 1.1 christos tmp.soff = 1;
348 1.1 christos lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp));
349 1.1 christos if (lcnt < HALFTEXT(sp)) {
350 1.1 christos if (vs_sm_fill(sp, 1, P_TOP))
351 1.1 christos return (1);
352 1.1 christos } else
353 1.1 christos middle: if (vs_sm_fill(sp, LNO, P_MIDDLE))
354 1.1 christos return (1);
355 1.1 christos if (0) {
356 1.1 christos top: if (vs_sm_fill(sp, LNO, P_TOP))
357 1.1 christos return (1);
358 1.1 christos }
359 1.1 christos F_SET(sp, SC_SCR_REDRAW);
360 1.1 christos
361 1.1 christos /*
362 1.1 christos * At this point we know part of the line is on the screen. Since
363 1.1 christos * scrolling is done using logical lines, not physical, all of the
364 1.1 christos * line may not be on the screen. While that's not necessarily bad,
365 1.1 christos * if the part the cursor is on isn't there, we're going to lose.
366 1.1 christos * This can be tricky; if the line covers the entire screen, lno
367 1.1 christos * may be the same as both ends of the map, that's why we test BOTH
368 1.1 christos * the top and the bottom of the map. This isn't a problem for
369 1.1 christos * left-right scrolling, the cursor movement code handles the problem.
370 1.1 christos *
371 1.1 christos * There's a performance issue here if editing *really* long lines.
372 1.1 christos * This gets to the right spot by scrolling, and, in a binary, by
373 1.1 christos * scrolling hundreds of lines. If the adjustment looks like it's
374 1.1 christos * going to be a serious problem, refill the screen and repaint.
375 1.1 christos */
376 1.1 christos adjust: if (!O_ISSET(sp, O_LEFTRIGHT) &&
377 1.1 christos (LNO == HMAP->lno || LNO == TMAP->lno)) {
378 1.1 christos cnt = vs_screens(sp, LNO, &CNO);
379 1.2 christos if (LNO == HMAP->lno && cnt < HMAP->soff) {
380 1.1 christos if ((HMAP->soff - cnt) > HALFTEXT(sp)) {
381 1.1 christos HMAP->soff = cnt;
382 1.1 christos vs_sm_fill(sp, OOBLNO, P_TOP);
383 1.1 christos F_SET(sp, SC_SCR_REDRAW);
384 1.1 christos } else
385 1.1 christos while (cnt < HMAP->soff)
386 1.1 christos if (vs_sm_1down(sp))
387 1.1 christos return (1);
388 1.2 christos }
389 1.2 christos if (LNO == TMAP->lno && cnt > TMAP->soff) {
390 1.1 christos if ((cnt - TMAP->soff) > HALFTEXT(sp)) {
391 1.1 christos TMAP->soff = cnt;
392 1.1 christos vs_sm_fill(sp, OOBLNO, P_BOTTOM);
393 1.1 christos F_SET(sp, SC_SCR_REDRAW);
394 1.1 christos } else
395 1.1 christos while (cnt > TMAP->soff)
396 1.1 christos if (vs_sm_1up(sp))
397 1.1 christos return (1);
398 1.2 christos }
399 1.1 christos }
400 1.1 christos
401 1.1 christos /*
402 1.1 christos * If the screen needs to be repainted, skip cursor optimization.
403 1.1 christos * However, in the code above we skipped leftright scrolling on
404 1.1 christos * the grounds that the cursor code would handle it. Make sure
405 1.1 christos * the right screen is up.
406 1.1 christos */
407 1.1 christos if (F_ISSET(sp, SC_SCR_REDRAW)) {
408 1.1 christos if (O_ISSET(sp, O_LEFTRIGHT))
409 1.1 christos goto slow;
410 1.1 christos goto paint;
411 1.1 christos }
412 1.1 christos
413 1.1 christos /*
414 1.1 christos * 7: Cursor movements (current screen only).
415 1.1 christos */
416 1.1 christos if (!LF_ISSET(UPDATE_CURSOR))
417 1.1 christos goto number;
418 1.1 christos
419 1.1 christos /*
420 1.1 christos * Decide cursor position. If the line has changed, the cursor has
421 1.1 christos * moved over a tab, or don't know where the cursor was, reparse the
422 1.1 christos * line. Otherwise, we've just moved over fixed-width characters,
423 1.1 christos * and can calculate the left/right scrolling and cursor movement
424 1.1 christos * without reparsing the line. Note that we don't know which (if any)
425 1.1 christos * of the characters between the old and new cursor positions changed.
426 1.1 christos *
427 1.1 christos * XXX
428 1.1 christos * With some work, it should be possible to handle tabs quickly, at
429 1.1 christos * least in obvious situations, like moving right and encountering
430 1.1 christos * a tab, without reparsing the whole line.
431 1.1 christos *
432 1.1 christos * If the line we're working with has changed, reread it..
433 1.1 christos */
434 1.1 christos if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO)
435 1.1 christos goto slow;
436 1.1 christos
437 1.1 christos /* Otherwise, if nothing's changed, ignore the cursor. */
438 1.1 christos if (CNO == OCNO)
439 1.1 christos goto fast;
440 1.1 christos
441 1.1 christos /*
442 1.1 christos * Get the current line. If this fails, we either have an empty
443 1.1 christos * file and can just repaint, or there's a real problem. This
444 1.1 christos * isn't a performance issue because there aren't any ways to get
445 1.1 christos * here repeatedly.
446 1.1 christos */
447 1.1 christos if (db_eget(sp, LNO, &p, &len, &isempty)) {
448 1.1 christos if (isempty)
449 1.1 christos goto slow;
450 1.1 christos return (1);
451 1.1 christos }
452 1.1 christos
453 1.1 christos #ifdef DEBUG
454 1.1 christos /* Sanity checking. */
455 1.1 christos if (CNO >= len && len != 0) {
456 1.1 christos msgq(sp, M_ERR, "Error: %s/%d: cno (%u) >= len (%u)",
457 1.1 christos tail(__FILE__), __LINE__, CNO, len);
458 1.1 christos return (1);
459 1.1 christos }
460 1.1 christos #endif
461 1.1 christos /*
462 1.1 christos * The basic scheme here is to look at the characters in between
463 1.1 christos * the old and new positions and decide how big they are on the
464 1.1 christos * screen, and therefore, how many screen positions to move.
465 1.1 christos */
466 1.1 christos if (CNO < OCNO) {
467 1.1 christos /*
468 1.1 christos * 7a: Cursor moved left.
469 1.1 christos *
470 1.7 rin * The old cursor position can be past EOL if, for example,
471 1.7 rin * we just deleted the rest of the line. In this case, since
472 1.7 rin * we don't know the width of the characters we traversed, we
473 1.7 rin * have to do it slowly.
474 1.1 christos */
475 1.1 christos if (OCNO >= len)
476 1.1 christos goto slow;
477 1.1 christos
478 1.1 christos /*
479 1.7 rin * cwtotal acts as new value for SCNO. Set cwtotal to the
480 1.7 rin * first char for content on CNO byte, for ease handling of
481 1.7 rin * wide characters.
482 1.7 rin *
483 1.7 rin * If the character we're stepping on lies across a screen
484 1.7 rin * boundary, we have no hope to speed it up. Do it slowly.
485 1.7 rin */
486 1.7 rin p += OCNO;
487 1.8 rin ch = (UCHAR_T)*p;
488 1.8 rin if (INTISWIDE(ch))
489 1.7 rin cwtotal = SCNO;
490 1.7 rin else {
491 1.7 rin if (ch == '\t' || (chlen = KEY_LEN(sp, ch)) > SCNO + 1)
492 1.7 rin goto slow;
493 1.7 rin cwtotal = SCNO + 1 - chlen;
494 1.7 rin }
495 1.7 rin cnt = OCNO - CNO;
496 1.7 rin
497 1.7 rin /*
498 1.1 christos * Quick sanity check -- it's hard to figure out exactly when
499 1.1 christos * we cross a screen boundary as we do in the cursor right
500 1.1 christos * movement. If cnt is so large that we're going to cross the
501 1.1 christos * boundary no matter what, stop now.
502 1.1 christos */
503 1.1 christos if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt)
504 1.1 christos goto slow;
505 1.1 christos
506 1.1 christos /*
507 1.1 christos * Count up the widths of the characters. If it's a tab
508 1.7 rin * character, go do it the slow way.
509 1.1 christos */
510 1.7 rin while (cnt--) {
511 1.7 rin if ((ch = (UCHAR_T)*--p) == '\t'
512 1.7 rin || (chlen = KEY_COL(sp, ch)) > cwtotal)
513 1.1 christos goto slow;
514 1.7 rin cwtotal -= chlen;
515 1.7 rin }
516 1.1 christos
517 1.1 christos /*
518 1.7 rin * If we're moving left, and there's a multi-width char in the
519 1.7 rin * current position, go to the end of the character.
520 1.1 christos */
521 1.7 rin if (!INTISWIDE(ch) && (chlen = KEY_LEN(sp, ch)) > 1)
522 1.7 rin cwtotal += chlen - 1;
523 1.1 christos
524 1.1 christos /*
525 1.7 rin * At last, update the screen cursor.
526 1.1 christos */
527 1.7 rin SCNO = cwtotal;
528 1.7 rin } else {
529 1.1 christos /*
530 1.7 rin * 7b: Cursor moved right.
531 1.1 christos */
532 1.7 rin if (OCNO >= len)
533 1.1 christos goto slow;
534 1.7 rin
535 1.1 christos /*
536 1.7 rin * cwtotal acts as new value for SCNO. Set cwtotal to the
537 1.7 rin * first char for content on CNO byte, for ease handling
538 1.7 rin * of wide characters.
539 1.1 christos */
540 1.7 rin p += OCNO;
541 1.8 rin ch = (UCHAR_T)*p;
542 1.8 rin if (INTISWIDE(ch))
543 1.7 rin cwtotal = SCNO;
544 1.7 rin else
545 1.7 rin cwtotal = SCNO + 1 - KEY_LEN(sp, ch);
546 1.1 christos cnt = CNO - OCNO;
547 1.1 christos
548 1.1 christos /*
549 1.1 christos * Count up the widths of the characters. If it's a tab
550 1.7 rin * character, go do it the the slow way.
551 1.7 rin *
552 1.7 rin * If a multi-width char seems to occupy the screen boundary,
553 1.7 rin * that will be pushed to the next line. Adjust the cursor
554 1.7 rin * in that case.
555 1.7 rin *
556 1.7 rin * If we cross a screen boundary, we can quit.
557 1.1 christos */
558 1.7 rin while (cnt) {
559 1.7 rin if (ch == '\t')
560 1.1 christos goto slow;
561 1.7 rin cwtotal += KEY_COL(sp, ch);
562 1.7 rin cnt--;
563 1.8 rin ch = (UCHAR_T)*++p;
564 1.8 rin if (INTISWIDE(ch)
565 1.7 rin && (chlen = CHAR_WIDTH(sp, ch)) > 1
566 1.7 rin && cwtotal + chlen >= SCREEN_COLS(sp))
567 1.7 rin cwtotal = SCREEN_COLS(sp);
568 1.7 rin if (cwtotal >= SCREEN_COLS(sp))
569 1.1 christos break;
570 1.1 christos }
571 1.1 christos
572 1.1 christos /*
573 1.7 rin * If we are on the tab character, we must do it slowly.
574 1.7 rin *
575 1.7 rin * If we're on a multi-width character in the current position,
576 1.7 rin * go to the end of the character.
577 1.1 christos */
578 1.7 rin if (ch == '\t')
579 1.7 rin goto slow;
580 1.7 rin if (!INTISWIDE(ch) && (chlen = KEY_LEN(sp, ch)) > 1)
581 1.7 rin cwtotal += chlen - 1;
582 1.1 christos
583 1.1 christos /* See screen change comment in section 6a. */
584 1.7 rin if (cwtotal >= SCREEN_COLS(sp))
585 1.1 christos goto slow;
586 1.7 rin
587 1.7 rin /*
588 1.7 rin * At last, update the screen cursor.
589 1.7 rin */
590 1.7 rin SCNO = cwtotal;
591 1.1 christos }
592 1.1 christos
593 1.1 christos /*
594 1.1 christos * 7c: Fast cursor update.
595 1.1 christos *
596 1.1 christos * We have the current column, retrieve the current row.
597 1.1 christos */
598 1.1 christos fast: (void)gp->scr_cursor(sp, &y, ¬used);
599 1.1 christos goto done_cursor;
600 1.1 christos
601 1.1 christos /*
602 1.1 christos * 7d: Slow cursor update.
603 1.1 christos *
604 1.1 christos * Walk through the map and find the current line.
605 1.1 christos */
606 1.9 mrg slow: for (smp = HMAP; smp->lno != LNO; ++smp)
607 1.9 mrg ;
608 1.1 christos
609 1.1 christos /*
610 1.1 christos * 7e: Leftright scrolling adjustment.
611 1.1 christos *
612 1.1 christos * If doing left-right scrolling and the cursor movement has changed
613 1.1 christos * the displayed screen, scroll the screen left or right, unless we're
614 1.1 christos * updating the info line in which case we just scroll that one line.
615 1.1 christos * We adjust the offset up or down until we have a window that covers
616 1.1 christos * the current column, making sure that we adjust differently for the
617 1.1 christos * first screen as compared to subsequent ones.
618 1.1 christos */
619 1.1 christos if (O_ISSET(sp, O_LEFTRIGHT)) {
620 1.1 christos /*
621 1.1 christos * Get the screen column for this character, and correct
622 1.1 christos * for the number option offset.
623 1.1 christos */
624 1.1 christos cnt = vs_columns(sp, NULL, LNO, &CNO, NULL);
625 1.1 christos if (O_ISSET(sp, O_NUMBER))
626 1.1 christos cnt -= O_NUMBER_LENGTH;
627 1.1 christos
628 1.1 christos /* Adjust the window towards the beginning of the line. */
629 1.1 christos off = smp->coff;
630 1.1 christos if (off >= cnt) {
631 1.1 christos do {
632 1.1 christos if (off >= O_VAL(sp, O_SIDESCROLL))
633 1.1 christos off -= O_VAL(sp, O_SIDESCROLL);
634 1.1 christos else {
635 1.1 christos off = 0;
636 1.1 christos break;
637 1.1 christos }
638 1.1 christos } while (off >= cnt);
639 1.1 christos goto shifted;
640 1.1 christos }
641 1.1 christos
642 1.1 christos /* Adjust the window towards the end of the line. */
643 1.2 christos if ((off == 0 && off + SCREEN_COLS(sp) < cnt) ||
644 1.2 christos (off != 0 && off + sp->cols < cnt)) {
645 1.1 christos do {
646 1.1 christos off += O_VAL(sp, O_SIDESCROLL);
647 1.1 christos } while (off + sp->cols < cnt);
648 1.1 christos
649 1.1 christos shifted: /* Fill in screen map with the new offset. */
650 1.1 christos if (F_ISSET(sp, SC_TINPUT_INFO))
651 1.1 christos smp->coff = off;
652 1.1 christos else {
653 1.1 christos for (smp = HMAP; smp <= TMAP; ++smp)
654 1.1 christos smp->coff = off;
655 1.1 christos leftright_warp = 1;
656 1.1 christos }
657 1.1 christos goto paint;
658 1.1 christos }
659 1.1 christos
660 1.1 christos /*
661 1.1 christos * We may have jumped here to adjust a leftright screen because
662 1.1 christos * redraw was set. If so, we have to paint the entire screen.
663 1.1 christos */
664 1.1 christos if (F_ISSET(sp, SC_SCR_REDRAW))
665 1.1 christos goto paint;
666 1.1 christos }
667 1.1 christos
668 1.1 christos /*
669 1.1 christos * Update the screen lines for this particular file line until we
670 1.1 christos * have a new screen cursor position.
671 1.1 christos */
672 1.1 christos for (y = -1,
673 1.1 christos vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) {
674 1.1 christos if (vs_line(sp, smp, &y, &SCNO))
675 1.1 christos return (1);
676 1.2 christos if (y != (size_t)-1) {
677 1.1 christos vip->sc_smap = smp;
678 1.1 christos break;
679 1.1 christos }
680 1.1 christos }
681 1.1 christos goto done_cursor;
682 1.1 christos
683 1.1 christos /*
684 1.1 christos * 8: Repaint the entire screen.
685 1.1 christos *
686 1.1 christos * Lost big, do what you have to do. We flush the cache, since
687 1.1 christos * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and
688 1.1 christos * it's simpler to repaint. So, don't trust anything that we
689 1.1 christos * think we know about it.
690 1.1 christos */
691 1.1 christos paint: for (smp = HMAP; smp <= TMAP; ++smp)
692 1.1 christos SMAP_FLUSH(smp);
693 1.1 christos for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) {
694 1.1 christos if (vs_line(sp, smp, &y, &SCNO))
695 1.1 christos return (1);
696 1.2 christos if (y != (size_t)-1 && vip->sc_smap == NULL)
697 1.1 christos vip->sc_smap = smp;
698 1.1 christos }
699 1.1 christos /*
700 1.1 christos * If it's a small screen and we're redrawing, clear the unused lines,
701 1.1 christos * ex may have overwritten them.
702 1.1 christos */
703 1.1 christos if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp))
704 1.1 christos for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {
705 1.1 christos (void)gp->scr_move(sp, cnt, 0);
706 1.1 christos (void)gp->scr_clrtoeol(sp);
707 1.1 christos }
708 1.1 christos
709 1.1 christos didpaint = 1;
710 1.1 christos
711 1.1 christos done_cursor:
712 1.1 christos /*
713 1.1 christos * Sanity checking. When the repainting code messes up, the usual
714 1.1 christos * result is we don't repaint the cursor and so sc_smap will be
715 1.1 christos * NULL. If we're debugging, die, otherwise restart from scratch.
716 1.1 christos */
717 1.1 christos #ifdef DEBUG
718 1.1 christos if (vip->sc_smap == NULL) {
719 1.1 christos fprintf(stderr, "smap error\n");
720 1.1 christos sleep(100);
721 1.1 christos abort();
722 1.1 christos }
723 1.1 christos #else
724 1.1 christos if (vip->sc_smap == NULL) {
725 1.7 rin if (F_ISSET(sp, SC_SCR_REFORMAT))
726 1.10 rin abort(); /* XXX infinite recursion */
727 1.1 christos F_SET(sp, SC_SCR_REFORMAT);
728 1.1 christos return (vs_paint(sp, flags));
729 1.1 christos }
730 1.1 christos #endif
731 1.1 christos
732 1.1 christos /*
733 1.1 christos * 9: Set the remembered cursor values.
734 1.1 christos */
735 1.1 christos OCNO = CNO;
736 1.1 christos OLNO = LNO;
737 1.1 christos
738 1.1 christos /*
739 1.1 christos * 10: Repaint the line numbers.
740 1.1 christos *
741 1.1 christos * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we
742 1.1 christos * didn't repaint the screen, repaint all of the line numbers,
743 1.1 christos * they've changed.
744 1.1 christos */
745 1.1 christos number: if (O_ISSET(sp, O_NUMBER) &&
746 1.1 christos F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp))
747 1.1 christos return (1);
748 1.1 christos
749 1.1 christos /*
750 1.1 christos * 11: Update the mode line, position the cursor, and flush changes.
751 1.1 christos *
752 1.1 christos * If we warped the screen, we have to refresh everything.
753 1.1 christos */
754 1.1 christos if (leftright_warp)
755 1.1 christos LF_SET(UPDATE_CURSOR | UPDATE_SCREEN);
756 1.1 christos
757 1.1 christos if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) &&
758 1.1 christos !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO))
759 1.1 christos vs_modeline(sp);
760 1.1 christos
761 1.1 christos if (LF_ISSET(UPDATE_CURSOR)) {
762 1.1 christos (void)gp->scr_move(sp, y, SCNO);
763 1.1 christos
764 1.1 christos /*
765 1.1 christos * XXX
766 1.1 christos * If the screen shifted, we recalculate the "most favorite"
767 1.1 christos * cursor position. Vi won't know that we've warped the
768 1.1 christos * screen, so it's going to have a wrong idea about where the
769 1.1 christos * cursor should be. This is vi's problem, and fixing it here
770 1.1 christos * is a gross layering violation.
771 1.1 christos */
772 1.1 christos if (leftright_warp)
773 1.1 christos (void)vs_column(sp, &sp->rcm);
774 1.1 christos }
775 1.1 christos
776 1.1 christos if (LF_ISSET(UPDATE_SCREEN))
777 1.1 christos (void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT));
778 1.1 christos
779 1.1 christos /* 12: Clear the flags that are handled by this routine. */
780 1.1 christos F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP);
781 1.1 christos F_CLR(vip, VIP_CUR_INVALID |
782 1.1 christos VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE);
783 1.1 christos
784 1.1 christos return (0);
785 1.1 christos
786 1.1 christos #undef LNO
787 1.1 christos #undef OLNO
788 1.1 christos #undef CNO
789 1.1 christos #undef OCNO
790 1.1 christos #undef SCNO
791 1.1 christos }
792 1.1 christos
793 1.1 christos /*
794 1.1 christos * vs_modeline --
795 1.1 christos * Update the mode line.
796 1.1 christos */
797 1.1 christos static void
798 1.1 christos vs_modeline(SCR *sp)
799 1.1 christos {
800 1.2 christos static const char * const modes[] = {
801 1.1 christos "215|Append", /* SM_APPEND */
802 1.1 christos "216|Change", /* SM_CHANGE */
803 1.1 christos "217|Command", /* SM_COMMAND */
804 1.1 christos "218|Insert", /* SM_INSERT */
805 1.1 christos "219|Replace", /* SM_REPLACE */
806 1.1 christos };
807 1.1 christos GS *gp;
808 1.1 christos size_t cols, curcol, curlen, endpoint, len, midpoint;
809 1.2 christos const char *t = NULL;
810 1.1 christos int ellipsis;
811 1.1 christos char *p, buf[20];
812 1.1 christos
813 1.1 christos gp = sp->gp;
814 1.1 christos
815 1.1 christos /*
816 1.1 christos * We put down the file name, the ruler, the mode and the dirty flag.
817 1.1 christos * If there's not enough room, there's not enough room, we don't play
818 1.1 christos * any special games. We try to put the ruler in the middle and the
819 1.1 christos * mode and dirty flag at the end.
820 1.1 christos *
821 1.1 christos * !!!
822 1.1 christos * Leave the last character blank, in case it's a really dumb terminal
823 1.1 christos * with hardware scroll. Second, don't paint the last character in the
824 1.1 christos * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you.
825 1.1 christos *
826 1.1 christos * Move to the last line on the screen.
827 1.1 christos */
828 1.1 christos (void)gp->scr_move(sp, LASTLINE(sp), 0);
829 1.1 christos
830 1.1 christos /* If more than one screen in the display, show the file name. */
831 1.1 christos curlen = 0;
832 1.1 christos if (IS_SPLIT(sp)) {
833 1.1 christos for (p = sp->frp->name; *p != '\0'; ++p);
834 1.1 christos for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) {
835 1.1 christos if (*p == '/') {
836 1.1 christos ++p;
837 1.1 christos break;
838 1.1 christos }
839 1.1 christos if ((curlen += KEY_LEN(sp, *p)) > cols) {
840 1.1 christos ellipsis = 3;
841 1.1 christos curlen +=
842 1.1 christos KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' ');
843 1.1 christos while (curlen > cols) {
844 1.1 christos ++p;
845 1.1 christos curlen -= KEY_LEN(sp, *p);
846 1.1 christos }
847 1.1 christos break;
848 1.1 christos }
849 1.1 christos }
850 1.1 christos if (ellipsis) {
851 1.1 christos while (ellipsis--)
852 1.1 christos (void)gp->scr_addstr(sp,
853 1.2 christos (const char *)KEY_NAME(sp, '.'),
854 1.2 christos KEY_LEN(sp, '.'));
855 1.1 christos (void)gp->scr_addstr(sp,
856 1.2 christos (const char *)KEY_NAME(sp, ' '), KEY_LEN(sp, ' '));
857 1.1 christos }
858 1.1 christos for (; *p != '\0'; ++p)
859 1.1 christos (void)gp->scr_addstr(sp,
860 1.2 christos (const char *)KEY_NAME(sp, *p), KEY_LEN(sp, *p));
861 1.1 christos }
862 1.1 christos
863 1.1 christos /* Clear the rest of the line. */
864 1.1 christos (void)gp->scr_clrtoeol(sp);
865 1.1 christos
866 1.1 christos /*
867 1.1 christos * Display the ruler. If we're not at the midpoint yet, move there.
868 1.1 christos * Otherwise, add in two extra spaces.
869 1.1 christos *
870 1.1 christos * Adjust the current column for the fact that the editor uses it as
871 1.1 christos * a zero-based number.
872 1.1 christos *
873 1.1 christos * XXX
874 1.1 christos * Assume that numbers, commas, and spaces only take up a single
875 1.1 christos * column on the screen.
876 1.1 christos */
877 1.1 christos cols = sp->cols - 1;
878 1.1 christos if (O_ISSET(sp, O_RULER)) {
879 1.1 christos vs_column(sp, &curcol);
880 1.1 christos len =
881 1.2 christos snprintf(buf, sizeof(buf), "%lu,%lu",
882 1.2 christos (unsigned long)sp->lno, (unsigned long)curcol + 1);
883 1.1 christos
884 1.1 christos midpoint = (cols - ((len + 1) / 2)) / 2;
885 1.1 christos if (curlen < midpoint) {
886 1.1 christos (void)gp->scr_move(sp, LASTLINE(sp), midpoint);
887 1.1 christos curlen += len;
888 1.1 christos } else if (curlen + 2 + len < cols) {
889 1.1 christos (void)gp->scr_addstr(sp, " ", 2);
890 1.1 christos curlen += 2 + len;
891 1.1 christos }
892 1.1 christos (void)gp->scr_addstr(sp, buf, len);
893 1.1 christos }
894 1.1 christos
895 1.1 christos /*
896 1.1 christos * Display the mode and the modified flag, as close to the end of the
897 1.1 christos * line as possible, but guaranteeing at least two spaces between the
898 1.1 christos * ruler and the modified flag.
899 1.1 christos */
900 1.1 christos #define MODESIZE 9
901 1.1 christos endpoint = cols;
902 1.1 christos if (O_ISSET(sp, O_SHOWMODE)) {
903 1.1 christos if (F_ISSET(sp->ep, F_MODIFIED))
904 1.1 christos --endpoint;
905 1.1 christos t = msg_cat(sp, modes[sp->showmode], &len);
906 1.1 christos endpoint -= len;
907 1.1 christos }
908 1.1 christos
909 1.1 christos if (endpoint > curlen + 2) {
910 1.1 christos (void)gp->scr_move(sp, LASTLINE(sp), endpoint);
911 1.1 christos if (O_ISSET(sp, O_SHOWMODE)) {
912 1.1 christos if (F_ISSET(sp->ep, F_MODIFIED))
913 1.1 christos (void)gp->scr_addstr(sp,
914 1.2 christos (const char *)KEY_NAME(sp, '*'),
915 1.2 christos KEY_LEN(sp, '*'));
916 1.1 christos (void)gp->scr_addstr(sp, t, len);
917 1.1 christos }
918 1.1 christos }
919 1.1 christos }
920