1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
|
/*-
* Copyright (c) 1993, 1994
* The Regents of the University of California. All rights reserved.
* Copyright (c) 1992, 1993, 1994, 1995, 1996
* Keith Bostic. All rights reserved.
*
* See the LICENSE file for redistribution information.
*/
#include "config.h"
#ifndef lint
static const char sccsid[] = "@(#)vs_line.c 10.19 (Berkeley) 9/26/96";
#endif /* not lint */
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <bitstring.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "../common/common.h"
#include "vi.h"
#ifdef VISIBLE_TAB_CHARS
#define TABCH '-'
#else
#define TABCH ' '
#endif
/*
* vs_line --
* Update one line on the screen.
*
* PUBLIC: int vs_line __P((SCR *, SMAP *, size_t *, size_t *));
*/
int
vs_line(sp, smp, yp, xp)
SCR *sp;
SMAP *smp;
size_t *xp, *yp;
{
CHAR_T *kp;
GS *gp;
SMAP *tsmp;
size_t chlen, cno_cnt, cols_per_screen, len, nlen;
size_t offset_in_char, offset_in_line, oldx, oldy;
size_t scno, skip_cols, skip_screens;
int ch, dne, is_cached, is_partial, is_tab;
int list_tab, list_dollar;
char *p, *cbp, *ecbp, cbuf[128];
#if defined(DEBUG) && 0
TRACE(sp, "vs_line: row %u: line: %u off: %u\n",
smp - HMAP, smp->lno, smp->off);
#endif
/*
* If ex modifies the screen after ex output is already on the screen,
* don't touch it -- we'll get scrolling wrong, at best.
*/
if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1)
return (0);
if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp))
return (0);
/*
* Assume that, if the cache entry for the line is filled in, the
* line is already on the screen, and all we need to do is return
* the cursor position. If the calling routine doesn't need the
* cursor position, we can just return.
*/
is_cached = SMAP_CACHE(smp);
if (yp == NULL && is_cached)
return (0);
/*
* A nasty side effect of this routine is that it returns the screen
* position for the "current" character. Not pretty, but this is the
* only routine that really knows what's out there.
*
* Move to the line. This routine can be called by vs_sm_position(),
* which uses it to fill in the cache entry so it can figure out what
* the real contents of the screen are. Because of this, we have to
* return to whereever we started from.
*/
gp = sp->gp;
(void)gp->scr_cursor(sp, &oldy, &oldx);
(void)gp->scr_move(sp, smp - HMAP, 0);
/* Get the line. */
dne = db_get(sp, smp->lno, 0, &p, &len);
/*
* Special case if we're printing the info/mode line. Skip printing
* the leading number, as well as other minor setup. The only time
* this code paints the mode line is when the user is entering text
* for a ":" command, so we can put the code here instead of dealing
* with the empty line logic below. This is a kludge, but it's pretty
* much confined to this module.
*
* Set the number of columns for this screen.
* Set the number of chars or screens to skip until a character is to
* be displayed.
*/
cols_per_screen = sp->cols;
if (O_ISSET(sp, O_LEFTRIGHT)) {
skip_screens = 0;
skip_cols = smp->coff;
} else {
skip_screens = smp->soff - 1;
skip_cols = skip_screens * cols_per_screen;
}
list_tab = O_ISSET(sp, O_LIST);
if (F_ISSET(sp, SC_TINPUT_INFO))
list_dollar = 0;
else {
list_dollar = list_tab;
/*
* If O_NUMBER is set, the line doesn't exist and it's line
* number 1, i.e., an empty file, display the line number.
*
* If O_NUMBER is set, the line exists and the first character
* on the screen is the first character in the line, display
* the line number.
*
* !!!
* If O_NUMBER set, decrement the number of columns in the
* first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The
* rest of the code expects this to reflect the number of
* columns in the first screen, regardless of the number of
* columns we're going to skip.
*/
if (O_ISSET(sp, O_NUMBER)) {
cols_per_screen -= O_NUMBER_LENGTH;
if ((!dne || smp->lno == 1) && skip_cols == 0) {
nlen = snprintf(cbuf,
sizeof(cbuf), O_NUMBER_FMT, smp->lno);
(void)gp->scr_addstr(sp, cbuf, nlen);
}
}
}
/*
* Special case non-existent lines and the first line of an empty
* file. In both cases, the cursor position is 0, but corrected
* as necessary for the O_NUMBER field, if it was displayed.
*/
if (dne || len == 0) {
/* Fill in the cursor. */
if (yp != NULL && smp->lno == sp->lno) {
*yp = smp - HMAP;
*xp = sp->cols - cols_per_screen;
}
/* If the line is on the screen, quit. */
if (is_cached)
goto ret1;
/* Set line cache information. */
smp->c_sboff = smp->c_eboff = 0;
smp->c_scoff = smp->c_eclen = 0;
/*
* Lots of special cases for empty lines, but they only apply
* if we're displaying the first screen of the line.
*/
if (skip_cols == 0)
if (dne) {
if (smp->lno == 1) {
if (list_dollar) {
ch = '$';
goto empty;
}
} else {
ch = '~';
goto empty;
}
} else
if (list_dollar) {
ch = '$';
empty: (void)gp->scr_addstr(sp,
KEY_NAME(sp, ch), KEY_LEN(sp, ch));
}
(void)gp->scr_clrtoeol(sp);
(void)gp->scr_move(sp, oldy, oldx);
return (0);
}
/*
* If we just wrote this or a previous line, we cached the starting
* and ending positions of that line. The way it works is we keep
* information about the lines displayed in the SMAP. If we're
* painting the screen in the forward direction, this saves us from
* reformatting the physical line for every line on the screen. This
* wins big on binary files with 10K lines.
*
* Test for the first screen of the line, then the current screen line,
* then the line behind us, then do the hard work. Note, it doesn't
* do us any good to have a line in front of us -- it would be really
* hard to try and figure out tabs in the reverse direction, i.e. how
* many spaces a tab takes up in the reverse direction depends on
* what characters preceded it.
*
* Test for the first screen of the line.
*/
if (skip_cols == 0) {
smp->c_sboff = offset_in_line = 0;
smp->c_scoff = offset_in_char = 0;
p = &p[offset_in_line];
goto display;
}
/* Test to see if we've seen this exact line before. */
if (is_cached) {
offset_in_line = smp->c_sboff;
offset_in_char = smp->c_scoff;
p = &p[offset_in_line];
/* Set cols_per_screen to 2nd and later line length. */
if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
cols_per_screen = sp->cols;
goto display;
}
/* Test to see if we saw an earlier part of this line before. */
if (smp != HMAP &&
SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) {
if (tsmp->c_eclen != tsmp->c_ecsize) {
offset_in_line = tsmp->c_eboff;
offset_in_char = tsmp->c_eclen;
} else {
offset_in_line = tsmp->c_eboff + 1;
offset_in_char = 0;
}
/* Put starting info for this line in the cache. */
smp->c_sboff = offset_in_line;
smp->c_scoff = offset_in_char;
p = &p[offset_in_line];
/* Set cols_per_screen to 2nd and later line length. */
if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen)
cols_per_screen = sp->cols;
goto display;
}
scno = 0;
offset_in_line = 0;
offset_in_char = 0;
/* Do it the hard way, for leftright scrolling screens. */
if (O_ISSET(sp, O_LEFTRIGHT)) {
for (; offset_in_line < len; ++offset_in_line) {
chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
TAB_OFF(scno) : KEY_LEN(sp, ch);
if ((scno += chlen) >= skip_cols)
break;
}
/* Set cols_per_screen to 2nd and later line length. */
cols_per_screen = sp->cols;
/* Put starting info for this line in the cache. */
if (scno != skip_cols) {
smp->c_sboff = offset_in_line;
smp->c_scoff =
offset_in_char = chlen - (scno - skip_cols);
--p;
} else {
smp->c_sboff = ++offset_in_line;
smp->c_scoff = 0;
}
}
/* Do it the hard way, for historic line-folding screens. */
else {
for (; offset_in_line < len; ++offset_in_line) {
chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ?
TAB_OFF(scno) : KEY_LEN(sp, ch);
if ((scno += chlen) < cols_per_screen)
continue;
scno -= cols_per_screen;
/* Set cols_per_screen to 2nd and later line length. */
cols_per_screen = sp->cols;
/*
* If crossed the last skipped screen boundary, start
* displaying the characters.
*/
if (--skip_screens == 0)
break;
}
/* Put starting info for this line in the cache. */
if (scno != 0) {
smp->c_sboff = offset_in_line;
smp->c_scoff = offset_in_char = chlen - scno;
--p;
} else {
smp->c_sboff = ++offset_in_line;
smp->c_scoff = 0;
}
}
display:
/*
* Set the number of characters to skip before reaching the cursor
* character. Offset by 1 and use 0 as a flag value. Vs_line is
* called repeatedly with a valid pointer to a cursor position.
* Don't fill anything in unless it's the right line and the right
* character, and the right part of the character...
*/
if (yp == NULL ||
smp->lno != sp->lno || sp->cno < offset_in_line ||
offset_in_line + cols_per_screen < sp->cno) {
cno_cnt = 0;
/* If the line is on the screen, quit. */
if (is_cached)
goto ret1;
} else
cno_cnt = (sp->cno - offset_in_line) + 1;
/* This is the loop that actually displays characters. */
ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
for (is_partial = 0, scno = 0;
offset_in_line < len; ++offset_in_line, offset_in_char = 0) {
if ((ch = *(u_char *)p++) == '\t' && !list_tab) {
scno += chlen = TAB_OFF(scno) - offset_in_char;
is_tab = 1;
} else {
scno += chlen = KEY_LEN(sp, ch) - offset_in_char;
is_tab = 0;
}
/*
* Only display up to the right-hand column. Set a flag if
* the entire character wasn't displayed for use in setting
* the cursor. If reached the end of the line, set the cache
* info for the screen. Don't worry about there not being
* characters to display on the next screen, its lno/off won't
* match up in that case.
*/
if (scno >= cols_per_screen) {
if (is_tab == 1) {
chlen -= scno - cols_per_screen;
smp->c_ecsize = smp->c_eclen = chlen;
scno = cols_per_screen;
} else {
smp->c_ecsize = chlen;
chlen -= scno - cols_per_screen;
smp->c_eclen = chlen;
if (scno > cols_per_screen)
is_partial = 1;
}
smp->c_eboff = offset_in_line;
/* Terminate the loop. */
offset_in_line = len;
}
/*
* If the caller wants the cursor value, and this was the
* cursor character, set the value. There are two ways to
* put the cursor on a character -- if it's normal display
* mode, it goes on the last column of the character. If
* it's input mode, it goes on the first. In normal mode,
* set the cursor only if the entire character was displayed.
*/
if (cno_cnt &&
--cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) {
*yp = smp - HMAP;
if (F_ISSET(sp, SC_TINPUT))
*xp = scno - chlen;
else
*xp = scno - 1;
if (O_ISSET(sp, O_NUMBER) &&
!F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0)
*xp += O_NUMBER_LENGTH;
/* If the line is on the screen, quit. */
if (is_cached)
goto ret1;
}
/* If the line is on the screen, don't display anything. */
if (is_cached)
continue;
#define FLUSH { \
*cbp = '\0'; \
(void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \
cbp = cbuf; \
}
/*
* Display the character. We do tab expansion here because
* the screen interface doesn't have any way to set the tab
* length. Note, it's theoretically possible for chlen to
* be larger than cbuf, if the user set a impossibly large
* tabstop.
*/
if (is_tab)
while (chlen--) {
if (cbp >= ecbp)
FLUSH;
*cbp++ = TABCH;
}
else {
if (cbp + chlen >= ecbp)
FLUSH;
for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;)
*cbp++ = *kp++;
}
}
if (scno < cols_per_screen) {
/* If didn't paint the whole line, update the cache. */
smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch);
smp->c_eboff = len - 1;
/*
* If not the info/mode line, and O_LIST set, and at the
* end of the line, and the line ended on this screen,
* add a trailing $.
*/
if (list_dollar) {
++scno;
chlen = KEY_LEN(sp, '$');
if (cbp + chlen >= ecbp)
FLUSH;
for (kp = KEY_NAME(sp, '$'); chlen--;)
*cbp++ = *kp++;
}
/* If still didn't paint the whole line, clear the rest. */
if (scno < cols_per_screen)
(void)gp->scr_clrtoeol(sp);
}
/* Flush any buffered characters. */
if (cbp > cbuf)
FLUSH;
ret1: (void)gp->scr_move(sp, oldy, oldx);
return (0);
}
/*
* vs_number --
* Repaint the numbers on all the lines.
*
* PUBLIC: int vs_number __P((SCR *));
*/
int
vs_number(sp)
SCR *sp;
{
GS *gp;
SMAP *smp;
VI_PRIVATE *vip;
size_t len, oldy, oldx;
int exist;
char nbuf[10];
gp = sp->gp;
vip = VIP(sp);
/* No reason to do anything if we're in input mode on the info line. */
if (F_ISSET(sp, SC_TINPUT_INFO))
return (0);
/*
* Try and avoid getting the last line in the file, by getting the
* line after the last line in the screen -- if it exists, we know
* we have to to number all the lines in the screen. Get the one
* after the last instead of the last, so that the info line doesn't
* fool us. (The problem is that file_lline will lie, and tell us
* that the info line is the last line in the file.) If that test
* fails, we have to check each line for existence.
*/
exist = db_exist(sp, TMAP->lno + 1);
(void)gp->scr_cursor(sp, &oldy, &oldx);
for (smp = HMAP; smp <= TMAP; ++smp) {
/* Numbers are only displayed for the first screen line. */
if (O_ISSET(sp, O_LEFTRIGHT)) {
if (smp->coff != 0)
continue;
} else
if (smp->soff != 1)
continue;
/*
* The first line of an empty file gets numbered, otherwise
* number any existing line.
*/
if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno))
break;
(void)gp->scr_move(sp, smp - HMAP, 0);
len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno);
(void)gp->scr_addstr(sp, nbuf, len);
}
(void)gp->scr_move(sp, oldy, oldx);
return (0);
}
|