char *tecdisp_c_version = "tecdisp.c: $Revision: 1.3 $"; /* * $Date: 2007/12/26 13:28:30 $ * $Source: /cvsroot/videoteco/videoteco/tecdisp.c,v $ * $Revision: 1.3 $ * $Locker: $ */ /** * \file tecdisp.c * \brief Terminal Screen Display Subroutines */ /* * Copyright (C) 1985-2007 BY Paul Cantrell * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "teco.h" #include "tecparse.h" /* * Global Storage is defined here for lack of a better place. */ /* * External Variables */ extern char teco_startup; extern char screen_startup; extern int term_columns; extern int term_lines; extern int forced_height,forced_width; extern int tty_input_chan; extern int tty_output_chan; extern char insert_delete_line_capability; extern char eight_bit_output_flag; extern char hide_cr_flag; extern char tab_expand[]; extern int tab_width; #ifdef TERMCAP extern int termcap_sg; #endif #ifdef TERMINFO extern int terminfo_magic_cookie_glitch; #endif void screen_message( char *string ); /* * Global Variables */ int curx; int cury; short cur_mode; int cursor_x; int cursor_y; int screen_sequence; int echo_cnt; char ring_audible_bell; int buffer_li; int buffer_half_li; char format_lines_invalid; struct format_line * allocate_format_buffer(struct window *,struct buff_line *); struct format_line * screen_find_window_format_line(struct window *,struct buff_line *); struct window *create_window(int,int); void screen_optimize_lines(void); void screen_optimize_by_pattern_matching(void); void screen_gc_format_lines(void); void screen_free_window_format_lines(struct window *,struct buff_line *); void screen_account_for_delete_line(int y, int count); void screen_account_for_insert_line(int y, int count); int screen_rebuild_from_scratch(struct window *wptr); int screen_format_buff_line(struct window *wptr,struct buff_line *lbp); int screen_find_dot(struct window *wptr); /* * Global structures */ struct screen_line *saved_screen; char saved_screen_valid; int next_window_number = 1; struct window *curwin; struct window *window_list; struct format_line message_line; char message_flag = NO; struct format_line echo_line; struct format_line *format_line_free_list = NULL; struct format_line *format_line_alloc_list = NULL; /* * External Routines, etc. */ extern struct buff_header *curbuf; extern struct buff_header *buffer_headers; extern int term_speed; extern char input_pending_flag; extern char resize_flag; /** * \brief Initialize the TECO screen package * * This entry point gives the screen package a chance to initialize * itself. */ int screen_init() { register int i,j; register struct window *wptr; register struct screen_line *lp; register short *sp; PREAMBLE(); /* * Call the terminal handling routines, and give them a chance to set * the terminal up. */ term_init(); /* * Set the cursor to the upper left of the display and perform a clear to end * of display command. This has the effect of clearing the entire display. */ term_goto(0,0); term_clrtobot(); /* * If this is a restart (from ^Z most likely), then that is all we have to do. * The rest of this is only to initialize data structures the first time * through */ if(screen_startup == NO) return(SUCCESS); screen_startup = NO; /* * Calculate how many lines are available for display of the edit buffer. */ buffer_li = term_lines - SCREEN_RESERVED_LINES; buffer_half_li = ((buffer_li + 1) & ~1) / 2; /* * Allocate memory for the saved screen structure. This is used to remember * what is currently showing on the terminal. Since we just cleared the screen, * we set the current contents of this to be all spaces (i.e., blank). */ i = sizeof(struct screen_line) * term_lines; saved_screen = (struct screen_line *)tec_alloc(TYPE_C_SCR,i); if(saved_screen == NULL) return(FAIL); bzero(saved_screen,i); screen_sequence = 1; for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ MAGIC_UPDATE(lp, MAGIC_SCREEN); lp->companion = NULL; lp->sequence = 0; lp->buffer = (short *) tec_alloc(TYPE_C_SCRBUF,(int)(sizeof(short) * term_columns)); sp = lp->buffer; for(j = 0; j < term_columns; j++,sp++) *sp = ' '; }/* End FOR */ saved_screen_valid = 1; /* * Allocate the initial window structure */ window_list = wptr = create_window(term_columns,term_lines - SCREEN_RESERVED_LINES); curwin = wptr; wptr->win_buffer = curbuf; lp = saved_screen; lp += term_lines - SCREEN_RESERVED_LINES - 1; wptr->win_label_line.fmt_saved_line = lp; lp->companion = &wptr->win_label_line; /* * Now we set up the structures for our special lines which form the bottom * of the display screen. */ echo_line.fmt_buffer_line = message_line.fmt_buffer_line = NULL; echo_line.fmt_buffer_size = message_line.fmt_buffer_size = term_columns; echo_line.fmt_buffer = (short *) tec_alloc( TYPE_C_SCRBUF, (int)(sizeof(short) * echo_line.fmt_buffer_size) ); message_line.fmt_buffer = (short *) tec_alloc( TYPE_C_SCRBUF, (int)(sizeof(short)*message_line.fmt_buffer_size) ); for(i = 0; i < term_columns; i++){ message_line.fmt_buffer[i] = ' '; echo_line.fmt_buffer[i] = ' '; }/* End FOR */ echo_line.fmt_buffer[0] = '*'; echo_line.fmt_sequence = message_line.fmt_sequence = 1; echo_line.fmt_next_line = message_line.fmt_next_line = NULL; message_line.fmt_visible_line_position = echo_line.fmt_visible_line_position = -1; lp = saved_screen; lp += term_lines - SCREEN_RESERVED_LINES; message_line.fmt_permanent = 1; message_line.fmt_saved_line = lp; MAGIC_UPDATE(&message_line, MAGIC_FORMAT_LOOKASIDE); lp->companion = &message_line; lp += 1; echo_line.fmt_permanent = 1; echo_line.fmt_saved_line = lp; MAGIC_UPDATE(&echo_line, MAGIC_FORMAT_LOOKASIDE); lp->companion = &echo_line; screen_label_line(curbuf," TECO",LABEL_C_TECONAME); return(SUCCESS); }/* End Routine */ /** * \brief Cause the screen to be refreshed at a new size * * This function is called when the size of the terminal screen has * changed. This is common on a windowing terminal. This routine has * to clean up the existing screen database and re-initialize it. */ void screen_resize() { register int i; register struct window *wptr; register struct screen_line *lp; #ifdef SUN_STYLE_WINDOW_SIZING struct winsize os_window; #endif PREAMBLE(); /* * The screen_gc_format_line is going to scavenge all the lines because * we're making them look not-used here. However, we still go ahead and * clobber the line_buffer's pointer, because we don't like the idea of * having hanging pointers pointed at our screen lines. */ if(saved_screen_valid){ for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ if(lp->companion){ if(lp->companion->fmt_saved_line != lp){ fprintf(stderr, "lp->companion %p != ->fmt_saved_line %p\n", lp->companion, lp->companion->fmt_saved_line); CAUSE_BUS_ERROR(); }/* End IF */ lp->companion->fmt_saved_line = NULL; lp->companion = NULL; }/* End IF */ }/* End FOR */ }/* End IF */ screen_gc_format_lines(); /* * Return the memory used for the saved screen structure. This structure * is the one used to remember what is currently showing on the terminal. */ if(saved_screen_valid){ saved_screen_valid = 0; for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ if(lp->buffer) tec_release(TYPE_C_SCRBUF,(char *)lp->buffer); lp->buffer = NULL; }/* End FOR */ if(saved_screen) tec_release(TYPE_C_SCR,(char *)saved_screen); saved_screen = NULL; }/* End IF */ /* * Reclaim all the window structures */ while(1){ wptr = window_list; if(wptr == NULL) break; window_list = wptr->win_next_window; /* * Release any memory used to hold the strings in the label line */ for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ if(wptr->win_label_field_contents[i]){ tec_release( TYPE_C_LABELFIELD, wptr->win_label_field_contents[i] ); wptr->win_label_field_contents[i] = NULL; }/* End IF */ }/* End FOR */ /* * Release the buffer used to hold formatted data */ tec_release(TYPE_C_SCRBUF,(char *)wptr->win_label_line.fmt_buffer); tec_release(TYPE_C_WINDOW,(char *)wptr); }/* End While */ window_list = NULL; if(echo_line.fmt_buffer){ tec_release(TYPE_C_SCRBUF,(char *)echo_line.fmt_buffer); }/* End IF */ if(message_line.fmt_buffer){ tec_release(TYPE_C_SCRBUF,(char *)message_line.fmt_buffer); }/* End IF */ echo_cnt = 0; echo_line.fmt_buffer_line = message_line.fmt_buffer_line = NULL; echo_line.fmt_buffer_size = message_line.fmt_buffer_size = 0; /* * If the OS supports window size ioctls, we try for that since it * will tend to be more correct for a window environment. */ #ifdef SUN_STYLE_WINDOW_SIZING if(ioctl(tty_output_chan,TIOCGWINSZ,&os_window) >= 0){ term_lines = os_window.ws_row; term_columns = os_window.ws_col; }/* End IF */ #endif if(forced_height > 0) term_lines = forced_height; if(forced_width > 0) term_columns = forced_width; if(term_lines == 0) term_lines = 24; if(term_columns == 0) term_columns = 80; resize_flag = NO; screen_startup = YES; screen_init(); buff_switch(curbuf,0); parser_reset_echo(); screen_echo('\0'); screen_format_windows(); }/* End Routine */ /** * \brief Here when we are exiting the editor * * This routine is called when TECO is being exited to give us a chance * to cleanly leave the screen package. We put the cursor at the bottom * of the screen, send any termination escape sequences that are required * and flush it all out. */ void screen_finish() { term_finish(); }/* End Routine */ /** * \brief Redraw the screen based on new database * * This routine gets called after the new companion entries have been set * up for the saved_screen database. Therefore, we can run through the * lines and update only those which have changed. */ void screen_refresh() { register int x,y; register struct screen_line *lp; register struct format_line *his_lp; register short *sp,*osp; char pos_flag; PREAMBLE(); input_pending_flag = tty_input_pending(); if(input_pending_flag == YES) return; /* * First try to optimize output by performing insert/delete line operations */ if(insert_delete_line_capability){ screen_optimize_lines(); }/* End IF */ /* * Go through each line and insure that the screen appearence is changed to * match those described by the line buffer structures. */ for(y = 0; y < term_lines; y++){ if(term_speed <= 2400){ term_flush(); }/* End IF */ input_pending_flag = tty_input_pending(); if(input_pending_flag == YES) break; /* * Get pointers to the saved screen entry which tells us what the screen * currently looks like, and pointers to the line buffer structure which * tells us what the screen ought to look like when we are done. */ lp = saved_screen + y; osp = lp->buffer; his_lp = lp->companion; /* * If there is no line buffer structure, the line ought to appear blank on * the screen. If it already is blank, no sweat, otherwise we want to do * a clear to end of line function. If the saved_screen sequence number * is zero, this is a flag that the line is already clear. */ if(his_lp == NULL){ if(lp->sequence == 0) continue; for(x = 0; x < term_columns; x++,osp++){ if((*osp & SCREEN_M_DATA) != ' '){ if(cury != y || curx > x) term_goto(x,y); if(cur_mode & SCREEN_M_REVERSE){ term_standend(); cur_mode &= ~SCREEN_M_REVERSE; }/* End IF */ term_clrtoeol(); while(x < term_columns){ *osp++ = ' '; x += 1; }/* End While */ }/* End IF */ }/* End FOR */ lp->sequence = 0; continue; }/* End IF */ /* * If the line buffer sequence number is less than that of the saved screen * sequence number, we know that this line has not been changed, and thus we * don't even have to check to see whether they match. */ if(his_lp->fmt_sequence <= lp->sequence) continue; lp->sequence = screen_sequence; sp = his_lp->fmt_buffer; his_lp->fmt_visible_line_position = y; /* * Well, changes have been made, so we have to go through the line on a byte * by byte basis comparing character positions. */ for(x = 0; x < term_columns; x++,sp++,osp++){ /* * If the characters are the same we don't have to take any special action * unless it is a newline in which case sometimes we have special things to do */ if((*sp & SCREEN_M_DATA) != '\n' && *osp == *sp){ continue; }/* End If */ /* * If it IS a newline, we want to make sure that the screen shows blanks * for the rest of this line. If it does not, we have to do a clear to * end of line operation and update the saved screen database to reflect * this. */ if((*sp & SCREEN_M_DATA) == '\n'){ for(; x < term_columns; x++,osp++){ if(*osp == ' ') continue; if(curx != x || cury != y) term_goto(x,y); if(cur_mode & SCREEN_M_REVERSE){ term_standend(); cur_mode &= ~SCREEN_M_REVERSE; }/* End IF */ term_clrtoeol(); for(; x < term_columns; x++){ *osp++ = ' '; }/* End FOR */ }/* End FOR */ break; }/* End IF */ /* * Copy the byte into our database to reflect the new appearance of the screen */ *osp = *sp; pos_flag = 1; /* * If the current position of the cursor is not the position of the character * we want to update, we have to position to that place. If we are on the * wrong line, or if we are already to the right of the desired position, * we have to do a full position escape sequence. */ if(cury != y || curx > x){ term_goto(x,y); pos_flag = 0; }/* End IF */ /* * If we are not positioned yet, check to see if we are only a few positions * to the left of where we want to be. If this is the case, it is faster to * output the actual characters than to generate a full cursor motion escape * sequence. */ if(pos_flag && x - curx < 5){ while(curx < x){ if((his_lp->fmt_buffer[curx] & SCREEN_M_REVERSE) != (cur_mode & SCREEN_M_REVERSE)) break; term_putc(his_lp->fmt_buffer[curx] & SCREEN_M_DATA); curx++; }/* End While */ pos_flag = 0; }/* End IF */ /* * If we get to here and the position is not correct, no shortcuts helped so * we have to output the full escape sequence to position the cursor */ if(curx != x || cury != y) term_goto(x,y); /* * Check to see that we are in the correct mode (standout or not). If not, * we have to change it before we output the data byte */ if((*sp & SCREEN_M_REVERSE) != (cur_mode & SCREEN_M_REVERSE)){ if(*sp & SCREEN_M_REVERSE){ term_standout(); cur_mode |= SCREEN_M_REVERSE; }/* End IF */ else { term_standend(); cur_mode &= ~SCREEN_M_REVERSE; }/* End Else */ }/* End IF */ /* * Output the data byte and record that that changes the current x position */ term_putc(*sp & SCREEN_M_DATA); curx++; }/* End FOR */ }/* End FOR */ /* * We don't want to leave the terminal in reverse video between screen updates, * so if we are in reverse video mode, put it back to non-reverse. */ if(cur_mode & SCREEN_M_REVERSE){ term_standend(); cur_mode &= ~SCREEN_M_REVERSE; }/* End IF */ /* * Make sure that the cursor gets left in the position that indicates the * "dot" position in the edit buffer. */ if(curx != cursor_x || cury != cursor_y){ term_goto(cursor_x,cursor_y); }/* End IF */ /* * Force this all to be written to the terminal, and increment the sequence * number */ term_flush(); screen_gc_format_lines(); screen_sequence += 1; }/* End Routine */ /** * \brief Optimize output with insert/delete line operations * * This routine looks for left over mappings between our saved screen * array and the formatted format_line structures. Old mappings can * tell us how lines that are still going to be visible have moved. */ void screen_optimize_lines() { register int y; int vlp; register struct screen_line *lp; int blk_delete_count; int blk_insert_count; int companion_count; int offset; PREAMBLE(); companion_count = 0; /* * Search for a line which is known to be out of place */ for(y = 0; y < buffer_li; y++){ lp = &saved_screen[y]; if(lp->companion == NULL) continue; if((vlp = lp->companion->fmt_visible_line_position) == -1) continue; if(vlp != y) break; companion_count += 1; break; }/* End FOR */ if(y == buffer_li){ if(companion_count == 0) screen_optimize_by_pattern_matching(); return; }/* End IF */ /* * Search for delete line opportunities */ offset = 0; for(y = 0; y < term_lines; y++){ lp = &saved_screen[y]; if(lp->companion == NULL) continue; if((vlp = lp->companion->fmt_visible_line_position) == -1) continue; vlp += offset; if(vlp == y) continue; if(vlp > y){ blk_delete_count = vlp - y; term_delete_line(y-offset,blk_delete_count); screen_account_for_delete_line(y-offset,blk_delete_count); continue; }/* End IF */ if(vlp < y){ offset += y - vlp; }/* End IF */ }/* End FOR */ /* * Search for insert line opportunities */ for(y = 0; y < term_lines; y++){ lp = &saved_screen[y]; if(lp->companion == NULL) continue; if((vlp = lp->companion->fmt_visible_line_position) == -1) continue; if(vlp == y) continue; if(vlp > y){ screen_message("insert_line confused"); }/* End IF */ if(vlp < y){ blk_insert_count = y - vlp; term_insert_line(vlp,blk_insert_count); screen_account_for_insert_line(vlp,blk_insert_count); }/* End IF */ }/* End FOR */ }/* End Routine */ /** * \brief Update screen database * * This routine is called when a delete line sequence is sent to * the terminal. It adjusts the screen database to reflect what * has happened to the terminal. */ void screen_account_for_delete_line( int y, int count ) { register int i,x; register struct screen_line *top_lp,*bottom_lp; register struct format_line *slp; register short *sp; PREAMBLE(); /* * The result of a delete line operation will be that starting at the * location it was issued, lines will be replaced by the contents of * lines lower down on the screen. */ top_lp = &saved_screen[y]; bottom_lp = top_lp + count; for(i = y + count; i < term_lines; i++,top_lp++,bottom_lp++){ sp = top_lp->buffer; top_lp->buffer = bottom_lp->buffer; bottom_lp->buffer = sp; top_lp->sequence = bottom_lp->sequence; }/* End FOR */ top_lp = saved_screen; for(i = 0; i < term_lines; i++,top_lp++){ if((slp = top_lp->companion) == NULL) continue; if(slp->fmt_visible_line_position >= y){ slp->fmt_visible_line_position -= count; }/* End IF */ if(slp->fmt_visible_line_position < 0){ slp->fmt_visible_line_position = -1; }/* End IF */ }/* End FOR */ top_lp = &saved_screen[term_lines - count]; for(i = term_lines - count; i < term_lines; i++,top_lp++){ top_lp->sequence = 0; sp = top_lp->buffer; for(x = 0; x < term_columns; x++){ *sp++ = ' '; }/* End FOR */ }/* End FOR */ }/* End Routine */ /** * \brief Update screen database * * This routine is called when an insert line sequence is sent to * the terminal. It adjusts the screen database to reflect what * has happened to the terminal appearence. */ void screen_account_for_insert_line( int y, int count ) { register int i,x; register struct screen_line *top_lp,*bottom_lp; register struct format_line *slp; register short *sp; PREAMBLE(); top_lp = &saved_screen[term_lines - count - 1]; bottom_lp = &saved_screen[term_lines - 1]; for(i = term_lines - count - 1; i >= y; i--,top_lp--,bottom_lp--){ sp = bottom_lp->buffer; bottom_lp->buffer = top_lp->buffer; top_lp->buffer = sp; bottom_lp->sequence = top_lp->sequence; }/* End FOR */ top_lp = saved_screen; for(i = 0; i < term_lines; i++,top_lp++){ if((slp = top_lp->companion) == NULL) continue; if(slp->fmt_visible_line_position >= y){ slp->fmt_visible_line_position += count; }/* End IF */ if(slp->fmt_visible_line_position >= term_lines){ slp->fmt_visible_line_position = -1; }/* End IF */ }/* End FOR */ top_lp = &saved_screen[y]; for(i = 0; i < count; i++,top_lp++){ top_lp->sequence = 0; sp = top_lp->buffer; for(x = 0; x < term_columns; x++){ *sp++ = ' '; }/* End FOR */ }/* End FOR */ }/* End Routine */ /** * \brief More insert/delete line optimizations * * This routine is called previous to the brute force screen repaint, * but after the first crack at insert/delete line optimization. This * routine looks for patterns of lines which indicate insert/delete * line can save us some output. */ void screen_optimize_by_pattern_matching() { register int x,y,i,c; register struct screen_line *lp; register struct format_line *his_lp; register short *sp,*osp; char stuff_flag; int old_check[SCREEN_MAX_LINES],new_check[SCREEN_MAX_LINES]; int first_change,last_change; PREAMBLE(); stuff_flag = 0; for(y = 0; y < buffer_li; y++){ /* * Get pointers to the saved screen entry which tells us what the screen * currently looks like, and pointers to the line buffer structure which * tells us what the screen ought to look like when we are done. */ lp = saved_screen + y; osp = lp->buffer; his_lp = lp->companion; /* * If there is no line buffer structure, the line ought to appear blank on * the screen. If it already is blank, no sweat, otherwise we want to do * a clear to end of line function. */ new_check[y] = old_check[y] = 0; if(his_lp && his_lp->fmt_sequence <= lp->sequence) continue; if(his_lp == NULL && lp->sequence == 0) continue; /* * Calculate the checksum for the data currently showing on the screen */ for(x = 0; x < term_columns; x++,osp++){ if((c = *osp & SCREEN_M_DATA) != ' '){ old_check[y] = (old_check[y] << 1) + c; stuff_flag = 1; }/* End IF */ }/* End FOR */ if(his_lp == NULL) continue; sp = his_lp->fmt_buffer; /* * Calculate the checksum for the data which we want to be showing on the * screen. */ for(x = 0; x < term_columns; x++,sp++){ c = *sp & SCREEN_M_DATA; if(c == ' ') continue; if(c == '\n') break; new_check[y] = (new_check[y] << 1) + c; stuff_flag = 1; }/* End FOR */ }/* End FOR */ /* * If no changes have been made, just return */ if(!stuff_flag) return; /* * Determine which region of the screen contains lines which have changed */ first_change = last_change = -1; for(y = 0; y < buffer_li; y++){ if(old_check[y] == new_check[y]) continue; first_change = y; break; }/* End FOR */ /* * If checksum arrays seem to be identical or only one line changed, then * just return and don't attempt further optimization. */ if(first_change == -1) return; for(y = buffer_li - 1; y >= first_change; y--){ if(old_check[y] == new_check[y]) continue; last_change = y; break; }/* End FOR */ if(first_change == last_change) return; /* * Here is the code which tries for a delete-line optimization */ for(i = 1; i < last_change - first_change - 3; i++){ if(old_check[first_change + i] == new_check[first_change]){ for(y = first_change; y < last_change - i; y++){ if(old_check[y + i] != new_check[y]) break; }/* End FOR */ if(y == (last_change - i)){ term_delete_line(first_change,i); screen_account_for_delete_line(first_change,i); term_insert_line(last_change+1-i,i); screen_account_for_insert_line(last_change+1-i,i); return; }/* End IF */ }/* End IF */ }/* End FOR */ /* * Here is the code which tries for a insert-line optimization */ for(i = 1; i < last_change - first_change - 3; i++){ if(old_check[first_change] == new_check[first_change + i]){ for(y = first_change; y < last_change - i; y++){ if(old_check[y] != new_check[y + i]) break; }/* End FOR */ if(y == (last_change - i)){ term_delete_line(last_change+1-i,i); screen_account_for_delete_line(last_change+1-i,i); term_insert_line(first_change,i); screen_account_for_insert_line(first_change,i); break; }/* End IF */ }/* End IF */ }/* End FOR */ }/* End Routine */ /** * \brief Echo the character * * This routine allows the parser to hand us input characters which need * to be echoed. The reason this is done here instead of by the normal * screen formatting routines is that some characters are echoed in a * different way than they are normally displayed. The most notable * example is newline which just indicates end of line in the normal * buffer output, but which gets echoed as '\' when it is an echo * operation. */ void screen_echo( char data ) { register int i; PREAMBLE(); /* * If we are getting close to the right edge of the screen, we remove the * first 3/4 of the echo line and just redisplay the last 1/4 to make room * for future echo characters. */ if(echo_cnt > (term_columns - 4)){ for(i = 0; i < (term_columns / 4); i++){ echo_line.fmt_buffer[ 1 + i ] = echo_line.fmt_buffer[ 1 + echo_cnt - (term_columns / 4) + i ]; }/* End FOR */ echo_cnt = i; for(; i < term_columns-2; i++){ echo_line.fmt_buffer[ 1 + i ] = ' '; }/* End FOR */ }/* End IF */ /* * Dispatch the byte to determine how it should be formatted. NULL is a * special case which is used to flag the fake cursor position on the * echo line. This is shown by outputing a reverse-video space to the * position. */ switch(data){ case '\0': /* * If we are on a high-speed line, do the reverse-video space trick to * show a non-blinking cursor. */ if(term_speed > 1200){ echo_line.fmt_buffer[ 1 + echo_cnt ] = ' ' | SCREEN_M_REVERSE; echo_line.fmt_buffer[ 2 + echo_cnt ] = ' '; echo_line.fmt_buffer[ 3 + echo_cnt ] = '\n'; }/* End IF */ /* * Otherwise, if we are on a low speed line, just insert a carriage return * so that the rest of the line is shown as blanks. */ else { echo_line.fmt_buffer[ 1 + echo_cnt ] = '\n'; }/* End IF */ echo_line.fmt_sequence = screen_sequence; return; case ESCAPE: data = '$'; break; case '\n': for(i = 0; (unsigned)i < strlen(""); i++){ screen_echo(""[i]); }/* End FOR */ return; case RUBOUT: return; /* * Tabs are always echoed as a fixed length string of spaces regardless of * the screen position since normal tab algorithms look weird on the echo * line. */ case '\t': for(i = 0; (unsigned)i < strlen(" "); i++){ screen_echo(" "[i]); }/* End FOR */ return; default: break; }/* End Switch */ /* * If this is a control character, echo as ^byte */ if((unsigned char)data < ' '){ screen_echo('^'); screen_echo(data + 'A' - 1); return; }/* End IF */ /* * Here if it is a normal printable character. We can just echo the character * as is with no special stuff going on. */ echo_line.fmt_buffer[ 1 + echo_cnt++ ] = data; echo_line.fmt_sequence = screen_sequence; }/* End Routine */ /** * \brief Reset the echo line * * This routine is called to reset the echo line according to the current * list of command tokens. This is used after difficult things like rubout * which would be very difficult to back out of. */ void screen_reset_echo( struct cmd_token *ct ) { PREAMBLE(); echo_line.fmt_buffer[ 1 + echo_cnt ] = ' '; while(echo_cnt){ echo_cnt -= 1; echo_line.fmt_buffer[ 1 + echo_cnt ] = ' '; }/* End While */ while(ct){ if(ct->opcode == TOK_C_INPUTCHAR){ screen_echo(ct->input_byte); }/* End IF */ ct = ct->next_token; }/* End While */ echo_line.fmt_buffer[ 1 + echo_cnt ] = '\n'; echo_line.fmt_sequence = screen_sequence; }/* End Routine */ /** * \brief Place a message in the message line * * This routine allows the parser to hand us messages which need to be * displayed. These can be error messages generated by the parse, or * they can be output messages from a user generated with the ^A command. */ void screen_message( char *string ) { register short *sp; register int i; register int c = 0; register char *mep; char multi_echo_buffer[32]; PREAMBLE(); message_flag = YES; sp = message_line.fmt_buffer; i = term_columns; mep = ""; while(*mep || *string){ while(*mep){ *sp++ = c = *mep++; if(i-- <= 0) goto too_wide; continue; }/* End While */ c = *string++; switch(c){ // case NULL: case 0: goto too_wide; case ESCAPE: c = '$'; break; case '\n': mep = ""; continue; case RUBOUT: mep = ""; continue; case '\t': mep = " "; continue; }/* End Switch */ if((unsigned char)c < ' '){ multi_echo_buffer[0] = '^'; multi_echo_buffer[1] = c + 'A' - 1; multi_echo_buffer[2] = '\0'; mep = multi_echo_buffer; continue; }/* End IF */ *sp++ = c; if(i-- <= 0) goto too_wide; }/* End While */ too_wide: if(c != '\n' && i > 0) *sp++ = '\n'; message_line.fmt_sequence = screen_sequence; }/* End Routine */ /** * \brief Place an error message in the message line * * This routine allows the parser to hand us error messages * which need to be displayed. In addition, the BELL character * is output to get the humans attention. */ void error_message( char *string ) { PREAMBLE(); screen_message(string); ring_audible_bell = 1; }/* End Routine */ /** * \brief Reset the message line * * This routine is called to reset the message line to null */ void screen_reset_message() { register int i; register short *sp; PREAMBLE(); if(message_flag == NO) return; message_flag = NO; sp = message_line.fmt_buffer; for(i = 0; i < term_columns; i++){ *sp++ = ' '; }/* End FOR */ message_line.fmt_sequence = screen_sequence; }/* End Routine */ /** * \brief Copy out the current message * * This routine copies out the current message up to a certain length. */ void screen_save_current_message( char *message_save_buff, int message_save_max_length ) { int i; short *sp; PREAMBLE(); sp = message_line.fmt_buffer; i = term_columns; if(i >= message_save_max_length) i = message_save_max_length - 1; while(i-- > 0){ if((*sp & SCREEN_M_DATA) == '\n') break; *message_save_buff++ = *sp++ & SCREEN_M_DATA; } *message_save_buff = '\0'; }/* End Routine */ /** * \brief Routine to change the contents of the label line * * This routine is called to change the label line */ int screen_label_line( struct buff_header *buffer, char *string, int field ) { register short *sp; register char *cp; register int i; register int column; register struct window *wptr; PREAMBLE(); /* * If the field specified is out of range, punt */ if(field < 0 || field >= SCREEN_MAX_LABEL_FIELDS){ screen_message("Illegal field specified to label line\n"); return(FAIL); }/* End IF */ /* * We want to modify the label in any window which is displaying this buffer. * There could be several windows with the same buffer displayed... */ for(wptr = window_list ; wptr != NULL; wptr = wptr->win_next_window){ if(wptr->win_buffer != buffer) continue; /* * If no space has been allocated for this field yet, we need to call the * allocator. Notice the overkill in that each field gets more than enough * memory. */ if(wptr->win_label_field_contents[field] == NULL){ wptr->win_label_field_contents[field] = tec_alloc(TYPE_C_LABELFIELD,SCREEN_NOMINAL_LINE_WIDTH); if(wptr->win_label_field_contents[field] == NULL){ return(FAIL); }/* End IF */ *(wptr->win_label_field_contents[field]) = '\0'; }/* End IF */ /* * If the current contents of the field are already what we want, we don't * have to change a thing. */ if(strcmp(string,wptr->win_label_field_contents[field]) == 0) continue; (void) strcpy(wptr->win_label_field_contents[field],string); /* * Figure out which column the field begins on. This changes as fields change, * so it must be computed each time a change happens. */ for(i = 0 , column = 0; i < field; i++){ if(wptr->win_label_field_contents[i] == NULL) continue; column += strlen(wptr->win_label_field_contents[i]) + 1; }/* End FOR */ /* * Now we have to update the label line from this column onward until we * reach the end of the final field. */ sp = &wptr->win_label_line.fmt_buffer[column]; for(i = field; i < SCREEN_MAX_LABEL_FIELDS; i++){ cp = wptr->win_label_field_contents[i]; if(cp == NULL) continue; while(*cp){ if(column >= term_columns) break; *sp++ = *cp++ | SCREEN_M_REVERSE; column += 1; }/* End While */ if(column >= term_columns) break; *sp++ = ' ' | SCREEN_M_REVERSE; column += 1; }/* End FOR */ /* * Blank fill to the end of the screen incase we shrank the label line. */ while(column < term_columns){ #ifdef TERMCAP *sp++ = termcap_sg ? '-' : ' ' | SCREEN_M_REVERSE; #endif #ifdef TERMINFO *sp++ = (terminfo_magic_cookie_glitch > 0) ? '-' : ' ' | SCREEN_M_REVERSE; #endif column += 1; }/* End While */ /* * Insure that the next screen refresh knows this line has changed */ wptr->win_label_line.fmt_sequence = screen_sequence; }/* End FOR */ return( SUCCESS ); }/* End Routine */ /** * \brief Routine to set the window number field * * This routine is called to modify the TECONAME field of the * label lines to reflect the window number. */ int screen_label_window() { register short *sp; register char *cp; register int i; register int column; register struct window *wptr; char tmp_buffer[SCREEN_NOMINAL_LINE_WIDTH]; int field = LABEL_C_TECONAME; PREAMBLE(); /* * We want to check the label in every window */ for(wptr = window_list ; wptr != NULL; wptr = wptr->win_next_window){ /* * If no space has been allocated for this field yet, we need to call the * allocator. Notice the overkill in that each field gets more than enough * memory. */ if(wptr->win_label_field_contents[field] == NULL){ wptr->win_label_field_contents[field] = tec_alloc(TYPE_C_LABELFIELD,SCREEN_NOMINAL_LINE_WIDTH); if(wptr->win_label_field_contents[field] == NULL){ return(FAIL); }/* End IF */ *(wptr->win_label_field_contents[field]) = '\0'; }/* End IF */ /* * If the current contents of the field are already what we want, we don't * have to change a thing. */ sprintf(tmp_buffer," TECO-%d",wptr->win_window_number); if(strcmp(tmp_buffer,wptr->win_label_field_contents[field]) == 0){ continue; }/* End IF */ (void) strcpy(wptr->win_label_field_contents[field],tmp_buffer); /* * Now we have to update the label line from this column onward until we * reach the end of the final field. */ column = 0; sp = &wptr->win_label_line.fmt_buffer[column]; for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ cp = wptr->win_label_field_contents[i]; if(cp == NULL) continue; while(*cp){ if(column >= term_columns) break; *sp++ = *cp++ | SCREEN_M_REVERSE; column += 1; }/* End While */ if(column >= term_columns) break; *sp++ = ' ' | SCREEN_M_REVERSE; column += 1; }/* End FOR */ /* * Blank fill to the end of the screen incase we shrank the label line. */ while(column < term_columns){ *sp++ = ' ' | SCREEN_M_REVERSE; column += 1; }/* End While */ /* * Insure that the next screen refresh knows this line has changed */ wptr->win_label_line.fmt_sequence = screen_sequence; }/* End FOR */ return(SUCCESS); }/* End Routine */ /** * \brief Generate display contents for all windows * * This is the main entry point to the screen formatting function. * It causes screen format_lines to be generated for all the buffers * that are currently visible on the screen. */ void screen_format_windows() { register struct window *wptr; PREAMBLE(); for(wptr = window_list; wptr != NULL; wptr = wptr->win_next_window){ screen_display_window(wptr); }/* End FOR */ }/* End Routine */ /** * \brief Reformat windows (because tab size changed) * * This entry point is called when the format buffers of all the * displayed windows need to be regenerated. The current case in * mind is when the user changes the tab stops - this causes all * the format lines to be obsolete. */ void screen_reformat_windows() { register int i; register struct screen_line *lp; PREAMBLE(); /* * The screen_gc_format_line is going to scavenge all the lines because * we're making them look not-used here. However, we still go ahead and * clobber the line_buffer's pointer, because we don't like the idea of * having hanging pointers pointed at our screen lines. */ if(saved_screen_valid){ for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ if(lp->companion){ if(lp->companion->fmt_saved_line != lp){ fprintf(stderr, "lp->companion %p != ->fmt_saved_line %p\n", lp->companion, lp->companion->fmt_saved_line); CAUSE_BUS_ERROR(); }/* End IF */ if( lp->companion->fmt_permanent ) continue; lp->companion->fmt_saved_line = NULL; lp->companion = NULL; }/* End IF */ }/* End FOR */ }/* End IF */ screen_gc_format_lines(); }/* End Routine */ /** * \brief Generate display for a window * * This routine is called to build up the format line descriptor * structures which represent the edit buffer displayed in the * specified window. */ int screen_display_window( struct window *wptr ) { struct buff_header *hbp; struct buff_line *tlbp,*blbp; struct format_line *tsbp,*bsbp; struct screen_line *sp; register int i; int lines; int anchor_line; char saw_dot; PREAMBLE(); /* * Find a line in our saved screen array that still maps to a buffer location */ sp = saved_screen + wptr->win_y_base; anchor_line = -1; for(i = wptr->win_y_base; i < wptr->win_y_end; i++,sp++){ if(sp->companion == NULL) continue; if(sp->companion->fmt_owning_buffer != wptr->win_buffer) continue; anchor_line = i; break; }/* End FOR */ if(anchor_line < 0){ return(screen_rebuild_from_scratch(wptr)); }/* End IF */ /* * Now walk the top back */ tsbp = sp->companion; tlbp = tsbp->fmt_buffer_line; while(i > wptr->win_y_base){ /* * If there is a format line in front of the one we are on, just back * up to it. */ if(tsbp->fmt_prev_line){ tsbp = tsbp->fmt_prev_line; i -= 1; continue; }/* End IF */ /* * Else if there is a buffer line in front of us, get to the final * format line associated with it. */ if(tlbp->prev_line != NULL){ tlbp = tlbp->prev_line; tsbp = screen_find_window_format_line(wptr,tlbp); if(tsbp == NULL){ if(screen_format_buff_line(wptr,tlbp) == FAIL) return(FAIL); tsbp = screen_find_window_format_line(wptr,tlbp); }/* End IF */ while(tsbp->fmt_next_line) tsbp = tsbp->fmt_next_line; i -= 1; continue; }/* End IF */ /* * If we got here, that means that before we could work back up to the old * top line, we hit the begining of the buffer. Give up. */ return(screen_rebuild_from_scratch(wptr)); }/* End While */ /* * If we make it to here, we have worked back to the top of the screen, now * we want to work down to the bottom of the screen. */ lines = 1; blbp = tlbp; bsbp = tsbp; /* * Determine the format_line that dot lies on so that as we work forward we can * keep an eye out for it. */ hbp = wptr->win_buffer; buff_find_line(hbp,hbp->dot); screen_find_dot(wptr); saw_dot = 0; while(lines <= wptr->win_y_size){ /* * If we see the line that dot is on, remember it. The one case when we don't * do this is if it is the top line of the screen. The reason for this is to * avoid over-optimizing such that the user makes changes but never sees them * because the changes are above the top line of the screen. */ if(bsbp == wptr->win_dot_format_line && lines != 1) saw_dot = 1; /* * If there is a format line below our current bottom one, simply go * forward to it. */ if(bsbp->fmt_next_line){ bsbp = bsbp->fmt_next_line; lines += 1; continue; }/* End IF */ /* * If we have to go on to the next buffer line, but there is not one there, * that means that we hit the end of the buffer. */ if(blbp->next_line == NULL){ return(screen_rebuild_from_scratch(wptr)); }/* End IF */ /* * Else, if there is another buffer line after this, go to the first * format line associated with it. */ if(blbp->next_line != NULL){ blbp = blbp->next_line; bsbp = screen_find_window_format_line(wptr,blbp); if(bsbp == NULL){ if(screen_format_buff_line(wptr,blbp) == FAIL) return(FAIL); bsbp = screen_find_window_format_line(wptr,blbp); }/* End IF */ lines += 1; }/* End IF */ }/* End While */ /* * Test to see whether we ever saw dot. If not, we need to do the scratch * screen rebuild. */ if(curwin == wptr && saw_dot == 0){ return(screen_rebuild_from_scratch(wptr)); }/* End IF */ /* * Now we can start setting up the screen appearance */ sp = saved_screen + wptr->win_y_base; for(i = wptr->win_y_base; i < wptr->win_y_end; i++,sp++){ if(tlbp == NULL){ if(sp->companion) sp->companion->fmt_saved_line = NULL; sp->companion = NULL; continue; }/* End IF */ if(sp->companion != tsbp){ if(sp->companion) sp->companion->fmt_saved_line = NULL; sp->companion = NULL; if(tsbp->fmt_saved_line) tsbp->fmt_saved_line->companion = NULL; tsbp->fmt_saved_line = NULL; sp->companion = tsbp; tsbp->fmt_saved_line = sp; tsbp->fmt_sequence = screen_sequence; }/* End IF */ if(curwin == wptr && tsbp == wptr->win_dot_format_line){ cursor_y = i; cursor_x = wptr->win_dot_screen_offset; }/* End IF */ tsbp = tsbp->fmt_next_line; if(tsbp == NULL){ tlbp = tlbp->next_line; if(tlbp){ tsbp = screen_find_window_format_line(wptr,tlbp); }/* End IF */ }/* End IF */ }/* End FOR */ return(SUCCESS); }/* End Routine */ /** * \brief Things have changed a lot, just rebuild it * * This routine is called to build up the format line descriptor * structures which represent the current edit buffer. */ int screen_rebuild_from_scratch( struct window *wptr ) { struct buff_header *hbp; struct buff_line *tlbp,*blbp; struct format_line *tsbp,*bsbp; struct screen_line *scrbp; register int i; int lines,olines; PREAMBLE(); /* * Find the buffer line that dot is on, and then the format_line that * it is on. */ hbp = wptr->win_buffer; tlbp = blbp = buff_find_line(hbp,hbp->dot); if(screen_find_dot(wptr) == FAIL) return(FAIL); tsbp = bsbp = wptr->win_dot_format_line; lines = 1; /* * Now walk the top back and the bottom forward */ while(lines < wptr->win_y_size){ olines = lines; /* * If there is a format_line in front of the one we are on, just back * up to it. */ if(tsbp->fmt_prev_line){ tsbp = tsbp->fmt_prev_line; lines += 1; }/* End IF */ /* * Else if there is a buffer line in front of us, get to the final * format_line associated with it. */ else if(tlbp->prev_line != NULL){ tlbp = tlbp->prev_line; tsbp = screen_find_window_format_line(wptr,tlbp); if(tsbp == NULL){ if(screen_format_buff_line(wptr,tlbp) == FAIL) return(FAIL); tsbp = screen_find_window_format_line(wptr,tlbp); }/* End IF */ while(tsbp && tsbp->fmt_next_line) tsbp = tsbp->fmt_next_line; lines += 1; }/* End IF */ /* * If there is a format_line below our current bottom one, simply go * forward to it. */ if(bsbp->fmt_next_line){ bsbp = bsbp->fmt_next_line; lines += 1; }/* End IF */ /* * Else, if there is another buffer line after this, go to the first * format_line associated with it. */ else if(blbp->next_line != NULL){ blbp = blbp->next_line; bsbp = screen_find_window_format_line(wptr,blbp); if(bsbp == NULL){ if(screen_format_buff_line(wptr,blbp) == FAIL) return(FAIL); bsbp = screen_find_window_format_line(wptr,blbp); }/* End IF */ lines += 1; }/* End IF */ /* * If we havn't made any progress, then the buffer is smaller than the * screen, so just display it. */ if(lines == olines) break; }/* End While */ /* * Now we can start setting up the screen appearance */ scrbp = saved_screen + wptr->win_y_base; for(i = wptr->win_y_base; i < wptr->win_y_end; i++,scrbp++){ if(tlbp == NULL){ if(scrbp->companion) scrbp->companion->fmt_saved_line = NULL; scrbp->companion = NULL; continue; }/* End IF */ if(scrbp->companion != tsbp){ if(scrbp->companion) scrbp->companion->fmt_saved_line = NULL; scrbp->companion = NULL; if(tsbp->fmt_saved_line){ tsbp->fmt_saved_line->companion = NULL; tsbp->fmt_saved_line = NULL; }/* End IF */ scrbp->companion = tsbp; tsbp->fmt_saved_line = scrbp; tsbp->fmt_sequence = screen_sequence; }/* End IF */ if(curwin == wptr && tsbp == wptr->win_dot_format_line){ cursor_y = i; cursor_x = wptr->win_dot_screen_offset; }/* End IF */ tsbp = tsbp->fmt_next_line; if(tsbp == NULL){ tlbp = tlbp->next_line; if(tlbp){ tsbp = screen_find_window_format_line(wptr,tlbp); if(tsbp == NULL){ if(screen_format_buff_line(wptr,tlbp) == FAIL){ return(FAIL); }/* End IF */ tsbp = screen_find_window_format_line(wptr,tlbp); }/* End IF */ }/* End IF */ }/* End IF */ }/* End FOR */ return(SUCCESS); }/* End Routine */ /** * \brief Move the current screen up or down specified # lines * * Here in response to the ES command. The user can scroll the screen * up and down using this command. The current edit position does not * move, so it is pointless to move it such that 'dot' moves off of the * screen. */ void screen_scroll( int count ) { struct window *wptr = curwin; struct screen_line *sp; register int i; PREAMBLE(); i = count; if(i < 0) i = -i; if(i > wptr->win_y_end) return; /* * If the scroll is forward, move the saved lines up in the array by count */ if(count > 0){ sp = &saved_screen[wptr->win_y_base]; for(i = wptr->win_y_base; i < wptr->win_y_end - count; i++,sp++){ if(sp->companion) sp->companion->fmt_saved_line = NULL; sp->companion = (sp+count)->companion; (sp+count)->companion = NULL; if(sp->companion == NULL) continue; sp->companion->fmt_saved_line = sp; sp->companion->fmt_sequence = screen_sequence; }/* End FOR */ sp = &saved_screen[wptr->win_y_end - count]; for(i = wptr->win_y_end - count; i < wptr->win_y_end; i++,sp++){ if(sp->companion == NULL) continue; sp->companion->fmt_saved_line = NULL; sp->companion = NULL; }/* End FOR */ }/* End IF */ /* * Else, if the scroll is backward, move the saved lines down in the array */ else if(count < 0){ sp = &saved_screen[wptr->win_y_end - 1]; for(i = wptr->win_y_end - 1; i >= wptr->win_y_base - count; i--,sp--){ if(sp->companion) sp->companion->fmt_saved_line = NULL; sp->companion = (sp+count)->companion; (sp+count)->companion = NULL; if(sp->companion == NULL) continue; sp->companion->fmt_saved_line = sp; sp->companion->fmt_sequence = screen_sequence; }/* End FOR */ sp = &saved_screen[wptr->win_y_base]; for(i = wptr->win_y_base; i < wptr->win_y_base - count; i++,sp++){ if(sp->companion == NULL) continue; sp->companion->fmt_saved_line = NULL; sp->companion = NULL; }/* End FOR */ }/* End IF */ }/* End Routine */ /** * \brief Garbage collect unused format line structures * * This routine determines which format structures are in use and * deallocates the rest of them so that we don't use an excessive * number of them. */ void screen_gc_format_lines() { register struct format_line *sbp,*tsbp; register struct screen_line *sp; register struct buff_line *lbp; register int i; struct window *wptr; PREAMBLE(); /* * First mark all the format_line structures as unused */ sbp = format_line_alloc_list; while(sbp){ sbp->fmt_in_use = 0; sbp = sbp->fmt_next_alloc; }/* End While */ /* * Now for each displayed line, find the format structure associated with it. * Then find the line_buffer structure which owns that format structure and * mark all of the format structures under it as in use. */ for(wptr = window_list ; wptr != NULL; wptr = wptr->win_next_window){ sp = saved_screen + wptr->win_y_base; for(i = wptr->win_y_base; i < wptr->win_y_end; i++,sp++){ sbp = sp->companion; if(sbp == NULL) continue; if(sbp->fmt_permanent) continue; if(sbp->fmt_buffer_line == NULL) continue; sbp = screen_find_window_format_line( wptr, sbp->fmt_buffer_line ); if(sbp == NULL) continue; while(sbp){ sbp->fmt_in_use = 1; sbp = sbp->fmt_next_line; }/* End While */ }/* End FOR */ }/* End FOR */ /* * Now go back and deallocate any format_line structures which are not in use */ sbp = format_line_alloc_list; tsbp = NULL; while(sbp){ if(sbp->fmt_in_use){ tsbp = sbp; sbp = sbp->fmt_next_alloc; continue; }/* End IF */ lbp = sbp->fmt_buffer_line; screen_free_window_format_lines(sbp->fmt_window_ptr,lbp); sbp = tsbp ? tsbp : format_line_alloc_list; }/* End While */ }/* End Routine */ /** * \brief Deallocate a window's format lines * * This routine is called to deallocate the list of format lines * linked off of a line buffer, if they are associated with the * specified window. */ void screen_free_window_format_lines( struct window *wptr, struct buff_line *lbp ) { register struct format_line *sbp,*tsbp; PREAMBLE(); /* * There are a couple cases to check for: The first situation is when * the very first format_line (chained off the buff_line structure) is * the one we want to clobber. */ sbp = lbp->format_line; if(sbp == NULL) return; if(sbp->fmt_window_ptr == wptr){ format_lines_invalid = 1; tsbp = sbp->fmt_next_window; lbp->format_line = tsbp; if(tsbp) tsbp->fmt_prev_window = NULL; sbp->fmt_next_window = NULL; screen_free_format_lines(sbp); format_lines_invalid = 0; return; }/* End IF */ /* * Here if it's not the head of the list. We want to keep the forward * and backward pointers consistent, so we need to treat this case a * little differently. */ while((tsbp = sbp->fmt_next_window)){ if(tsbp->fmt_window_ptr == wptr){ format_lines_invalid = 1; sbp->fmt_next_window = tsbp->fmt_next_window; if(tsbp->fmt_next_window){ tsbp->fmt_next_window->fmt_prev_window = sbp; }/* End IF */ tsbp->fmt_next_window = tsbp->fmt_prev_window = NULL; screen_free_format_lines(tsbp); format_lines_invalid = 0; return; }/* End IF */ sbp = tsbp; }/* End While */ }/* End Routine */ /** * \brief Deallocate a list of format lines * * This routine will clean up all the storage associated with a list of * format lines. It is called with the address of the head of the list, * and it will continue on down until the entire list is cleaned up. * It will also take care of cleaning up any companion pointers from the * screen array. */ void screen_free_format_lines( struct format_line *sbp ) { register struct format_line *osbp; register struct format_line *next_win; PREAMBLE(); /* * Loop on down the list till we hit the tail */ while(sbp){ /* * If this structure is tied to one of the current screen lines, break the * connection. */ if(sbp->fmt_saved_line){ if(sbp->fmt_saved_line->companion != sbp){ fprintf(stderr, "sbp->fmt_saved_line %p != sbp->...companion %p\n", sbp->fmt_saved_line, sbp->fmt_saved_line->companion); CAUSE_BUS_ERROR(); }/* End IF */ sbp->fmt_saved_line->companion = NULL; sbp->fmt_saved_line = NULL; }/* End IF */ next_win = sbp->fmt_next_window; if(next_win){ sbp->fmt_next_window = NULL; sbp->fmt_prev_window = NULL; if(sbp->fmt_next_line){ sbp->fmt_next_line->fmt_next_window = next_win; }/* End IF */ }/* End IF */ /* * Take it off the head of the list and place it on the free list. */ MAGIC_UPDATE(sbp, MAGIC_FORMAT_LOOKASIDE); osbp = sbp; sbp = sbp->fmt_next_line; if(sbp == NULL) sbp = next_win; osbp->fmt_next_line = format_line_free_list; format_line_free_list = osbp; /* * Remove it from the allocated list. This list is used to find all * the format_line structures which are outstanding at any given time. */ if(osbp->fmt_prev_alloc){ osbp->fmt_prev_alloc->fmt_next_alloc = osbp->fmt_next_alloc; }/* End IF */ else if(format_line_alloc_list == osbp){ format_line_alloc_list = osbp->fmt_next_alloc; }/* End Else */ if(osbp->fmt_next_alloc){ osbp->fmt_next_alloc->fmt_prev_alloc = osbp->fmt_prev_alloc; }/* End IF */ osbp->fmt_next_alloc = osbp->fmt_prev_alloc = NULL; }/* End While */ }/* End Routine */ void screen_check_format_lines( struct format_line *sbp, int who ) { PREAMBLE(); /* * Loop on down the list till we hit the tail */ while(sbp){ /* * If this structure is tied to one of the current screen lines, break the * connection. */ if(sbp->fmt_saved_line){ if(sbp->fmt_saved_line->companion != sbp){ fprintf(stderr, "w%d sbp->fmt_saved_line %p != sbp->...companion %p\n", who, sbp->fmt_saved_line, sbp->fmt_saved_line->companion); CAUSE_BUS_ERROR(); }/* End IF */ }/* End IF */ /* * Take it off the head of the list and place it on the free list. */ sbp = sbp->fmt_next_line; }/* End While */ } /** * This routine frees up any format_lines we've cached on our local * lookaside list. */ void screen_deallocate_format_lookaside_list() { register struct format_line *sbp; PREAMBLE(); while((sbp = format_line_free_list) != NULL){ format_line_free_list = sbp->fmt_next_line; MAGIC_UPDATE(sbp, 0); tec_release(TYPE_C_SCREENBUF,(char *)sbp->fmt_buffer); tec_release(TYPE_C_SCREEN,(char *)sbp); }/* End While */ }/* End Routine */ /** * \brief Finds format lines for a particular window * * Each buffer line has chained off of it the format lines which hold * the data as it appears on the screen. In order to handle multiple * windows, we need the ability to have a format line per visible * window. Thus, from the buff_line structure, we chain off a list * of format lines. Since a buffer line may need many format lines to * represent it (because it may wrap) we need a list. In addition, * we want to have one of these lists on a per-window basis, so we * have a pointer to the next list of format lines for another window. */ struct format_line * screen_find_window_format_line( struct window *wptr, struct buff_line *lbp ) { struct format_line *sbp; PREAMBLE(); sbp = lbp->format_line; while(sbp){ if(sbp->fmt_window_ptr == wptr) return(sbp); sbp = sbp->fmt_next_window; }/* End While */ return(NULL); }/* End Routine */ /** * \brief Create the format_line structures for a buff line * * This routine is called with the address of a buff_line structure. It * creates the format_line structures which represent the printed version * of the data, i.e., ready to be written to the terminal. */ int screen_format_buff_line( struct window *wptr, struct buff_line *lbp ) { register struct format_line *sbp; register char *cp; register short *sp; register unsigned int c,c_data; register int i; int byte_count; int current_column; char *multi_byte_echo; int multi_byte_echo_reverse; char expand_buffer[MAXOF(32,MAX_TAB_WIDTH+1)]; PREAMBLE(); /* * If we're called with a null lbp, we've run out of memory or something, * and we just punt */ if(lbp == NULL) return(FAIL); /* * If there is an old format structure hanging around (doubtful), make it * go away. Note that this has been modified to try to deal with the new * scheme where we have a different set of format structures for each * window. */ if(lbp->format_line){ screen_free_window_format_lines(wptr,lbp); }/* End IF */ sbp = allocate_format_buffer(wptr,lbp); if(sbp == NULL) return(FAIL); sbp->fmt_next_window = lbp->format_line; if(sbp->fmt_next_window){ sbp->fmt_next_window->fmt_prev_window = sbp; }/* End IF */ lbp->format_line = sbp; sp = sbp->fmt_buffer; cp = lbp->buffer; byte_count = lbp->byte_count; c = ' '; current_column = 0; multi_byte_echo = NULL; multi_byte_echo_reverse = 0; while(byte_count > 0 || multi_byte_echo){ if(current_column >= term_columns){ sbp->fmt_next_line = allocate_format_buffer(wptr,lbp); if(sbp->fmt_next_line == NULL) return(FAIL); sbp->fmt_next_line->fmt_prev_line = sbp; sbp = sbp->fmt_next_line; sp = sbp->fmt_buffer; current_column = 0; }/* End IF */ if(multi_byte_echo){ /* * All multi-byte echos are for Escape ($) or ^x control chars. * Therefore they can be printed in reverse like in SciTECO. */ c = *multi_byte_echo++; if(multi_byte_echo_reverse) c |= SCREEN_M_REVERSE; if(*multi_byte_echo == '\0'){ multi_byte_echo = NULL; multi_byte_echo_reverse = 0; } }/* End IF */ else { c = *cp++; byte_count -= 1; }/* End Else */ /* without possible SCREEN_M_REVERSE flags */ c_data = c & SCREEN_M_DATA; switch(c_data){ case '\t': i = tab_width - ( current_column % tab_width ); multi_byte_echo = tab_expand + MAX_TAB_WIDTH - i; break; case ESCAPE: multi_byte_echo = "$"; multi_byte_echo_reverse = 1; break; case '\n': *sp++ = c; current_column = 0; break; default: if(c_data < ' '){ if( hide_cr_flag ){ expand_buffer[0] = ' '; expand_buffer[1] = '\0'; } else { expand_buffer[0] = '^'; expand_buffer[1] = c_data + 'A' - 1; expand_buffer[2] = '\0'; multi_byte_echo_reverse = 1; } multi_byte_echo = expand_buffer; continue; }/* End IF */ /* * If the high bit is on, display it as the code with that bit stripped off, * or as '.' if it is a non-printable character. We display it reverse video * if the terminal is not a brain damaged 'magic cookie' terminal. */ if(eight_bit_output_flag == NO && c_data >= 128){ c = c_data - 128; if(isprint(c)) c |= SCREEN_M_REVERSE; else c = '.' | SCREEN_M_REVERSE; #ifdef TERMCAP if(termcap_sg){ c &= ~SCREEN_M_REVERSE; }/* End IF */ #endif }/* End IF */ *sp++ = c; current_column += 1; break; }/* End Switch */ }/* End While */ if(c != '\n'){ if(current_column < (term_columns - 1)){ *sp++ = '\n'; }/* End IF */ }/* End IF */ return(SUCCESS); }/* End Routine */ /** * \brief Find position of dot in a format_line * * There are several times that we need to find the format buffer that is * associated with dot. This routine determines which format_line dot is * on, and the offset onto that line (in bytes). */ int screen_find_dot( struct window *wptr ) { register struct buff_line *lbp; register struct format_line *sbp; register char *cp; register unsigned char c; int i; int byte_count; int current_column; char *multi_byte_echo; char expand_buffer[32]; struct buff_header *hbp = wptr->win_buffer; PREAMBLE(); /* * Find the line buffer and format buffer structures */ lbp = buff_find_line(hbp,hbp->dot); sbp = screen_find_window_format_line(wptr,lbp); if(sbp == NULL){ if(screen_format_buff_line(wptr,lbp) == FAIL) return(FAIL); sbp = screen_find_window_format_line(wptr,lbp); }/* End IF */ cp = lbp->buffer; byte_count = buff_find_offset(hbp,lbp,hbp->dot); current_column = 0; multi_byte_echo = NULL; while(byte_count > 0 || multi_byte_echo){ if(current_column >= term_columns){ sbp = sbp->fmt_next_line; current_column = 0; }/* End IF */ if(multi_byte_echo){ c = *multi_byte_echo++; if(*multi_byte_echo == '\0') multi_byte_echo = NULL; }/* End IF */ else { c = *cp++; byte_count -= 1; }/* End Else */ switch(c){ case '\t': i = tab_width - ( current_column % tab_width ); multi_byte_echo = tab_expand + MAX_TAB_WIDTH - i; break; case ESCAPE: multi_byte_echo = "$"; break; case '\n': current_column = 0; break; default: if(c < ' '){ expand_buffer[0] = '^'; expand_buffer[1] = c + 'A' - 1; expand_buffer[2] = '\0'; multi_byte_echo = expand_buffer; continue; }/* End IF */ current_column += 1; break; }/* End Switch */ }/* End While */ wptr->win_dot_format_line = sbp; wptr->win_dot_screen_offset = current_column; return(SUCCESS); }/* End Routine */ /** * \brief Routine to return a format_line structure * * This routine will return a format_line structure to the caller. It * first looks on the free list, and if there is not one there, it will * create one the hard way. */ struct format_line * allocate_format_buffer( struct window *wptr, struct buff_line *lbp ) { register struct format_line *sbp; struct buff_header *hbp = wptr->win_buffer; PREAMBLE(); sbp = format_line_free_list; if(sbp != NULL){ if(sbp->fmt_buffer_size == (size_t)term_columns){ format_line_free_list = sbp->fmt_next_line; } else { screen_deallocate_format_lookaside_list(); sbp = NULL; } }/* End IF */ if(sbp == NULL){ sbp = (struct format_line *) tec_alloc(TYPE_C_SCREEN,sizeof(struct format_line)); if(sbp == NULL) return(NULL); memset(sbp,0,sizeof(*sbp)); sbp->fmt_buffer_size = term_columns; sbp->fmt_buffer = (short *) tec_alloc( TYPE_C_SCREENBUF, (int)(sizeof(short) * sbp->fmt_buffer_size) ); if(sbp->fmt_buffer == NULL){ tec_release(TYPE_C_SCREEN,(char *)sbp); return(NULL); }/* End IF */ }/* End Else */ MAGIC_UPDATE(sbp, MAGIC_FORMAT); sbp->fmt_owning_buffer = hbp; sbp->fmt_buffer_line = lbp; sbp->fmt_sequence = screen_sequence; sbp->fmt_next_line = sbp->fmt_prev_line = NULL; sbp->fmt_next_window = sbp->fmt_prev_window = NULL; sbp->fmt_window_ptr = wptr; sbp->fmt_saved_line = NULL; sbp->fmt_visible_line_position = -1; sbp->fmt_permanent = 0; /* * Place this at the head of the allocated queue */ sbp->fmt_next_alloc = format_line_alloc_list; sbp->fmt_prev_alloc = NULL; if(sbp->fmt_next_alloc) sbp->fmt_next_alloc->fmt_prev_alloc = sbp; format_line_alloc_list = sbp; return(sbp); }/* End Routine */ /** * \brief Cause a redraw of the screen * * This routine is called when the screen may have become corrupted * and needs to be refreshed. */ void screen_redraw() { register struct screen_line *lp; register short *sp; register int i,j; PREAMBLE(); term_goto(0,0); term_clrtobot(); for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ sp = lp->buffer; for(j = 0; j < term_columns; j++,sp++) *sp = ' '; lp->sequence = 0; }/* End FOR */ screen_format_windows(); screen_refresh(); }/* End Routine */ /** * \brief Create a window data structure * * This function is called to create a window data structure which * then occupies some portion of the screen. The initial structure * starts out owning all but the reserved lines, but then lines are * stolen from it as it is split into other windows. */ struct window * create_window( int x_size, int y_size ) { register struct window *wptr; register int i; PREAMBLE(); wptr = (struct window *)tec_alloc(TYPE_C_WINDOW,sizeof(struct window)); if(wptr == NULL) return(NULL); memset(wptr,0,sizeof(*wptr)); wptr->win_label_line.fmt_buffer = (short *) tec_alloc(TYPE_C_SCRBUF,(int)(sizeof(short) * x_size)); if(wptr->win_label_line.fmt_buffer == NULL){ tec_release(TYPE_C_WINDOW,(char *)wptr); return(NULL); }/* End IF */ wptr->win_window_number = next_window_number++; /* * Initialize the xy size of the window. The y gets set to size-1 to leave * room for the label line. */ wptr->win_x_size = x_size; wptr->win_y_size = y_size - 1; wptr->win_y_end = wptr->win_y_base + wptr->win_y_size; /* * Initialize the label line structure. Each window has one of these in * reverse video showing which buffer is being displayed. */ wptr->win_label_line.fmt_permanent = 1; wptr->win_label_line.fmt_visible_line_position = -1; wptr->win_label_line.fmt_window_ptr = wptr; /* FIX */ wptr->win_label_line.fmt_buffer_size = x_size; for(i = 0; i < x_size; i++){ wptr->win_label_line.fmt_buffer[i] = ' ' | SCREEN_M_REVERSE; }/* End FOR */ wptr->win_label_line.fmt_sequence = screen_sequence; return(wptr); }/* End Routine */ /** * \brief Make two where there is only one * * This routine is called when the user wants to split the current window * into two. This allows him to display several buffers simultaneously. */ struct window * screen_split_window( struct window *old_wptr, int lines, int buffer_number ) { register int i; register struct window *new_wptr; register struct screen_line *lp; PREAMBLE(); if(old_wptr->win_y_size < SCREEN_MINIMUM_WINDOW_HEIGHT){ error_message("Window is already as small as possible"); return(NULL); }/* End IF */ if(lines < SCREEN_MINIMUM_WINDOW_HEIGHT){ lines = SCREEN_MINIMUM_WINDOW_HEIGHT; }/* End If */ if((old_wptr->win_y_size - (lines + 1)) < SCREEN_MINIMUM_WINDOW_HEIGHT){ error_message("Resulting windows would be too small"); return(NULL); }/* End IF */ new_wptr = create_window(old_wptr->win_x_size,old_wptr->win_y_size-lines); new_wptr->win_next_window = window_list; window_list = new_wptr; new_wptr->win_buffer = old_wptr->win_buffer; /* * Take the lines of the new window away from the old window */ old_wptr->win_y_size -= new_wptr->win_y_size + 1; /* * Have the new window start above the old one */ new_wptr->win_y_base = old_wptr->win_y_base; old_wptr->win_y_base += new_wptr->win_y_size + 1; /* * Recalculate the ends of each window */ old_wptr->win_y_end = old_wptr->win_y_base + old_wptr->win_y_size; new_wptr->win_y_end = new_wptr->win_y_base + new_wptr->win_y_size; /* * Initialize the label line structure. Each window has one of these in * reverse video showing which buffer is being displayed. */ for(i = 0; i < new_wptr->win_x_size; i++){ new_wptr->win_label_line.fmt_buffer[i] = old_wptr->win_label_line.fmt_buffer[i]; }/* End FOR */ for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ if(old_wptr->win_label_field_contents[i]){ screen_label_line(old_wptr->win_buffer, old_wptr->win_label_field_contents[i],i); }/* End IF */ }/* End FOR */ screen_label_window(); lp = saved_screen + new_wptr->win_y_end; if(new_wptr->win_label_line.fmt_saved_line){ new_wptr->win_label_line.fmt_saved_line->companion = NULL; }/* End IF */ new_wptr->win_label_line.fmt_saved_line = lp; if(lp->companion) lp->companion->fmt_saved_line = NULL; lp->companion = &new_wptr->win_label_line; new_wptr->win_label_line.fmt_sequence = screen_sequence; if(buffer_number){ buff_openbuffnum(buffer_number,0); }/* End IF */ return(new_wptr); }/* End Routine */ /** * \brief Here to delete a window * * This function is called when a user wants to delete a window. * The space gets given back to the window next to it. */ void screen_delete_window( struct window *old_wptr ) { register int i; register struct window *new_wptr; register struct screen_line *lp; PREAMBLE(); if((old_wptr->win_y_size + 1) == (term_lines - SCREEN_RESERVED_LINES)){ error_message("Illegal to delete the final window"); return; }/* End IF */ /* * Find the old_wptr on the list of windows, and remove it */ if(window_list == old_wptr) window_list = old_wptr->win_next_window; else { new_wptr = window_list; while(new_wptr->win_next_window != old_wptr){ new_wptr = new_wptr->win_next_window; }/* End While */ new_wptr->win_next_window = old_wptr->win_next_window; }/* End Else */ /* * Remove the label line from the screen display */ lp = old_wptr->win_label_line.fmt_saved_line; lp->companion = NULL; old_wptr->win_label_line.fmt_saved_line = NULL; /* * If the window is at the bottom of the screen, we will combine it with * the window above it. */ if((old_wptr->win_y_end + 1) == (term_lines - SCREEN_RESERVED_LINES)){ new_wptr = window_list; while(new_wptr){ if((new_wptr->win_y_end + 1) == old_wptr->win_y_base) break; new_wptr = new_wptr->win_next_window; }/* End While */ if(!new_wptr){ error_message("Failure to find window above"); return; }/* End IF */ lp = new_wptr->win_label_line.fmt_saved_line; lp->companion = NULL; new_wptr->win_y_size += old_wptr->win_y_size + 1; new_wptr->win_y_end = new_wptr->win_y_base + new_wptr->win_y_size; lp = saved_screen + new_wptr->win_y_end; lp->companion = &new_wptr->win_label_line; new_wptr->win_label_line.fmt_saved_line = lp; }/* End IF */ /* * Else, we combine it with the window below it */ else { new_wptr = window_list; while(new_wptr){ if(new_wptr->win_y_base == (old_wptr->win_y_end + 1)) break; new_wptr = new_wptr->win_next_window; }/* End While */ if(!new_wptr){ error_message("Failure to find window below"); return; }/* End IF */ lp = new_wptr->win_label_line.fmt_saved_line; lp->companion = NULL; new_wptr->win_y_base -= old_wptr->win_y_size + 1; new_wptr->win_y_size += old_wptr->win_y_size + 1; new_wptr->win_y_end = new_wptr->win_y_base + new_wptr->win_y_size; lp = saved_screen + new_wptr->win_y_end; lp->companion = &new_wptr->win_label_line; new_wptr->win_label_line.fmt_saved_line = lp; }/* End IF */ /* * Release any memory used to hold the strings in the label line */ for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ if(old_wptr->win_label_field_contents[i]){ tec_release( TYPE_C_LABELFIELD, old_wptr->win_label_field_contents[i] ); old_wptr->win_label_field_contents[i] = NULL; }/* End IF */ }/* End FOR */ /* * Release the buffer used to hold formatted data */ tec_release(TYPE_C_SCRBUF,(char *)old_wptr->win_label_line.fmt_buffer); tec_release(TYPE_C_WINDOW,(char *)old_wptr); curwin = new_wptr; curbuf = new_wptr->win_buffer; }/* End Routine */ /** * \brief Switch to a new window * * This function is called to switch the current window to a different * window. */ int window_switch( int window_number ) { register struct window *wptr; PREAMBLE(); /* * If the window number is specified as zero, just select the next window * on the list. */ if(window_number == 0){ if((wptr = curwin->win_next_window) == NULL) wptr = window_list; window_number = wptr->win_window_number; }/* End IF */ wptr = window_list; /* * Search the list until we find the correct window. */ while(wptr && wptr->win_window_number != window_number){ wptr = wptr->win_next_window; }/* End While */ /* * If the search failed, give up. */ if(wptr == NULL){ char tmp_message[LINE_BUFFER_SIZE]; sprintf(tmp_message,"can't switch to window %d",window_number); error_message(tmp_message); return(FAIL); }/* End IF */ /* * Check to see that we actually found the right window. */ if(wptr->win_window_number != window_number) return(FAIL); /* * Switch the current window and edit buffer to the newly selected window. */ curwin = wptr; return(SUCCESS); }/* End Routine */