/* ** screen.c ** ** screen handling functions for the screen editor. ** ** This file is part of mse, under GPLv3. */ #include "config.h" #include #include #include #include "se.h" #include "extern.h" #include "main.h" #include "misc.h" #include "scratch.h" #include "screen.h" #include "term.h" #include /* clrrow --- clear out all of a row except the bar */ void clrrow (int row) { loadstr ("", row, 0, BARCOL - 1); loadstr ("", row, BARCOL + 1, Ncols - 1); } /* display_message --- copy contents of message file to screen */ int display_message (FILE *fp) { char lin[MAXCOLS]; int row, col, eof, k; static char more[] = " M O R E T O C O M E "; if (Toprow > 0) { Topln = max (0, Topln - (Toprow - 1)); Toprow = 0; } eof = NO; for (row = Toprow; row <= Botrow; row++) { if (fgets (lin, Ncols, fp) == NULL) { eof = YES; break; } Toprow++; Topln++; lin[strlen (lin)] = EOS; /* remove '\n' */ loadstr (lin, row, 0, Ncols); } if (eof == NO) { k = (Ncols - strlen (more)) / 2; for (col = 0; col < k; col++) load ('*', row, col); for (k = 0; more[k] != EOS; k++, col++) load (more[k], row, col); for (; col < Ncols; col++) load ('*', row, col); Toprow++; Topln++; row++; } for (col = 0; col < Ncols; col++) load ('-', row, col); Toprow++; Topln++; if (Topln > Lastln) adjust_window (0, Lastln); if (Curln < Topln) Curln = Topln; First_affected = Topln; /* must rewrite the whole screen */ mesg ("Enter o- to restore display", HELP_MSG); if (eof == YES) return (EOF); return (OK); } static char smargin[] = "MARGIN"; /* getcmd --- read a line from the terminal (for se) */ void getcmd (char *lin, int col1, int *curpos, char *termchar) { int cursor, nlpos, prev_cursor, prev_status, status, scan_pos, tab_pos, first; char c; nlpos = strlen (lin) - 1; if (nlpos == -1 || lin[nlpos] != '\n') nlpos++; if (*curpos < 0) cursor = 0; else if (*curpos >= MAXLINE - 1) cursor = nlpos; else set_cursor (*curpos, &status, &cursor, &nlpos, lin); prev_cursor = cursor; watch (); /* display the time of day */ switch (Nchoise) { /* update the line number display */ case CURLINE: litnnum ("line ", Curln, LINE_MSG); break; case LASTLINE: litnnum ("$ = ", Lastln, LINE_MSG); break; case TOPLINE: litnnum ("# = ", Topln, LINE_MSG); break; default: mesg ("", LINE_MSG); break; } if (cursor + 1 < Warncol) /* erase the column display */ mesg ("", COL_MSG); *termchar = EOS; /* not yet terminated */ status = OK; prev_status = ERR; first = col1; while (*termchar == EOS) { lin[nlpos] = EOS; /* make sure the line has an EOS */ if (status == ERR) /* last iteration generated an error */ twrite (1, "\007", 1); /* Bell */ else if (prev_status == ERR) /* last one OK but one before had error */ mesg ("", CHAR_MSG); prev_status = status; status = OK; if (first > cursor) /* do horiz. scroll if needed */ first = cursor; else if (first < cursor - Ncols + POOPCOL + 1) first = cursor - Ncols + POOPCOL + 1; if (first == col1) /* indicate horizontally shifted line */ load ('|', Cmdrow, BARCOL); else if (first > col1) load ('<', Cmdrow, BARCOL); else if (first < col1) load ('>', Cmdrow, BARCOL); loadstr (&lin[first], Cmdrow, POOPCOL, Ncols - 1); if (cursor == Warncol - 1 && prev_cursor < Warncol - 1) twrite (1, "\007", 1); /* Bell */ if (cursor >= Warncol - 1) litnnum ("col ", cursor + 1, COL_MSG); else if (prev_cursor >= Warncol - 1) mesg ("", COL_MSG); position_cursor (Cmdrow, cursor + POOPCOL - first); prev_cursor = cursor; /* get a character */ switch (c = cread()) { /* branch on character value */ /* Literal characters: */ case ' ': case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case ';': case '<': case '=': case '>': case '?': case '@': case '[': case '\\': case ']': case '^': case '_': case '`': case '{': case '|': case '}': case '~': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case ESC: if (c == ESC) /* take next char literally */ { if ((c = cread()) == '\r') c = '\n'; } else if (Invert_case == YES && isalpha (c)) c ^= 040; /* toggle case (ASCII only) */ if (Insert_mode == YES) insert (1, &nlpos, &status, cursor, lin); else if (cursor >= MAXLINE - 2) { status = ERR; mesg (smargin, CHAR_MSG); } if (status != ERR) { lin[cursor++] = c; if (nlpos < cursor) nlpos = cursor; if (c == '\n') *termchar = CURSOR_SAME; } break; /* Leftward cursor functions: */ case CURSOR_LEFT: set_cursor (cursor - 1, &status, &cursor, &nlpos, lin); break; case TAB_LEFT: tab_pos = scan_tab (TAB_LEFT, cursor, &status); if (status != ERR) cursor = tab_pos; break; case SKIP_LEFT: cursor = 0; break; case SCAN_LEFT: scan_pos = scan_char (c, YES, cursor, nlpos, lin, &status); if (status != ERR) cursor = scan_pos; break; case G_LEFT: set_cursor (cursor - 1, &status, &cursor, &nlpos, lin); if (status != ERR) gobble (1, cursor, &status, &nlpos, lin); break; case G_TAB_LEFT: tab_pos = scan_tab (TAB_LEFT, cursor, &status); if (status != ERR) { cursor = tab_pos; gobble (prev_cursor - tab_pos, cursor, &status, &nlpos, lin); } break; case KILL_LEFT: cursor = 0; gobble (prev_cursor /* - 1 */, cursor, &status, &nlpos, lin); break; case G_SCAN_LEFT: scan_pos = scan_char (c, NO, cursor, nlpos, lin, &status); if (status != ERR) { cursor = scan_pos; gobble (prev_cursor - scan_pos, cursor, &status, &nlpos, lin); } break; /* Rightward cursor functions: */ case CURSOR_RIGHT: set_cursor (cursor + 1, &status, &cursor, &nlpos, lin); break; case TAB_RIGHT: tab_pos = scan_tab (TAB_RIGHT, cursor, &status); if (status != ERR) set_cursor (tab_pos, &status, &cursor, &nlpos, lin); break; case SKIP_RIGHT: cursor = nlpos; first = col1; break; case SCAN_RIGHT: scan_pos = scan_char (c, YES, cursor, nlpos, lin, &status); if (status != ERR) cursor = scan_pos; break; case G_RIGHT: gobble (1, cursor, &status, &nlpos, lin); break; case G_TAB_RIGHT: tab_pos = scan_tab (TAB_RIGHT, cursor, &status); if (status != ERR) gobble (tab_pos - cursor, cursor, &status, &nlpos, lin); break; case KILL_RIGHT: gobble (nlpos - cursor, cursor, &status, &nlpos, lin); break; case G_SCAN_RIGHT: scan_pos = scan_char (c, NO, cursor, nlpos, lin, &status); if (status != ERR) gobble (scan_pos - cursor, cursor, &status, &nlpos, lin); break; /* Line termination functions: */ case T_SKIP_RIGHT: cursor = nlpos; *termchar = c; break; case T_KILL_RIGHT: nlpos = cursor; *termchar = c; break; case FUNNY: case CURSOR_UP: case CURSOR_DOWN: *termchar = c; break; /* Insertion functions: */ case INSERT_BLANK: insert (1, &nlpos, &status, cursor, lin); if (status != ERR) lin[cursor] = ' '; break; case INSERT_NEWLINE: insert (1, &nlpos, &status, cursor, lin); if (status != ERR) { lin[cursor] = '\n'; *termchar = CURSOR_UP; } break; case INSERT_TAB: while (lin[cursor] == ' ' || lin[cursor] == '\t') cursor++; tab_pos = scan_tab (TAB_RIGHT, cursor, &status); if (status != ERR) insert (tab_pos - cursor, &nlpos, &status, cursor, lin); if (status != ERR) for (; cursor < tab_pos; cursor++) lin[cursor] = ' '; cursor = prev_cursor; break; /* Miscellanious control functions: */ case TOGGLE_INSERT_MODE: Insert_mode = YES + NO - Insert_mode; if (Insert_mode == NO) mesg ("", INS_MSG); else mesg ("INSERT", INS_MSG); break; case SHIFT_CASE: Invert_case = YES + NO - Invert_case; if (Invert_case == NO) mesg ("", CASE_MSG); else mesg ("CASE", CASE_MSG); break; case KILL_ALL: nlpos = cursor = 0; break; case FIX_SCREEN: restore_screen (); break; default: status = ERR; mesg ("WHA?", CHAR_MSG); break; } /* end switch */ } /* while (termchar == EOS) */ lin[nlpos] = '\n'; lin[nlpos + 1] = EOS; load ('|', Cmdrow, BARCOL); if (nlpos <= col1) loadstr ("", Cmdrow, POOPCOL, Ncols - 1); else loadstr (&lin[col1], Cmdrow, POOPCOL, Ncols - 1); if (cursor >= Warncol - 1) litnnum ("col ", cursor + 1, COL_MSG); else if (prev_cursor >= Warncol - 1) mesg ("", COL_MSG); *curpos = cursor; } /* cread --- read a character, handle interrupts and hangups */ /* ALL keyboard input should pass through this routine */ int cread (void) { char c; tflush (); if (Peekc) { c = Peekc; Peekc = EOS; } else { Reading = YES; if (read (0, &c, 1) == -1) { if (Hup_caught) { hangup (); } else /* must be a SIGINT at present */ { Int_caught = 0; Errcode = ENOERR; c = '\177'; } } Reading = NO; } return c; } /* scan_char --- scan current line for a character */ int scan_char (char chr, int wrap, int cursor, int nlpos, char *lin, int *status) { char c; int inc, scan_pos; c = cread (); if (Invert_case == YES && isalpha (c)) c ^= 040; /* toggle case */ if (c == chr) c = Last_char_scanned; Last_char_scanned = c; if (chr == SCAN_LEFT || chr == G_SCAN_LEFT) inc = -1; else inc = 1; /* NOTE: modify this code AT YOUR OWN RISK! */ scan_pos = cursor; do { if (scan_pos < 0) if (wrap == NO) break; else scan_pos = nlpos; else if (scan_pos > nlpos) if (wrap == NO) break; else scan_pos = 0; else scan_pos += inc; if (-1 < scan_pos && scan_pos < nlpos && lin[scan_pos] == c) break; } while (scan_pos != cursor); if (scan_pos < 0 || scan_pos >= nlpos || lin[scan_pos] != c) { *status = ERR; mesg ("NOCHAR", CHAR_MSG); } return (scan_pos); } /* scan_tab */ int scan_tab (char chr, int cursor, int *status) { int inc, tab_pos; if (chr == TAB_LEFT) { inc = -1; tab_pos = cursor - 1; } else { inc = 1; tab_pos = cursor + 1; } for (; -1 < tab_pos && tab_pos < MAXLINE; tab_pos += inc) { if (Tabstops[tab_pos] == YES) { break; } } if (tab_pos < 0 || tab_pos >= MAXLINE - 1) { *status = ERR; mesg (smargin, CHAR_MSG); } return (tab_pos); } /* gobble --- delete characters starting at the current cursor position */ void gobble (int len, int cursor, int *status, int *nlpos, char *lin) { if (cursor + len > *nlpos) { *status = ERR; mesg (smargin, CHAR_MSG); } else if (len > 0) { strcpy (&lin[cursor], &lin[cursor + len]); *nlpos -= len; } } /* insert --- shift characters right starting at the current cursor position */ void insert (int len, int *nlpos, int *status, int cursor, char *lin) { int fr, to; if (*nlpos + len >= MAXLINE - 1) { *status = ERR; mesg (smargin, CHAR_MSG); } else { for (fr = *nlpos, to = *nlpos + len; fr >= cursor; fr--, to--) lin[to] = lin[fr]; *nlpos += len; } } /* set_cursor --- move the cursor, extend line if necessary */ void set_cursor (int pos, int *status, int *cursor, int *nlpos, char *lin) { if (pos < 0 || pos >= MAXLINE - 1) { *status = ERR; mesg (smargin, CHAR_MSG); } else { *cursor = pos; for (; *nlpos < *cursor; (*nlpos)++) { lin[*nlpos] = ' '; } } } /* litnnum --- display literal and number in message area */ void litnnum (char *lit, int num, int type) { char msg[MAXLINE]; memset(msg, EOS, MAXLINE); snprintf (msg, MAXLINE-1, "%s%d", lit, num); mesg (msg, type); } /* load --- load a character onto the screen at given coordinates */ void load (char chr, int row, int col) { char ch; ch = (chr < ' ' || chr >= DEL) ? Unprintable : chr; if (row >= 0 && row < Nrows && col >= 0 && col < Ncols && Screen_image[row][col] != ch) { position_cursor (row, col); Screen_image[row][col] = ch; send (ch); } } /* loadstr --- load a string into a field of the screen */ void loadstr (char *str, int row, int stcol, int endcol) { char ch; int p, c, limit; if (row >= 0 && row < Nrows && stcol >= 0) { c = stcol; for (p = 0; str[p] != EOS; p++) { if (c >= Ncols) { break; } ch = str[p]; if (ch < ' ' || ch >= DEL) { ch = Unprintable; } if (Screen_image[row][c] != ch) { Screen_image[row][c] = ch; position_cursor (row, c); send (ch); } c++; } if (endcol >= Ncols - 1 && c < Ncols - 1) { clear_to_eol (row, c); } else { limit = (endcol < Ncols - 1) ? endcol : Ncols - 1; for (; c <= limit; c++) { if (Screen_image[row][c] != ' ') { Screen_image[row][c] = ' '; position_cursor (row, c); send (' '); } } } } } /* mesg --- display a message in the status row */ void mesg (char *s, int t) { int col, need, c, first, last; for (first = 0; first < Ncols; first++) { if (Msgalloc[first] == t) { break; } } for (last = first; last < Ncols; last++) { if (Msgalloc[last] != t) { break; } Msgalloc[last] = NOMSG; } for (; first > 0 && Msgalloc[first - 1] == NOMSG; first--) { ; } need = strlen (s) + 2; /* for two blanks */ if (need > 2) /* non-empty message */ { if (need <= last - first) /* fits in previous slot */ col = first; /* keep it there */ else /* needs a new slot */ for (col = 0; col < Ncols - 1; col = c) { while (col < Ncols - 1 && Msgalloc[col] != NOMSG) col++; for (c = col; Msgalloc[c] == NOMSG; c++) if (c >= Ncols - 1) break; if (c - col >= need) break; } if (col + need >= Ncols) /* have to collect garbage */ { col = 0; for (c = 0; c < Ncols; c++) if (Msgalloc[c] != NOMSG) { load (Screen_image[Nrows - 1][c], Nrows - 1, col); Msgalloc[col] = Msgalloc[c]; col++; } for (c = col; c < Ncols; c++) Msgalloc[c] = NOMSG; } load (' ', Nrows - 1, col); if (t == REMARK_MSG) brighton (); /* reverse video or highlight - on */ loadstr (s, Nrows - 1, col + 1, 0); if (t == REMARK_MSG) brightoff (); /* turn back off */ load (' ', Nrows - 1, col + need - 1); last = col + need - 1; if (last > Ncols - 1) last = Ncols - 1; for (c = col; c <= last; c++) Msgalloc[c] = t; } for (col = 0; col < Ncols; col++) if (Msgalloc[col] == NOMSG) load ('.', Nrows - 1, col); tflush (); } /* prompt --- load str into margin of command line */ void prompt (char *str) { loadstr (str, Cmdrow, 0, BARCOL - 1); tflush (); } /* restore_screen --- screen has been garbaged; fix it */ void restore_screen (void) { int row, col; clrscreen (); for (row = 0; row < Nrows && ! intrpt (); row++) { for (col = 0; col < Ncols; col++) { if (Screen_image[row][col] != ' ') { position_cursor (row, col); send (Screen_image[row][col]); } } } remark (""); /* get rid of 'type control-q....' */ } /* saynum --- display a number in the message area */ void saynum (int n) { char s[MAXLINE]; memset(s, EOS, MAXLINE); snprintf (s, MAXLINE-1, "%d", n); remark (s); } /* updscreen --- update screen from edit buffer */ void updscreen (void) { char abs_lineno[10], rel_lineno[10]; int line, row; int i; LINEDESC *k; memset(abs_lineno, EOS, 10); memset(rel_lineno, EOS, 10); fixscreen (); line = Topln; k = getind (line); for (row = Toprow; row <= Botrow; row++) { if (line > Lastln || line < 1) { loadstr ("", row, 0, BARCOL - 1); load ('|', row, BARCOL); loadstr ("", row, BARCOL + 1, Ncols - 1); } else { if (line == Curln) loadstr (". ->", row, 0, NAMECOL - 1); else if (line == 1) loadstr ("1", row, 0, NAMECOL - 1); else if (line == Lastln) loadstr ("$", row, 0, NAMECOL - 1); else if (Absnos == NO && row <= 25) { rel_lineno[0] = Rel_a + row; rel_lineno[1] = EOS; loadstr (rel_lineno, row, 0, NAMECOL - 1); } else { snprintf (abs_lineno, 10-1, "%d", line); loadstr (abs_lineno, row, 0, NAMECOL - 1); } load ((char) k->Markname, row, NAMECOL); load ('|', row, BARCOL); if (line >= First_affected) { gtxt (k); if (Firstcol >= k->Lineleng) loadstr ("", row, POOPCOL, Ncols - 1); else loadstr (&Txt[Firstcol], row, POOPCOL, Ncols - 1); } } line++; k = NEXTLINE(k); } First_affected = Lastln; Sctop = Topln; Sclen = Botrow - Toprow + 1; for (i = 0; i < Sclen; i++) Scline[i] = Sctop + i <= Lastln ? i : -1; } /* warn_deleted --- indicate which rows on screen are no longer valid */ void warn_deleted (int from, int to) { int row; for (row = Toprow; row <= Botrow; row++) { if (Topln + row - Toprow >= from && Topln + row - Toprow <= to) { loadstr ("gone", row, 0, BARCOL - 1); } } } /* watch --- keep time displayed on screen for users without watches */ void watch (void) { time_t clock; struct tm *now; char face[10]; memset(face, EOS, 10); time (&clock); now = localtime (&clock); snprintf (face, 10 - 1, "%02d:%02d", now->tm_hour, now->tm_min); mesg (face, TIME_MSG); } /* adjust_window --- position window to include lines 'from' through 'to' */ void adjust_window (int from, int to) { int i, l1, l2, hw; /* see if the whole range of lines is on the screen */ if (from < Topln || to > Topln + (Botrow - Toprow)) { /* find the first and last lines that are on the screen */ for (i = 0; i < Sclen && Scline[i] == -1; i++) { ; } l1 = i < Sclen ? Scline[i] + Sctop : Lastln + 1; for (i = Sclen - 1; i >= 0 && Scline[i] == -1; i--) { ; } l2 = i >= 0 ? Scline[i] + Sctop : 0; /* see if we have hardware line insert/delete */ hw = hwinsdel(); /* now find the best place to move the screen */ if (to - from > Botrow - Toprow) Topln = to - (Botrow - Toprow); /* put last part on screen */ else if (hw && from >= l1 && to <= l2) Topln = (l1 + l2 + Toprow - Botrow) / 2;/* center l1 through l2 */ else if (hw && from < l1 && l1 - from < (Botrow - Toprow + 1) / 2) Topln = from; /* slide the screen down */ else if (hw && to > l2 && to - l2 < (Botrow - Toprow + 1) / 2) Topln = to - (Botrow - Toprow); /* slide the screen up */ else Topln = (from + to + Toprow - Botrow) / 2; /* center the range */ if (Topln + (Botrow - Toprow) > Lastln) Topln = Lastln - (Botrow - Toprow); if (Topln < 1) Topln = 1; if (First_affected > Topln) First_affected = Topln; } } /* svdel --- record the deletion of buffer lines for screen update */ void svdel (int ln, int n) { int i, j, lb, ub; if (ln + n <= Sctop) { Sctop -= n; } else if (ln < Sctop) { ub = ln + n - Sctop; for (i = 0; i < Sclen; i++) { if (Scline[i] == -1) { ; } else if (Scline[i] < ub) { Scline[i] = -1; } else { Scline[i] -= ub; } } Sctop = ln; } else { lb = ln - Sctop; ub = ln + n - Sctop; for (i = 0; i < Sclen; i++) { if (Scline[i] == -1 || Scline[i] < lb) { ; } else if (Scline[i] < ub) { Scline[i] = -1; } else { Scline[i] -= n; } } } } /* svins --- record a buffer insertion for screen updating */ void svins (int ln, int n) { int i, lb; if (ln < Sctop) { Sctop += n; } else { lb = ln - Sctop; for (i = 0; i < Sclen; i++) { if (Scline[i] != -1 && Scline[i] > lb) { Scline[i] += n; } } } } /* fixscreen --- try to patch up the screen using insert/delete line */ void fixscreen (void) { int oi; /* "old" screen index */ int ni; /* "new" screen index */ int dc; /* number of deletions in current block */ int ic; /* number of insertions in current block */ int ul; /* number of lines that must be rewritten if */ /* we don't update by insert/delete */ int wline[MAXROWS]; /* new screen contents; Scline contains old */ int p; /* if the screen width was changed, give up before it's too late */ if (Botrow - Toprow + 1 != Sclen || ! hwinsdel()) { return; } /* scale the offsets in Scline to the new Topln; set any that are */ /* off the screen to -1 so we won't have to fool with them */ for (oi = 0; oi < Sclen; oi++) if (Scline[oi] == -1 || Scline[oi] + Sctop < Topln || Scline[oi] + Sctop > Topln + (Botrow - Toprow)) Scline[oi] = -1; else Scline[oi] += Sctop - Topln; /* fill in wline with only those numbers that are in Scline */ for (oi = 0, ni = 0; ni < Sclen; ni++) { while (oi < Sclen && Scline[oi] == -1) oi++; if (oi < Sclen && Scline[oi] == ni) { wline[ni] = ni; oi++; } else wline[ni] = -1; } /* see if it's still advisable to fix the screen: if the number */ /* of lines that must be rewritten is less than 2 + the number */ /* of lines that must be inserted, don't bother (this is a dumb */ /* but fairly effective heuristic) */ ul = ni = 0; for (oi = 0; oi < Sclen; oi++) { if (Scline[oi] != wline[oi] || Scline[oi] == -1) ul++; if (wline[oi] == -1) ni++; } if (ul < ni + 2) return; /* Now scan the screens backwards and put out delete-lines */ /* for all deletions and changes with more old lines than new */ oi = ni = Sclen - 1; while (oi >= 0 || ni >= 0) { for (dc = 0; oi >= 0 && Scline[oi] == -1; dc++) oi--; for (ic = 0; ni >= 0 && wline[ni] == -1; ic++) ni--; if (dc > ic) dellines (oi + 1 + Toprow + ic, dc - ic); while (oi >= 0 && Scline[oi] != -1 && ni >= 0 && wline[ni] == Scline[oi]) oi--, ni--; } /* At last, scan the screens forward and put out insert-lines */ /* for all insertions and changes with more new lines than old */ oi = ni = 0; while (oi < Sclen || ni < Sclen) { p = ni; for (dc = 0; oi < Sclen && Scline[oi] == -1; dc++) oi++; for (ic = 0; ni < Sclen && wline[ni] == -1; ic++) ni++; if (ic > dc) inslines (p + Toprow + dc, ic - dc); while (oi < Sclen && Scline[oi] != -1 && ni < Sclen && wline[ni] == Scline[oi]) oi++, ni++; } }