710 lines
23 KiB
C
710 lines
23 KiB
C
/*
|
|
*
|
|
* Routines to manipulate the screen representations.
|
|
*/
|
|
|
|
#include "stevie.h"
|
|
|
|
/*
|
|
* This gets set if we ignored an update request while input was pending.
|
|
* We check this when the input is drained to see if the screen should be
|
|
* updated.
|
|
*/
|
|
bool_t need_redraw = FALSE;
|
|
|
|
/*
|
|
* The following variable is set (in filetonext) to the number of physical
|
|
* lines taken by the line the cursor is on. We use this to avoid extra
|
|
* calls to plines(). The optimized routines lfiletonext() and lnexttoscreen()
|
|
* make sure that the size of the cursor line hasn't changed. If so, lines
|
|
* below the cursor will move up or down and we need to call the routines
|
|
* filetonext() and nexttoscreen() to examine the entire screen.
|
|
*/
|
|
static int Cline_size; /* size (in rows) of the cursor line */
|
|
static int Cline_row; /* starting row of the cursor line */
|
|
|
|
static char *mkline(); /* calculate line string for "number" mode */
|
|
|
|
/*
|
|
* filetonext()
|
|
*
|
|
* Based on the current value of Topchar, transfer a screenfull of
|
|
* stuff from Filemem to Nextscreen, and update Botchar.
|
|
*/
|
|
|
|
static void
|
|
filetonext()
|
|
{
|
|
register int row, col;
|
|
register char *screenp = Nextscreen;
|
|
LNPTR memp;
|
|
LNPTR save; /* save pos. in case line won't fit */
|
|
register char *endscreen;
|
|
register char *nextrow;
|
|
char extra[16];
|
|
int nextra = 0;
|
|
register int c;
|
|
int n;
|
|
bool_t done; /* if TRUE, we hit the end of the file */
|
|
bool_t didline; /* if TRUE, we finished the last line */
|
|
int srow; /* starting row of the current line */
|
|
int lno; /* number of the line we're doing */
|
|
int coff; /* column offset */
|
|
|
|
coff = P(P_NU) ? 8 : 0;
|
|
|
|
save = memp = *Topchar;
|
|
|
|
if (P(P_NU))
|
|
lno = cntllines(Filemem, Topchar);
|
|
|
|
/*
|
|
* The number of rows shown is Rows-1.
|
|
* The last line is the status/command line.
|
|
*/
|
|
endscreen = &screenp[(Rows-1)*Columns];
|
|
|
|
done = didline = FALSE;
|
|
srow = row = col = 0;
|
|
/*
|
|
* We go one past the end of the screen so we can find out if the
|
|
* last line fit on the screen or not.
|
|
*/
|
|
while ( screenp <= endscreen && !done) {
|
|
|
|
|
|
if (P(P_NU) && col == 0 && memp.index == 0) {
|
|
strcpy(extra, mkline(lno++));
|
|
nextra = 8;
|
|
}
|
|
|
|
/* Get the next character to put on the screen. */
|
|
|
|
/* The 'extra' array contains the extra stuff that is */
|
|
/* inserted to represent special characters (tabs, and */
|
|
/* other non-printable stuff. The order in the 'extra' */
|
|
/* array is reversed. */
|
|
|
|
if ( nextra > 0 )
|
|
c = extra[--nextra];
|
|
else {
|
|
c = (unsigned)(0xff & gchar(&memp));
|
|
if (inc(&memp) == -1)
|
|
done = 1;
|
|
/* when getting a character from the file, we */
|
|
/* may have to turn it into something else on */
|
|
/* the way to putting it into 'Nextscreen'. */
|
|
if ( c == TAB && !P(P_LS) ) {
|
|
strcpy(extra," ");
|
|
/* tab amount depends on current column */
|
|
nextra = ((P(P_TS)-1) - (col - coff)%P(P_TS));
|
|
c = ' ';
|
|
}
|
|
else if ( c == NUL && P(P_LS) ) {
|
|
extra[0] = NUL;
|
|
nextra = 1;
|
|
c = '$';
|
|
} else if ((n = chars[c].ch_size) > 1 ) {
|
|
char *p;
|
|
nextra = 0;
|
|
p = chars[c].ch_str;
|
|
/* copy 'ch-str'ing into 'extra' in reverse */
|
|
while ( n > 1 )
|
|
extra[nextra++] = p[--n];
|
|
c = p[0];
|
|
}
|
|
}
|
|
|
|
if (screenp == endscreen) {
|
|
/*
|
|
* We're one past the end of the screen. If the
|
|
* current character is null, then we really did
|
|
* finish, so set didline = TRUE. In either case,
|
|
* break out because we're done.
|
|
*/
|
|
dec(&memp);
|
|
if (memp.index != 0 && c == NUL) {
|
|
didline = TRUE;
|
|
inc(&memp);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( c == NUL ) {
|
|
srow = ++row;
|
|
/*
|
|
* Save this position in case the next line won't
|
|
* fit on the screen completely.
|
|
*/
|
|
save = memp;
|
|
/* get pointer to start of next row */
|
|
nextrow = &Nextscreen[row*Columns];
|
|
/* blank out the rest of this row */
|
|
while ( screenp != nextrow )
|
|
*screenp++ = ' ';
|
|
col = 0;
|
|
continue;
|
|
}
|
|
if ( col >= Columns ) {
|
|
row++;
|
|
col = 0;
|
|
}
|
|
/* store the character in Nextscreen */
|
|
*screenp++ = (char)c;
|
|
col++;
|
|
}
|
|
/*
|
|
* If we didn't hit the end of the file, and we didn't finish
|
|
* the last line we were working on, then the line didn't fit.
|
|
*/
|
|
if (!done && !didline) {
|
|
/*
|
|
* Clear the rest of the screen and mark the unused lines.
|
|
*/
|
|
screenp = &Nextscreen[srow * Columns];
|
|
while (screenp < endscreen)
|
|
*screenp++ = ' ';
|
|
for (; srow < (Rows-1) ;srow++)
|
|
Nextscreen[srow * Columns] = '@';
|
|
*Botchar = save;
|
|
return;
|
|
}
|
|
/* make sure the rest of the screen is blank */
|
|
while ( screenp < endscreen )
|
|
*screenp++ = ' ';
|
|
/* put '~'s on rows that aren't part of the file. */
|
|
if ( col != 0 )
|
|
row++;
|
|
while ( row < Rows ) {
|
|
Nextscreen[row*Columns] = '~';
|
|
row++;
|
|
}
|
|
if (done) /* we hit the end of the file */
|
|
*Botchar = *Fileend;
|
|
else
|
|
*Botchar = memp; /* FIX - prev? */
|
|
}
|
|
|
|
/*
|
|
* nexttoscreen
|
|
*
|
|
* Transfer the contents of Nextscreen to the screen, using Realscreen
|
|
* to avoid unnecessary output.
|
|
*/
|
|
static void
|
|
nexttoscreen()
|
|
{
|
|
register char *np = Nextscreen;
|
|
register char *rp = Realscreen;
|
|
register char *endscreen;
|
|
register int row = 0, col = 0;
|
|
int gorow = -1, gocol = -1;
|
|
|
|
if (anyinput()) {
|
|
need_redraw = TRUE;
|
|
return;
|
|
}
|
|
|
|
endscreen = &np[(Rows-1)*Columns];
|
|
|
|
InvisibleCursor();
|
|
|
|
for ( ; np < endscreen ; np++,rp++ ) {
|
|
/* If desired screen (contents of Nextscreen) does not */
|
|
/* match what's really there, put it there. */
|
|
if ( *np != *rp ) {
|
|
/* if we are positioned at the right place, */
|
|
/* we don't have to use windgoto(). */
|
|
if (gocol != col || gorow != row) {
|
|
/*
|
|
* If we're just off by one, don't send
|
|
* an entire esc. seq. (this happens a lot!)
|
|
*/
|
|
if (gorow == row && gocol+1 == col) {
|
|
outchar(*(np-1));
|
|
gocol++;
|
|
} else
|
|
windgoto(gorow=row,gocol=col);
|
|
}
|
|
outchar(*rp = *np);
|
|
gocol++;
|
|
}
|
|
if ( ++col >= Columns ) {
|
|
col = 0;
|
|
row++;
|
|
}
|
|
}
|
|
VisibleCursor();
|
|
}
|
|
|
|
/*
|
|
* lfiletonext() - like filetonext() but only for cursor line
|
|
*
|
|
* Returns true if the size of the cursor line (in rows) hasn't changed.
|
|
* This determines whether or not we need to call filetonext() to examine
|
|
* the entire screen for changes.
|
|
*/
|
|
static bool_t
|
|
lfiletonext()
|
|
{
|
|
register int row, col;
|
|
register char *screenp;
|
|
LNPTR memp;
|
|
register char *nextrow;
|
|
char extra[16];
|
|
int nextra = 0;
|
|
register int c;
|
|
int n;
|
|
bool_t eof;
|
|
int lno; /* number of the line we're doing */
|
|
int coff; /* column offset */
|
|
|
|
coff = P(P_NU) ? 8 : 0;
|
|
|
|
/*
|
|
* This should be done more efficiently.
|
|
*/
|
|
if (P(P_NU))
|
|
lno = cntllines(Filemem, Curschar);
|
|
|
|
screenp = Nextscreen + (Cline_row * Columns);
|
|
|
|
memp = *Curschar;
|
|
memp.index = 0;
|
|
|
|
eof = FALSE;
|
|
col = 0;
|
|
row = Cline_row;
|
|
|
|
while (!eof) {
|
|
|
|
if (P(P_NU) && col == 0 && memp.index == 0) {
|
|
strcpy(extra, mkline(lno));
|
|
nextra = 8;
|
|
}
|
|
|
|
/* Get the next character to put on the screen. */
|
|
|
|
/* The 'extra' array contains the extra stuff that is */
|
|
/* inserted to represent special characters (tabs, and */
|
|
/* other non-printable stuff. The order in the 'extra' */
|
|
/* array is reversed. */
|
|
|
|
if ( nextra > 0 )
|
|
c = extra[--nextra];
|
|
else {
|
|
c = (unsigned)(0xff & gchar(&memp));
|
|
if (inc(&memp) == -1)
|
|
eof = TRUE;
|
|
/* when getting a character from the file, we */
|
|
/* may have to turn it into something else on */
|
|
/* the way to putting it into 'Nextscreen'. */
|
|
if ( c == TAB && !P(P_LS) ) {
|
|
strcpy(extra," ");
|
|
/* tab amount depends on current column */
|
|
nextra = ((P(P_TS)-1) - (col - coff)%P(P_TS));
|
|
c = ' ';
|
|
} else if ( c == NUL && P(P_LS) ) {
|
|
extra[0] = NUL;
|
|
nextra = 1;
|
|
c = '$';
|
|
} else if (c != NUL && (n=chars[c].ch_size) > 1 )
|
|
{
|
|
char *p;
|
|
nextra = 0;
|
|
p = chars[c].ch_str;
|
|
/* copy 'ch-str'ing into 'extra' in reverse */
|
|
while ( n > 1 )
|
|
extra[nextra++] = p[--n];
|
|
c = p[0];
|
|
}
|
|
}
|
|
|
|
if ( c == NUL ) {
|
|
row++;
|
|
/* get pointer to start of next row */
|
|
nextrow = &Nextscreen[row*Columns];
|
|
/* blank out the rest of this row */
|
|
while ( screenp != nextrow )
|
|
*screenp++ = ' ';
|
|
col = 0;
|
|
break;
|
|
}
|
|
|
|
if ( col >= Columns ) {
|
|
row++;
|
|
col = 0;
|
|
}
|
|
/* store the character in Nextscreen */
|
|
*screenp++ = (char)c;
|
|
col++;
|
|
}
|
|
return ((row - Cline_row) == Cline_size);
|
|
}
|
|
|
|
/*
|
|
* lnexttoscreen
|
|
*
|
|
* Like nexttoscreen() but only for the cursor line.
|
|
*/
|
|
static void
|
|
lnexttoscreen()
|
|
{
|
|
register char *np = Nextscreen + (Cline_row * Columns);
|
|
register char *rp = Realscreen + (Cline_row * Columns);
|
|
register char *endline;
|
|
register int row, col;
|
|
int gorow = -1, gocol = -1;
|
|
|
|
if (anyinput()) {
|
|
need_redraw = TRUE;
|
|
return;
|
|
}
|
|
|
|
endline = np + (Cline_size * Columns);
|
|
|
|
row = Cline_row;
|
|
col = 0;
|
|
|
|
InvisibleCursor();
|
|
|
|
for ( ; np < endline ; np++,rp++ ) {
|
|
/* If desired screen (contents of Nextscreen) does not */
|
|
/* match what's really there, put it there. */
|
|
if ( *np != *rp ) {
|
|
/* if we are positioned at the right place, */
|
|
/* we don't have to use windgoto(). */
|
|
if (gocol != col || gorow != row) {
|
|
/*
|
|
* If we're just off by one, don't send
|
|
* an entire esc. seq. (this happens a lot!)
|
|
*/
|
|
if (gorow == row && gocol+1 == col) {
|
|
outchar(*(np-1));
|
|
gocol++;
|
|
} else
|
|
windgoto(gorow=row,gocol=col);
|
|
}
|
|
outchar(*rp = *np);
|
|
gocol++;
|
|
}
|
|
if ( ++col >= Columns ) {
|
|
col = 0;
|
|
row++;
|
|
}
|
|
}
|
|
VisibleCursor();
|
|
}
|
|
|
|
static char *
|
|
mkline(n)
|
|
register int n;
|
|
{
|
|
static char lbuf[9];
|
|
register int i = 2;
|
|
|
|
strcpy(lbuf, " ");
|
|
|
|
lbuf[i++] = (char)((n % 10) + '0');
|
|
n /= 10;
|
|
if (n != 0) {
|
|
lbuf[i++] = (char)((n % 10) + '0');
|
|
n /= 10;
|
|
}
|
|
if (n != 0) {
|
|
lbuf[i++] = (char)((n % 10) + '0');
|
|
n /= 10;
|
|
}
|
|
if (n != 0) {
|
|
lbuf[i++] = (char)((n % 10) + '0');
|
|
n /= 10;
|
|
}
|
|
if (n != 0) {
|
|
lbuf[i++] = (char)((n % 10) + '0');
|
|
n /= 10;
|
|
}
|
|
return lbuf;
|
|
}
|
|
|
|
/*
|
|
* updateline() - update the line the cursor is on
|
|
*
|
|
* Updateline() is called after changes that only affect the line that
|
|
* the cursor is on. This improves performance tremendously for normal
|
|
* insert mode operation. The only thing we have to watch for is when
|
|
* the cursor line grows or shrinks around a row boundary. This means
|
|
* we have to repaint other parts of the screen appropriately. If
|
|
* lfiletonext() returns FALSE, the size of the cursor line (in rows)
|
|
* has changed and we have to call updatescreen() to do a complete job.
|
|
*/
|
|
void
|
|
updateline()
|
|
{
|
|
if (!lfiletonext())
|
|
updatescreen(); /* bag it, do the whole screen */
|
|
else
|
|
lnexttoscreen();
|
|
}
|
|
|
|
void
|
|
updatescreen()
|
|
{
|
|
extern bool_t interactive;
|
|
|
|
if (interactive) {
|
|
filetonext();
|
|
nexttoscreen();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* prt_line() - print the given line
|
|
*/
|
|
void
|
|
prt_line(s)
|
|
char *s;
|
|
{
|
|
register int si = 0;
|
|
register int c;
|
|
register int col = 0;
|
|
|
|
char extra[16];
|
|
int nextra = 0;
|
|
int n;
|
|
|
|
for (;;) {
|
|
|
|
if ( nextra > 0 )
|
|
c = extra[--nextra];
|
|
else {
|
|
c = s[si++];
|
|
if ( c == TAB && !P(P_LS) ) {
|
|
strcpy(extra, " ");
|
|
/* tab amount depends on current column */
|
|
nextra = (P(P_TS) - 1) - col%P(P_TS);
|
|
c = ' ';
|
|
} else if ( c == NUL && P(P_LS) ) {
|
|
extra[0] = NUL;
|
|
nextra = 1;
|
|
c = '$';
|
|
} else if ( c != NUL && (n=chars[c].ch_size) > 1 ) {
|
|
char *p;
|
|
|
|
nextra = 0;
|
|
p = chars[c].ch_str;
|
|
/* copy 'ch-str'ing into 'extra' in reverse */
|
|
while ( n > 1 )
|
|
extra[nextra++] = p[--n];
|
|
c = p[0];
|
|
}
|
|
}
|
|
|
|
if ( c == NUL )
|
|
break;
|
|
|
|
outchar(c);
|
|
col++;
|
|
}
|
|
}
|
|
|
|
void
|
|
screenclear()
|
|
{
|
|
register char *rp, *np;
|
|
register char *end;
|
|
|
|
ClearDisplay();
|
|
|
|
rp = Realscreen;
|
|
end = Realscreen + Rows * Columns;
|
|
np = Nextscreen;
|
|
|
|
/* blank out the stored screens */
|
|
while (rp != end)
|
|
*rp++ = *np++ = ' ';
|
|
}
|
|
|
|
void
|
|
cursupdate()
|
|
{
|
|
register LNPTR *p;
|
|
register int icnt, c, nlines;
|
|
register int i;
|
|
int didinc;
|
|
|
|
if (bufempty()) { /* special case - file is empty */
|
|
*Topchar = *Filemem;
|
|
*Curschar = *Filemem;
|
|
} else if ( LINEOF(Curschar) < LINEOF(Topchar) ) {
|
|
nlines = cntllines(Curschar,Topchar);
|
|
/* if the cursor is above the top of */
|
|
/* the screen, put it at the top of the screen.. */
|
|
*Topchar = *Curschar;
|
|
Topchar->index = 0;
|
|
/* ... and, if we weren't very close to begin with, */
|
|
/* we scroll so that the line is close to the middle. */
|
|
if ( nlines > Rows/3 ) {
|
|
for (i=0, p = Topchar; i < Rows/3 ;i++, *Topchar = *p)
|
|
if ((p = prevline(p)) == NULL)
|
|
break;
|
|
} else
|
|
s_ins(0, nlines-1);
|
|
updatescreen();
|
|
}
|
|
else if (LINEOF(Curschar) >= LINEOF(Botchar)) {
|
|
nlines = cntllines(Botchar,Curschar);
|
|
/* If the cursor is off the bottom of the screen, */
|
|
/* put it at the top of the screen.. */
|
|
/* ... and back up */
|
|
if ( nlines > Rows/3 ) {
|
|
p = Curschar;
|
|
for (i=0; i < (2*Rows)/3 ;i++)
|
|
if ((p = prevline(p)) == NULL)
|
|
break;
|
|
*Topchar = *p;
|
|
} else {
|
|
scrollup(nlines+1);
|
|
}
|
|
updatescreen();
|
|
}
|
|
|
|
Cursrow = Curscol = Cursvcol = 0;
|
|
for ( p=Topchar; p->linep != Curschar->linep ;p = nextline(p) )
|
|
Cursrow += plines(p);
|
|
|
|
Cline_row = Cursrow;
|
|
Cline_size = plines(p);
|
|
|
|
if (P(P_NU))
|
|
Curscol = 8;
|
|
|
|
for (i=0; i <= Curschar->index ;i++) {
|
|
c = Curschar->linep->s[i];
|
|
/* A tab gets expanded, depending on the current column */
|
|
if ( c == TAB && !P(P_LS) )
|
|
icnt = P(P_TS) - (Cursvcol % P(P_TS));
|
|
else
|
|
icnt = chars[(unsigned)(c & 0xff)].ch_size;
|
|
Curscol += icnt;
|
|
Cursvcol += icnt;
|
|
if ( Curscol >= Columns ) {
|
|
Curscol -= Columns;
|
|
Cursrow++;
|
|
didinc = TRUE;
|
|
}
|
|
else
|
|
didinc = FALSE;
|
|
}
|
|
if (didinc)
|
|
Cursrow--;
|
|
|
|
if (c == TAB && State == NORMAL && !P(P_LS)) {
|
|
Curscol--;
|
|
Cursvcol--;
|
|
} else {
|
|
Curscol -= icnt;
|
|
Cursvcol -= icnt;
|
|
}
|
|
if (Curscol < 0)
|
|
Curscol += Columns;
|
|
|
|
if (set_want_col) {
|
|
Curswant = Cursvcol;
|
|
set_want_col = FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The rest of the routines in this file perform screen manipulations.
|
|
* The given operation is performed physically on the screen. The
|
|
* corresponding change is also made to the internal screen image.
|
|
* In this way, the editor anticipates the effect of editing changes
|
|
* on the appearance of the screen. That way, when we call screenupdate
|
|
* a complete redraw isn't usually necessary. Another advantage is that
|
|
* we can keep adding code to anticipate screen changes, and in the
|
|
* meantime, everything still works.
|
|
*/
|
|
|
|
/*
|
|
* s_ins(row, nlines) - insert 'nlines' lines at 'row'
|
|
*/
|
|
void
|
|
s_ins(row, nlines)
|
|
int row;
|
|
int nlines;
|
|
{
|
|
register char *s, *d; /* src & dest for block copy */
|
|
register char *e; /* end point for copy */
|
|
|
|
SaveCursor();
|
|
|
|
// clip to screen
|
|
|
|
if(row <= Rows-2-nlines) {
|
|
Scroll(row,0,Rows-2-nlines,Columns-1,row+nlines,0);
|
|
EraseNLinesAtRow(nlines,row);
|
|
} else {
|
|
// just erase to end of screen
|
|
EraseNLinesAtRow(Rows-2-row+1,row);
|
|
}
|
|
|
|
windgoto(Rows-1, 0); /* delete any garbage that may have */
|
|
EraseLine();
|
|
RestoreCursor();
|
|
|
|
/*
|
|
* Now do a block move to update the internal screen image
|
|
*/
|
|
d = Realscreen + (Columns * (Rows - 1)) - 1;
|
|
s = d - (Columns * nlines);
|
|
e = Realscreen + (Columns * row);
|
|
|
|
while (s >= e)
|
|
*d-- = *s--;
|
|
|
|
/*
|
|
* Clear the inserted lines
|
|
*/
|
|
s = Realscreen + (row * Columns);
|
|
e = s + (nlines * Columns);
|
|
while (s < e)
|
|
*s++ = ' ';
|
|
}
|
|
|
|
/*
|
|
* s_del(row, nlines) - delete 'nlines' lines at 'row'
|
|
*/
|
|
void
|
|
s_del(row, nlines)
|
|
int row;
|
|
int nlines;
|
|
{
|
|
register char *s, *d, *e;
|
|
|
|
SaveCursor();
|
|
windgoto(Rows-1,0);
|
|
EraseLine(); // erase status line
|
|
windgoto(row,0);
|
|
|
|
if(row + nlines >= Rows - 1) { // more than a screenfull?
|
|
EraseNLinesAtRow(Rows-row-1,row);
|
|
} else {
|
|
Scroll(row+nlines,0,Rows-2,Columns-1,row,0);
|
|
EraseNLinesAtRow(nlines,Rows-nlines-1);
|
|
}
|
|
RestoreCursor();
|
|
|
|
/*
|
|
* do a block move to update the internal image
|
|
*/
|
|
d = Realscreen + (row * Columns);
|
|
s = d + (nlines * Columns);
|
|
e = Realscreen + ((Rows - 1) * Columns);
|
|
|
|
while (s < e)
|
|
*d++ = *s++;
|
|
|
|
while (d < e) /* clear the lines at the bottom */
|
|
*d++ = ' ';
|
|
}
|