119304Speter/*- 219304Speter * Copyright (c) 1993, 1994 319304Speter * The Regents of the University of California. All rights reserved. 419304Speter * Copyright (c) 1992, 1993, 1994, 1995, 1996 519304Speter * Keith Bostic. All rights reserved. 619304Speter * 719304Speter * See the LICENSE file for redistribution information. 819304Speter */ 919304Speter 1019304Speter#include "config.h" 1119304Speter 1219304Speter#ifndef lint 1390022Ssheldonh#if 0 1419304Speterstatic const char sccsid[] = "@(#)vs_line.c 10.19 (Berkeley) 9/26/96"; 1590022Ssheldonh#endif 1690022Ssheldonhstatic const char rcsid[] = 1790022Ssheldonh "$FreeBSD$"; 1819304Speter#endif /* not lint */ 1919304Speter 2019304Speter#include <sys/types.h> 2119304Speter#include <sys/queue.h> 2219304Speter#include <sys/time.h> 2319304Speter 2419304Speter#include <bitstring.h> 2519304Speter#include <limits.h> 2619304Speter#include <stdio.h> 2719304Speter#include <string.h> 2819304Speter 2919304Speter#include "../common/common.h" 3019304Speter#include "vi.h" 3119304Speter 3219304Speter#ifdef VISIBLE_TAB_CHARS 3319304Speter#define TABCH '-' 3419304Speter#else 3519304Speter#define TABCH ' ' 3619304Speter#endif 3719304Speter 3819304Speter/* 3919304Speter * vs_line -- 4019304Speter * Update one line on the screen. 4119304Speter * 4219304Speter * PUBLIC: int vs_line __P((SCR *, SMAP *, size_t *, size_t *)); 4319304Speter */ 4419304Speterint 4519304Spetervs_line(sp, smp, yp, xp) 4619304Speter SCR *sp; 4719304Speter SMAP *smp; 4819304Speter size_t *xp, *yp; 4919304Speter{ 5019304Speter CHAR_T *kp; 5119304Speter GS *gp; 5219304Speter SMAP *tsmp; 5319304Speter size_t chlen, cno_cnt, cols_per_screen, len, nlen; 5419304Speter size_t offset_in_char, offset_in_line, oldx, oldy; 5519304Speter size_t scno, skip_cols, skip_screens; 5690026Ssheldonh int ch, dne, is_cached, is_partial, is_tab, no_draw; 5719304Speter int list_tab, list_dollar; 5819304Speter char *p, *cbp, *ecbp, cbuf[128]; 5919304Speter 6019304Speter#if defined(DEBUG) && 0 6119304Speter TRACE(sp, "vs_line: row %u: line: %u off: %u\n", 6219304Speter smp - HMAP, smp->lno, smp->off); 6319304Speter#endif 6419304Speter /* 6519304Speter * If ex modifies the screen after ex output is already on the screen, 6619304Speter * don't touch it -- we'll get scrolling wrong, at best. 6719304Speter */ 6890026Ssheldonh no_draw = 0; 6919304Speter if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1) 7090026Ssheldonh no_draw = 1; 7119304Speter if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp)) 7290026Ssheldonh no_draw = 1; 7319304Speter 7419304Speter /* 7519304Speter * Assume that, if the cache entry for the line is filled in, the 7619304Speter * line is already on the screen, and all we need to do is return 7719304Speter * the cursor position. If the calling routine doesn't need the 7819304Speter * cursor position, we can just return. 7919304Speter */ 8019304Speter is_cached = SMAP_CACHE(smp); 8190026Ssheldonh if (yp == NULL && (is_cached || no_draw)) 8219304Speter return (0); 8319304Speter 8419304Speter /* 8519304Speter * A nasty side effect of this routine is that it returns the screen 8619304Speter * position for the "current" character. Not pretty, but this is the 8719304Speter * only routine that really knows what's out there. 8819304Speter * 8919304Speter * Move to the line. This routine can be called by vs_sm_position(), 9019304Speter * which uses it to fill in the cache entry so it can figure out what 9119304Speter * the real contents of the screen are. Because of this, we have to 9219304Speter * return to whereever we started from. 9319304Speter */ 9419304Speter gp = sp->gp; 9519304Speter (void)gp->scr_cursor(sp, &oldy, &oldx); 9619304Speter (void)gp->scr_move(sp, smp - HMAP, 0); 9719304Speter 9819304Speter /* Get the line. */ 9919304Speter dne = db_get(sp, smp->lno, 0, &p, &len); 10019304Speter 10119304Speter /* 10219304Speter * Special case if we're printing the info/mode line. Skip printing 10319304Speter * the leading number, as well as other minor setup. The only time 10419304Speter * this code paints the mode line is when the user is entering text 10519304Speter * for a ":" command, so we can put the code here instead of dealing 10619304Speter * with the empty line logic below. This is a kludge, but it's pretty 10719304Speter * much confined to this module. 10819304Speter * 10919304Speter * Set the number of columns for this screen. 11019304Speter * Set the number of chars or screens to skip until a character is to 11119304Speter * be displayed. 11219304Speter */ 11319304Speter cols_per_screen = sp->cols; 11419304Speter if (O_ISSET(sp, O_LEFTRIGHT)) { 11519304Speter skip_screens = 0; 11619304Speter skip_cols = smp->coff; 11719304Speter } else { 11819304Speter skip_screens = smp->soff - 1; 11919304Speter skip_cols = skip_screens * cols_per_screen; 12019304Speter } 12119304Speter 12219304Speter list_tab = O_ISSET(sp, O_LIST); 12319304Speter if (F_ISSET(sp, SC_TINPUT_INFO)) 12419304Speter list_dollar = 0; 12519304Speter else { 12619304Speter list_dollar = list_tab; 12719304Speter 12819304Speter /* 12919304Speter * If O_NUMBER is set, the line doesn't exist and it's line 13019304Speter * number 1, i.e., an empty file, display the line number. 13119304Speter * 13219304Speter * If O_NUMBER is set, the line exists and the first character 13319304Speter * on the screen is the first character in the line, display 13419304Speter * the line number. 13519304Speter * 13619304Speter * !!! 13719304Speter * If O_NUMBER set, decrement the number of columns in the 13819304Speter * first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The 13919304Speter * rest of the code expects this to reflect the number of 14019304Speter * columns in the first screen, regardless of the number of 14119304Speter * columns we're going to skip. 14219304Speter */ 14319304Speter if (O_ISSET(sp, O_NUMBER)) { 14419304Speter cols_per_screen -= O_NUMBER_LENGTH; 14519304Speter if ((!dne || smp->lno == 1) && skip_cols == 0) { 14638022Sbde nlen = snprintf(cbuf, sizeof(cbuf), 14738022Sbde O_NUMBER_FMT, (u_long)smp->lno); 14819304Speter (void)gp->scr_addstr(sp, cbuf, nlen); 14919304Speter } 15019304Speter } 15119304Speter } 15219304Speter 15319304Speter /* 15419304Speter * Special case non-existent lines and the first line of an empty 15519304Speter * file. In both cases, the cursor position is 0, but corrected 15619304Speter * as necessary for the O_NUMBER field, if it was displayed. 15719304Speter */ 15819304Speter if (dne || len == 0) { 15919304Speter /* Fill in the cursor. */ 16019304Speter if (yp != NULL && smp->lno == sp->lno) { 16119304Speter *yp = smp - HMAP; 16219304Speter *xp = sp->cols - cols_per_screen; 16319304Speter } 16419304Speter 16519304Speter /* If the line is on the screen, quit. */ 16690026Ssheldonh if (is_cached || no_draw) 16719304Speter goto ret1; 16819304Speter 16919304Speter /* Set line cache information. */ 17019304Speter smp->c_sboff = smp->c_eboff = 0; 17119304Speter smp->c_scoff = smp->c_eclen = 0; 17219304Speter 17319304Speter /* 17419304Speter * Lots of special cases for empty lines, but they only apply 17519304Speter * if we're displaying the first screen of the line. 17619304Speter */ 17719304Speter if (skip_cols == 0) 17819304Speter if (dne) { 17919304Speter if (smp->lno == 1) { 18019304Speter if (list_dollar) { 18119304Speter ch = '$'; 18219304Speter goto empty; 18319304Speter } 18419304Speter } else { 18519304Speter ch = '~'; 18619304Speter goto empty; 18719304Speter } 18819304Speter } else 18919304Speter if (list_dollar) { 19019304Speter ch = '$'; 19119304Speterempty: (void)gp->scr_addstr(sp, 19219304Speter KEY_NAME(sp, ch), KEY_LEN(sp, ch)); 19319304Speter } 19419304Speter 19519304Speter (void)gp->scr_clrtoeol(sp); 19619304Speter (void)gp->scr_move(sp, oldy, oldx); 19719304Speter return (0); 19819304Speter } 19919304Speter 20019304Speter /* 20119304Speter * If we just wrote this or a previous line, we cached the starting 20219304Speter * and ending positions of that line. The way it works is we keep 20319304Speter * information about the lines displayed in the SMAP. If we're 20419304Speter * painting the screen in the forward direction, this saves us from 20519304Speter * reformatting the physical line for every line on the screen. This 20619304Speter * wins big on binary files with 10K lines. 20719304Speter * 20819304Speter * Test for the first screen of the line, then the current screen line, 20919304Speter * then the line behind us, then do the hard work. Note, it doesn't 21019304Speter * do us any good to have a line in front of us -- it would be really 21119304Speter * hard to try and figure out tabs in the reverse direction, i.e. how 21219304Speter * many spaces a tab takes up in the reverse direction depends on 21319304Speter * what characters preceded it. 21419304Speter * 21519304Speter * Test for the first screen of the line. 21619304Speter */ 21719304Speter if (skip_cols == 0) { 21819304Speter smp->c_sboff = offset_in_line = 0; 21919304Speter smp->c_scoff = offset_in_char = 0; 22019304Speter p = &p[offset_in_line]; 22119304Speter goto display; 22219304Speter } 22319304Speter 22419304Speter /* Test to see if we've seen this exact line before. */ 22519304Speter if (is_cached) { 22619304Speter offset_in_line = smp->c_sboff; 22719304Speter offset_in_char = smp->c_scoff; 22819304Speter p = &p[offset_in_line]; 22919304Speter 23019304Speter /* Set cols_per_screen to 2nd and later line length. */ 23119304Speter if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) 23219304Speter cols_per_screen = sp->cols; 23319304Speter goto display; 23419304Speter } 23519304Speter 23619304Speter /* Test to see if we saw an earlier part of this line before. */ 23719304Speter if (smp != HMAP && 23819304Speter SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) { 23919304Speter if (tsmp->c_eclen != tsmp->c_ecsize) { 24019304Speter offset_in_line = tsmp->c_eboff; 24119304Speter offset_in_char = tsmp->c_eclen; 24219304Speter } else { 24319304Speter offset_in_line = tsmp->c_eboff + 1; 24419304Speter offset_in_char = 0; 24519304Speter } 24619304Speter 24719304Speter /* Put starting info for this line in the cache. */ 24819304Speter smp->c_sboff = offset_in_line; 24919304Speter smp->c_scoff = offset_in_char; 25019304Speter p = &p[offset_in_line]; 25119304Speter 25219304Speter /* Set cols_per_screen to 2nd and later line length. */ 25319304Speter if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) 25419304Speter cols_per_screen = sp->cols; 25519304Speter goto display; 25619304Speter } 25719304Speter 25819304Speter scno = 0; 25919304Speter offset_in_line = 0; 26019304Speter offset_in_char = 0; 26119304Speter 26219304Speter /* Do it the hard way, for leftright scrolling screens. */ 26319304Speter if (O_ISSET(sp, O_LEFTRIGHT)) { 26419304Speter for (; offset_in_line < len; ++offset_in_line) { 26519304Speter chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ? 26619304Speter TAB_OFF(scno) : KEY_LEN(sp, ch); 26719304Speter if ((scno += chlen) >= skip_cols) 26819304Speter break; 26919304Speter } 27019304Speter 27119304Speter /* Set cols_per_screen to 2nd and later line length. */ 27219304Speter cols_per_screen = sp->cols; 27319304Speter 27419304Speter /* Put starting info for this line in the cache. */ 27590023Ssheldonh if (offset_in_line >= len) { 27619304Speter smp->c_sboff = offset_in_line; 27790023Ssheldonh smp->c_scoff = 255; 27890023Ssheldonh } else if (scno != skip_cols) { 27990023Ssheldonh smp->c_sboff = offset_in_line; 28019304Speter smp->c_scoff = 28119304Speter offset_in_char = chlen - (scno - skip_cols); 28219304Speter --p; 28319304Speter } else { 28419304Speter smp->c_sboff = ++offset_in_line; 28519304Speter smp->c_scoff = 0; 28619304Speter } 28719304Speter } 28819304Speter 28919304Speter /* Do it the hard way, for historic line-folding screens. */ 29019304Speter else { 29119304Speter for (; offset_in_line < len; ++offset_in_line) { 29219304Speter chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ? 29319304Speter TAB_OFF(scno) : KEY_LEN(sp, ch); 29419304Speter if ((scno += chlen) < cols_per_screen) 29519304Speter continue; 29619304Speter scno -= cols_per_screen; 29719304Speter 29819304Speter /* Set cols_per_screen to 2nd and later line length. */ 29919304Speter cols_per_screen = sp->cols; 30019304Speter 30119304Speter /* 30219304Speter * If crossed the last skipped screen boundary, start 30319304Speter * displaying the characters. 30419304Speter */ 30519304Speter if (--skip_screens == 0) 30619304Speter break; 30719304Speter } 30819304Speter 30919304Speter /* Put starting info for this line in the cache. */ 31019304Speter if (scno != 0) { 31119304Speter smp->c_sboff = offset_in_line; 31219304Speter smp->c_scoff = offset_in_char = chlen - scno; 31319304Speter --p; 31419304Speter } else { 31519304Speter smp->c_sboff = ++offset_in_line; 31619304Speter smp->c_scoff = 0; 31719304Speter } 31819304Speter } 31919304Speter 32019304Speterdisplay: 32119304Speter /* 32219304Speter * Set the number of characters to skip before reaching the cursor 32319304Speter * character. Offset by 1 and use 0 as a flag value. Vs_line is 32419304Speter * called repeatedly with a valid pointer to a cursor position. 32519304Speter * Don't fill anything in unless it's the right line and the right 32619304Speter * character, and the right part of the character... 32719304Speter */ 32819304Speter if (yp == NULL || 32919304Speter smp->lno != sp->lno || sp->cno < offset_in_line || 33019304Speter offset_in_line + cols_per_screen < sp->cno) { 33119304Speter cno_cnt = 0; 33219304Speter /* If the line is on the screen, quit. */ 33390026Ssheldonh if (is_cached || no_draw) 33419304Speter goto ret1; 33519304Speter } else 33619304Speter cno_cnt = (sp->cno - offset_in_line) + 1; 33719304Speter 33819304Speter /* This is the loop that actually displays characters. */ 33919304Speter ecbp = (cbp = cbuf) + sizeof(cbuf) - 1; 34019304Speter for (is_partial = 0, scno = 0; 34119304Speter offset_in_line < len; ++offset_in_line, offset_in_char = 0) { 34219304Speter if ((ch = *(u_char *)p++) == '\t' && !list_tab) { 34319304Speter scno += chlen = TAB_OFF(scno) - offset_in_char; 34419304Speter is_tab = 1; 34519304Speter } else { 34619304Speter scno += chlen = KEY_LEN(sp, ch) - offset_in_char; 34719304Speter is_tab = 0; 34819304Speter } 34919304Speter 35019304Speter /* 35119304Speter * Only display up to the right-hand column. Set a flag if 35219304Speter * the entire character wasn't displayed for use in setting 35319304Speter * the cursor. If reached the end of the line, set the cache 35419304Speter * info for the screen. Don't worry about there not being 35519304Speter * characters to display on the next screen, its lno/off won't 35619304Speter * match up in that case. 35719304Speter */ 35819304Speter if (scno >= cols_per_screen) { 35919304Speter if (is_tab == 1) { 36019304Speter chlen -= scno - cols_per_screen; 36119304Speter smp->c_ecsize = smp->c_eclen = chlen; 36219304Speter scno = cols_per_screen; 36319304Speter } else { 36419304Speter smp->c_ecsize = chlen; 36519304Speter chlen -= scno - cols_per_screen; 36619304Speter smp->c_eclen = chlen; 36719304Speter 36819304Speter if (scno > cols_per_screen) 36919304Speter is_partial = 1; 37019304Speter } 37119304Speter smp->c_eboff = offset_in_line; 37219304Speter 37319304Speter /* Terminate the loop. */ 37419304Speter offset_in_line = len; 37519304Speter } 37619304Speter 37719304Speter /* 37819304Speter * If the caller wants the cursor value, and this was the 37919304Speter * cursor character, set the value. There are two ways to 38019304Speter * put the cursor on a character -- if it's normal display 38119304Speter * mode, it goes on the last column of the character. If 38219304Speter * it's input mode, it goes on the first. In normal mode, 38319304Speter * set the cursor only if the entire character was displayed. 38419304Speter */ 38519304Speter if (cno_cnt && 38619304Speter --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) { 38719304Speter *yp = smp - HMAP; 38819304Speter if (F_ISSET(sp, SC_TINPUT)) 38919304Speter *xp = scno - chlen; 39019304Speter else 39119304Speter *xp = scno - 1; 39219304Speter if (O_ISSET(sp, O_NUMBER) && 39319304Speter !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0) 39419304Speter *xp += O_NUMBER_LENGTH; 39519304Speter 39619304Speter /* If the line is on the screen, quit. */ 39790026Ssheldonh if (is_cached || no_draw) 39819304Speter goto ret1; 39919304Speter } 40019304Speter 40119304Speter /* If the line is on the screen, don't display anything. */ 40290026Ssheldonh if (is_cached || no_draw) 40319304Speter continue; 40419304Speter 40519304Speter#define FLUSH { \ 40619304Speter *cbp = '\0'; \ 40719304Speter (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \ 40819304Speter cbp = cbuf; \ 40919304Speter} 41019304Speter /* 41119304Speter * Display the character. We do tab expansion here because 41219304Speter * the screen interface doesn't have any way to set the tab 41319304Speter * length. Note, it's theoretically possible for chlen to 41419304Speter * be larger than cbuf, if the user set a impossibly large 41519304Speter * tabstop. 41619304Speter */ 41719304Speter if (is_tab) 41819304Speter while (chlen--) { 41919304Speter if (cbp >= ecbp) 42019304Speter FLUSH; 42119304Speter *cbp++ = TABCH; 42219304Speter } 42319304Speter else { 42419304Speter if (cbp + chlen >= ecbp) 42519304Speter FLUSH; 42619304Speter for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;) 42719304Speter *cbp++ = *kp++; 42819304Speter } 42919304Speter } 43019304Speter 43119304Speter if (scno < cols_per_screen) { 43219304Speter /* If didn't paint the whole line, update the cache. */ 43319304Speter smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch); 43419304Speter smp->c_eboff = len - 1; 43519304Speter 43619304Speter /* 43719304Speter * If not the info/mode line, and O_LIST set, and at the 43819304Speter * end of the line, and the line ended on this screen, 43919304Speter * add a trailing $. 44019304Speter */ 44119304Speter if (list_dollar) { 44219304Speter ++scno; 44319304Speter 44419304Speter chlen = KEY_LEN(sp, '$'); 44519304Speter if (cbp + chlen >= ecbp) 44619304Speter FLUSH; 44719304Speter for (kp = KEY_NAME(sp, '$'); chlen--;) 44819304Speter *cbp++ = *kp++; 44919304Speter } 45019304Speter 45119304Speter /* If still didn't paint the whole line, clear the rest. */ 45219304Speter if (scno < cols_per_screen) 45319304Speter (void)gp->scr_clrtoeol(sp); 45419304Speter } 45519304Speter 45619304Speter /* Flush any buffered characters. */ 45719304Speter if (cbp > cbuf) 45819304Speter FLUSH; 45919304Speter 46019304Speterret1: (void)gp->scr_move(sp, oldy, oldx); 46119304Speter return (0); 46219304Speter} 46319304Speter 46419304Speter/* 46519304Speter * vs_number -- 46619304Speter * Repaint the numbers on all the lines. 46719304Speter * 46819304Speter * PUBLIC: int vs_number __P((SCR *)); 46919304Speter */ 47019304Speterint 47119304Spetervs_number(sp) 47219304Speter SCR *sp; 47319304Speter{ 47419304Speter GS *gp; 47519304Speter SMAP *smp; 47619304Speter VI_PRIVATE *vip; 47719304Speter size_t len, oldy, oldx; 47819304Speter int exist; 47919304Speter char nbuf[10]; 48019304Speter 48119304Speter gp = sp->gp; 48219304Speter vip = VIP(sp); 48319304Speter 48419304Speter /* No reason to do anything if we're in input mode on the info line. */ 48519304Speter if (F_ISSET(sp, SC_TINPUT_INFO)) 48619304Speter return (0); 48719304Speter 48819304Speter /* 48919304Speter * Try and avoid getting the last line in the file, by getting the 49019304Speter * line after the last line in the screen -- if it exists, we know 49119304Speter * we have to to number all the lines in the screen. Get the one 49219304Speter * after the last instead of the last, so that the info line doesn't 49319304Speter * fool us. (The problem is that file_lline will lie, and tell us 49419304Speter * that the info line is the last line in the file.) If that test 49519304Speter * fails, we have to check each line for existence. 49619304Speter */ 49719304Speter exist = db_exist(sp, TMAP->lno + 1); 49819304Speter 49919304Speter (void)gp->scr_cursor(sp, &oldy, &oldx); 50019304Speter for (smp = HMAP; smp <= TMAP; ++smp) { 50119304Speter /* Numbers are only displayed for the first screen line. */ 50219304Speter if (O_ISSET(sp, O_LEFTRIGHT)) { 50319304Speter if (smp->coff != 0) 50419304Speter continue; 50519304Speter } else 50619304Speter if (smp->soff != 1) 50719304Speter continue; 50819304Speter 50919304Speter /* 51019304Speter * The first line of an empty file gets numbered, otherwise 51119304Speter * number any existing line. 51219304Speter */ 51319304Speter if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno)) 51419304Speter break; 51519304Speter 51619304Speter (void)gp->scr_move(sp, smp - HMAP, 0); 51738022Sbde len = snprintf(nbuf, sizeof(nbuf), 51838022Sbde O_NUMBER_FMT, (u_long)smp->lno); 51919304Speter (void)gp->scr_addstr(sp, nbuf, len); 52019304Speter } 52119304Speter (void)gp->scr_move(sp, oldy, oldx); 52219304Speter return (0); 52319304Speter} 524