Commit | Line | Data |
---|---|---|
ed554bc5 C |
1 | /*- |
2 | * Copyright (c) 1993, 1994 | |
3 | * The Regents of the University of California. All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. All advertising materials mentioning features or use of this software | |
14 | * must display the following acknowledgement: | |
15 | * This product includes software developed by the University of | |
16 | * California, Berkeley and its contributors. | |
17 | * 4. Neither the name of the University nor the names of its contributors | |
18 | * may be used to endorse or promote products derived from this software | |
19 | * without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
31 | * SUCH DAMAGE. | |
32 | */ | |
33 | ||
34 | #ifndef lint | |
fd88f5c5 | 35 | static char sccsid[] = "@(#)svi_line.c 9.1 (Berkeley) 11/9/94"; |
ed554bc5 C |
36 | #endif /* not lint */ |
37 | ||
38 | #include <sys/types.h> | |
39 | #include <sys/queue.h> | |
40 | #include <sys/time.h> | |
41 | ||
42 | #include <bitstring.h> | |
43 | #include <limits.h> | |
44 | #include <signal.h> | |
45 | #include <stdio.h> | |
46 | #include <string.h> | |
47 | #include <termios.h> | |
48 | ||
49 | #include "compat.h" | |
50 | #include <curses.h> | |
51 | #include <db.h> | |
52 | #include <regex.h> | |
53 | ||
54 | #include "vi.h" | |
55 | #include "svi_screen.h" | |
56 | ||
57 | #if defined(DEBUG) && 0 | |
58 | #define TABCH '-' | |
59 | #define TABSTR "--------------------" | |
60 | #else | |
61 | #define TABSTR " " | |
62 | #define TABCH ' ' | |
63 | #endif | |
64 | ||
65 | /* | |
66 | * svi_line -- | |
67 | * Update one line on the screen. | |
68 | */ | |
69 | int | |
fd88f5c5 | 70 | svi_line(sp, smp, yp, xp) |
ed554bc5 | 71 | SCR *sp; |
ed554bc5 C |
72 | SMAP *smp; |
73 | size_t *xp, *yp; | |
74 | { | |
75 | SMAP *tsmp; | |
76 | size_t chlen, cols_per_screen, cno_cnt, len, scno, skip_screens; | |
77 | size_t offset_in_char, offset_in_line; | |
78 | size_t oldy, oldx; | |
fd88f5c5 C |
79 | int ch, is_cached, is_infoline, is_partial, is_tab; |
80 | int list_tab, list_dollar; | |
ed554bc5 C |
81 | char *p, nbuf[10]; |
82 | ||
83 | #if defined(DEBUG) && 0 | |
84 | TRACE(sp, "svi_line: row %u: line: %u off: %u\n", | |
85 | smp - HMAP, smp->lno, smp->off); | |
86 | #endif | |
87 | ||
88 | /* | |
89 | * Assume that, if the cache entry for the line is filled in, the | |
90 | * line is already on the screen, and all we need to do is return | |
91 | * the cursor position. If the calling routine doesn't need the | |
92 | * cursor position, we can just return. | |
93 | */ | |
94 | is_cached = SMAP_CACHE(smp); | |
95 | if (yp == NULL && is_cached) | |
96 | return (0); | |
97 | ||
98 | /* | |
99 | * A nasty side effect of this routine is that it returns the screen | |
100 | * position for the "current" character. Not pretty, but this is the | |
101 | * only routine that really knows what's out there. | |
102 | * | |
103 | * Move to the line. This routine can be called by svi_sm_position(), | |
104 | * which uses it to fill in the cache entry so it can figure out what | |
105 | * the real contents of the screen are. Because of this, we have to | |
106 | * return to whereever we started from. | |
107 | */ | |
108 | getyx(stdscr, oldy, oldx); | |
109 | MOVE(sp, smp - HMAP, 0); | |
110 | ||
111 | /* Get a copy of the line. */ | |
fd88f5c5 | 112 | p = file_gline(sp, smp->lno, &len); |
ed554bc5 C |
113 | |
114 | /* | |
115 | * Special case if we're printing the info/mode line. Skip printing | |
116 | * the leading number, as well as other minor setup. If painting the | |
117 | * line between two screens, it's always in reverse video. The only | |
118 | * time this code paints the mode line is when the user is entering | |
119 | * text for a ":" command, so we can put the code here instead of | |
120 | * dealing with the empty line logic below. This is a kludge, but it's | |
121 | * pretty much confined to this module. | |
122 | * | |
123 | * Set the number of screens to skip until a character is displayed. | |
124 | * Left-right screens are special, because we don't bother building | |
125 | * a buffer to be skipped over. | |
126 | * | |
127 | * Set the number of columns for this screen. | |
128 | */ | |
129 | cols_per_screen = sp->cols; | |
fd88f5c5 | 130 | list_tab = O_ISSET(sp, O_LIST); |
ed554bc5 | 131 | if (is_infoline = ISINFOLINE(sp, smp)) { |
fd88f5c5 | 132 | list_dollar = 0; |
ed554bc5 C |
133 | if (O_ISSET(sp, O_LEFTRIGHT)) |
134 | skip_screens = 0; | |
135 | else | |
136 | skip_screens = smp->off - 1; | |
137 | } else { | |
fd88f5c5 | 138 | list_dollar = list_tab; |
ed554bc5 C |
139 | skip_screens = smp->off - 1; |
140 | ||
141 | /* | |
142 | * If O_NUMBER is set and it's line number 1 or the line exists | |
143 | * and this is the first screen of a folding line or any left- | |
144 | * right line, display the line number. | |
145 | */ | |
146 | if (O_ISSET(sp, O_NUMBER)) { | |
147 | cols_per_screen -= O_NUMBER_LENGTH; | |
148 | if ((smp->lno == 1 || p != NULL) && skip_screens == 0) { | |
149 | (void)snprintf(nbuf, | |
150 | sizeof(nbuf), O_NUMBER_FMT, smp->lno); | |
151 | ADDSTR(nbuf); | |
152 | } | |
153 | } | |
154 | } | |
155 | ||
156 | /* | |
157 | * Special case non-existent lines and the first line of an empty | |
158 | * file. In both cases, the cursor position is 0, but corrected | |
159 | * for the O_NUMBER field if it was displayed. | |
160 | */ | |
161 | if (p == NULL || len == 0) { | |
162 | /* Fill in the cursor. */ | |
163 | if (yp != NULL && smp->lno == sp->lno) { | |
164 | *yp = smp - HMAP; | |
165 | *xp = sp->cols - cols_per_screen; | |
166 | } | |
167 | ||
168 | /* If the line is on the screen, quit. */ | |
169 | if (is_cached) | |
170 | goto ret; | |
171 | ||
172 | /* Set line cacheing information. */ | |
173 | smp->c_sboff = smp->c_eboff = 0; | |
174 | smp->c_scoff = smp->c_eclen = 0; | |
175 | ||
176 | /* Lots of special cases for empty lines. */ | |
177 | if (skip_screens == 0) | |
178 | if (p == NULL) { | |
179 | if (smp->lno == 1) { | |
fd88f5c5 | 180 | if (list_dollar) { |
ed554bc5 C |
181 | ch = '$'; |
182 | goto empty; | |
183 | } | |
184 | } else { | |
185 | ch = '~'; | |
186 | goto empty; | |
187 | } | |
188 | } else | |
fd88f5c5 | 189 | if (list_dollar) { |
ed554bc5 C |
190 | ch = '$'; |
191 | empty: ADDCH(ch); | |
192 | } | |
193 | ||
194 | clrtoeol(); | |
195 | MOVEA(sp, oldy, oldx); | |
196 | return (0); | |
197 | } | |
198 | ||
199 | /* | |
200 | * If we wrote a line that's this or a previous one, we can do this | |
201 | * much more quickly -- we cached the starting and ending positions | |
202 | * of that line. The way it works is we keep information about the | |
203 | * lines displayed in the SMAP. If we're painting the screen in | |
204 | * the forward, this saves us from reformatting the physical line for | |
205 | * every line on the screen. This wins big on binary files with 10K | |
206 | * lines. | |
207 | * | |
208 | * Test for the first screen of the line, then the current screen line, | |
209 | * then the line behind us, then do the hard work. Note, it doesn't | |
210 | * do us any good to have a line in front of us -- it would be really | |
211 | * hard to try and figure out tabs in the reverse direction, i.e. how | |
212 | * many spaces a tab takes up in the reverse direction depends on | |
213 | * what characters preceded it. | |
214 | */ | |
215 | if (smp->off == 1) { | |
216 | smp->c_sboff = offset_in_line = 0; | |
217 | smp->c_scoff = offset_in_char = 0; | |
218 | p = &p[offset_in_line]; | |
219 | } else if (is_cached) { | |
220 | offset_in_line = smp->c_sboff; | |
221 | offset_in_char = smp->c_scoff; | |
222 | p = &p[offset_in_line]; | |
223 | if (skip_screens != 0) | |
224 | cols_per_screen = sp->cols; | |
225 | } else if (smp != HMAP && | |
226 | SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) { | |
227 | if (tsmp->c_eclen != tsmp->c_ecsize) { | |
228 | offset_in_line = tsmp->c_eboff; | |
229 | offset_in_char = tsmp->c_eclen; | |
230 | } else { | |
231 | offset_in_line = tsmp->c_eboff + 1; | |
232 | offset_in_char = 0; | |
233 | } | |
234 | ||
235 | /* Put starting info for this line in the cache. */ | |
236 | smp->c_sboff = offset_in_line; | |
237 | smp->c_scoff = offset_in_char; | |
238 | p = &p[offset_in_line]; | |
239 | if (skip_screens != 0) | |
240 | cols_per_screen = sp->cols; | |
241 | } else { | |
242 | offset_in_line = 0; | |
243 | offset_in_char = 0; | |
244 | ||
245 | /* This is the loop that skips through screens. */ | |
246 | if (skip_screens == 0) { | |
247 | smp->c_sboff = offset_in_line; | |
248 | smp->c_scoff = offset_in_char; | |
249 | } else for (scno = 0; offset_in_line < len; ++offset_in_line) { | |
250 | scno += chlen = | |
fd88f5c5 | 251 | (ch = *(u_char *)p++) == '\t' && !list_tab ? |
ed554bc5 C |
252 | TAB_OFF(sp, scno) : KEY_LEN(sp, ch); |
253 | if (scno < cols_per_screen) | |
254 | continue; | |
255 | /* | |
256 | * Reset cols_per_screen to second and subsequent line | |
257 | * length. | |
258 | */ | |
259 | scno -= cols_per_screen; | |
260 | cols_per_screen = sp->cols; | |
261 | ||
262 | /* | |
263 | * If crossed the last skipped screen boundary, start | |
264 | * displaying the characters. | |
265 | */ | |
266 | if (--skip_screens) | |
267 | continue; | |
268 | ||
269 | /* Put starting info for this line in the cache. */ | |
270 | if (scno) { | |
271 | smp->c_sboff = offset_in_line; | |
272 | smp->c_scoff = offset_in_char = chlen - scno; | |
273 | --p; | |
274 | } else { | |
275 | smp->c_sboff = ++offset_in_line; | |
276 | smp->c_scoff = 0; | |
277 | } | |
278 | break; | |
279 | } | |
280 | } | |
281 | ||
282 | /* | |
283 | * Set the number of characters to skip before reaching the cursor | |
284 | * character. Offset by 1 and use 0 as a flag value. Svi_line is | |
285 | * called repeatedly with a valid pointer to a cursor position. | |
286 | * Don't fill anything in unless it's the right line and the right | |
287 | * character, and the right part of the character... | |
288 | */ | |
289 | if (yp == NULL || | |
290 | smp->lno != sp->lno || sp->cno < offset_in_line || | |
291 | offset_in_line + cols_per_screen < sp->cno) { | |
292 | cno_cnt = 0; | |
293 | /* If the line is on the screen, quit. */ | |
294 | if (is_cached) | |
295 | goto ret; | |
296 | } else | |
297 | cno_cnt = (sp->cno - offset_in_line) + 1; | |
298 | ||
299 | /* This is the loop that actually displays characters. */ | |
300 | for (is_partial = 0, scno = 0; | |
301 | offset_in_line < len; ++offset_in_line, offset_in_char = 0) { | |
fd88f5c5 | 302 | if ((ch = *(u_char *)p++) == '\t' && !list_tab) { |
ed554bc5 C |
303 | scno += chlen = TAB_OFF(sp, scno) - offset_in_char; |
304 | is_tab = 1; | |
305 | } else { | |
306 | scno += chlen = KEY_LEN(sp, ch) - offset_in_char; | |
307 | is_tab = 0; | |
308 | } | |
309 | ||
310 | /* | |
311 | * Only display up to the right-hand column. Set a flag if | |
312 | * the entire character wasn't displayed for use in setting | |
313 | * the cursor. If reached the end of the line, set the cache | |
314 | * info for the screen. Don't worry about there not being | |
315 | * characters to display on the next screen, its lno/off won't | |
316 | * match up in that case. | |
317 | */ | |
318 | if (scno >= cols_per_screen) { | |
319 | smp->c_ecsize = chlen; | |
320 | chlen -= scno - cols_per_screen; | |
321 | smp->c_eclen = chlen; | |
322 | smp->c_eboff = offset_in_line; | |
323 | if (scno > cols_per_screen) | |
324 | is_partial = 1; | |
325 | ||
326 | /* Terminate the loop. */ | |
327 | offset_in_line = len; | |
328 | } | |
329 | ||
330 | /* | |
331 | * If the caller wants the cursor value, and this was the | |
332 | * cursor character, set the value. There are two ways to | |
333 | * put the cursor on a character -- if it's normal display | |
334 | * mode, it goes on the last column of the character. If | |
335 | * it's input mode, it goes on the first. In normal mode, | |
336 | * set the cursor only if the entire character was displayed. | |
337 | */ | |
338 | if (cno_cnt && | |
339 | --cno_cnt == 0 && (F_ISSET(sp, S_INPUT) || !is_partial)) { | |
340 | *yp = smp - HMAP; | |
341 | if (F_ISSET(sp, S_INPUT)) | |
342 | *xp = scno - chlen; | |
343 | else | |
344 | *xp = scno - 1; | |
345 | if (O_ISSET(sp, O_NUMBER) && | |
346 | !is_infoline && smp->off == 1) | |
347 | *xp += O_NUMBER_LENGTH; | |
348 | ||
349 | /* If the line is on the screen, quit. */ | |
350 | if (is_cached) | |
351 | goto ret; | |
352 | } | |
353 | ||
354 | /* If the line is on the screen, don't display anything. */ | |
355 | if (is_cached) | |
356 | continue; | |
357 | ||
358 | /* | |
359 | * Display the character. If it's a tab and tabs aren't some | |
360 | * ridiculous length, do it fast. (We do tab expansion here | |
361 | * because curses doesn't have a way to set the tab length.) | |
362 | */ | |
363 | if (is_tab) { | |
364 | if (chlen <= sizeof(TABSTR) - 1) { | |
365 | ADDNSTR(TABSTR, chlen); | |
366 | } else | |
367 | while (chlen--) | |
368 | ADDCH(TABCH); | |
369 | } else | |
370 | ADDNSTR(KEY_NAME(sp, ch) + offset_in_char, chlen); | |
371 | } | |
372 | ||
373 | if (scno < cols_per_screen) { | |
374 | /* If didn't paint the whole line, update the cache. */ | |
375 | smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch); | |
376 | smp->c_eboff = len - 1; | |
377 | ||
378 | /* | |
379 | * If not the info/mode line, and O_LIST set, and at the | |
380 | * end of the line, and the line ended on this screen, | |
381 | * add a trailing $. | |
382 | */ | |
fd88f5c5 | 383 | if (list_dollar) { |
ed554bc5 C |
384 | ++scno; |
385 | ADDCH('$'); | |
386 | } | |
387 | ||
388 | /* If still didn't paint the whole line, clear the rest. */ | |
389 | if (scno < cols_per_screen) | |
390 | clrtoeol(); | |
391 | } | |
392 | ||
393 | ret: MOVEA(sp, oldy, oldx); | |
394 | return (0); | |
395 | } | |
396 | ||
397 | /* | |
398 | * svi_number -- | |
399 | * Repaint the numbers on all the lines. | |
400 | */ | |
401 | int | |
fd88f5c5 | 402 | svi_number(sp) |
ed554bc5 | 403 | SCR *sp; |
ed554bc5 C |
404 | { |
405 | SMAP *smp; | |
406 | size_t oldy, oldx; | |
407 | char *lp, nbuf[10]; | |
408 | ||
409 | /* | |
410 | * Try and avoid getting the last line in the file, by getting the | |
411 | * line after the last line in the screen -- if it exists, we know | |
412 | * we have to to number all the lines in the screen. Get the one | |
413 | * after the last instead of the last, so that the info line doesn't | |
414 | * fool us. | |
415 | * | |
416 | * If that test fails, we have to check each line for existence. | |
417 | * | |
418 | * XXX | |
419 | * The problem is that file_lline will lie, and tell us that the | |
420 | * info line is the last line in the file. | |
421 | */ | |
fd88f5c5 | 422 | lp = file_gline(sp, TMAP->lno + 1, NULL); |
ed554bc5 C |
423 | |
424 | getyx(stdscr, oldy, oldx); | |
425 | for (smp = HMAP; smp <= TMAP; ++smp) { | |
426 | if (smp->off != 1) | |
427 | continue; | |
428 | if (ISINFOLINE(sp, smp)) | |
429 | break; | |
430 | if (smp->lno != 1 && lp == NULL && | |
fd88f5c5 | 431 | file_gline(sp, smp->lno, NULL) == NULL) |
ed554bc5 C |
432 | break; |
433 | MOVE(sp, smp - HMAP, 0); | |
434 | (void)snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno); | |
435 | ADDSTR(nbuf); | |
436 | } | |
437 | MOVEA(sp, oldy, oldx); | |
438 | return (0); | |
439 | } |