diff options
-rw-r--r-- | config.h | 225 | ||||
-rw-r--r-- | poll.h | 19 | ||||
-rw-r--r-- | syspoll.h | 153 | ||||
-rw-r--r-- | tecbuf.c | 2710 | ||||
-rw-r--r-- | teccmd.c | 2668 | ||||
-rw-r--r-- | tecdebug.c | 401 | ||||
-rw-r--r-- | tecdisp.c | 2867 | ||||
-rw-r--r-- | tecexec.c | 2801 | ||||
-rw-r--r-- | tecmem.c | 456 | ||||
-rw-r--r-- | teco.c | 1888 | ||||
-rw-r--r-- | teco.h | 657 | ||||
-rw-r--r-- | tecparse.c | 2178 | ||||
-rw-r--r-- | tecparse.h | 327 | ||||
-rw-r--r-- | tecstate.c | 2374 | ||||
-rw-r--r-- | tecterm.c | 1579 | ||||
-rw-r--r-- | tecundo.c | 504 | ||||
-rw-r--r-- | tecvms.h | 51 |
17 files changed, 21858 insertions, 0 deletions
diff --git a/config.h b/config.h new file mode 100644 index 0000000..708cd2d --- /dev/null +++ b/config.h @@ -0,0 +1,225 @@ +/* config.h. Generated by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if the `closedir' function returns void instead of `int'. */ +/* #undef CLOSEDIR_VOID */ + +/* Define to 1 if `TIOCGWINSZ' requires <sys/ioctl.h>. */ +/* #undef GWINSZ_IN_SYS_IOCTL */ + +/* Define to 1 if you have the `alarm' function. */ +#define HAVE_ALARM 1 + +/* Define to 1 if you have the `bzero' function. */ +#define HAVE_BZERO 1 + +/* Define to 1 if you have the <ctype.h> header file. */ +#define HAVE_CTYPE_H 1 + +/* Define to 1 if you have the declaration of `cfgetospeed', and to 0 if you + don't. */ +#define HAVE_DECL_CFGETOSPEED 1 + +/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'. + */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the <errno.h> header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the <fcntl.h> header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `fork' function. */ +#define HAVE_FORK 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `ncurses' library (-lncurses). */ +#define HAVE_LIBNCURSES 1 + +/* Define to 1 if you have the `termcap' library (-ltermcap). */ +#define HAVE_LIBTERMCAP 1 + +/* Define to 1 if you have the `terminfo' library (-lterminfo). */ +/* #undef HAVE_LIBTERMINFO */ + +/* Define to 1 if you support file names longer than 14 characters. */ +#define HAVE_LONG_FILE_NAMES 1 + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#define HAVE_MALLOC 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkdir' function. */ +#define HAVE_MKDIR 1 + +/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Define to 1 if you have the <poll.h> header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have the <pwd.h> header file. */ +#define HAVE_PWD_H 1 + +/* Define to 1 if you have the `sbrk' function. */ +#define HAVE_SBRK 1 + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define to 1 if you have the <sgtty.h> header file. */ +#define HAVE_SGTTY_H 1 + +/* Define to 1 if you have the <signal.h> header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +/* #undef HAVE_STAT_EMPTY_STRING_BUG */ + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdio.h> header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if `c_cflag' is member of `struct termios'. */ +#define HAVE_STRUCT_TERMIOS_C_CFLAG 1 + +/* Define to 1 if `c_ospeed' is member of `struct termios'. */ +#define HAVE_STRUCT_TERMIOS_C_OSPEED 1 + +/* Define to 1 if `sg_ospeed' is member of `struct termios'. */ +/* #undef HAVE_STRUCT_TERMIOS_SG_OSPEED */ + +/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the <sys/file.h> header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define to 1 if you have the <sys/filio.h> header file. */ +#define HAVE_SYS_FILIO_H 1 + +/* Define to 1 if you have the <sys/ioctl.h> header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the <sys/param.h> header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the <sys/select.h> header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the `tcgetattr' function. */ +#define HAVE_TCGETATTR 1 + +/* Define to 1 if you have the `tcsetattr' function. */ +#define HAVE_TCSETATTR 1 + +/* Define to 1 if you have the <termcap.h> header file. */ +#define HAVE_TERMCAP_H 1 + +/* Define to 1 if you have the <termios.h> header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the <termio.h> header file. */ +/* #undef HAVE_TERMIO_H */ + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vfork' function. */ +#define HAVE_VFORK 1 + +/* Define to 1 if you have the <vfork.h> header file. */ +/* #undef HAVE_VFORK_H */ + +/* Define to 1 if `fork' works. */ +/* #undef HAVE_WORKING_FORK */ + +/* Define to 1 if `vfork' works. */ +#define HAVE_WORKING_VFORK 1 + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +/* #undef LSTAT_FOLLOWS_SLASHED_SYMLINK */ + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "paul@copters.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "VTECO" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "VTECO 6.4" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "vteco" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "6.4" + +/* Define as the return type of signal handlers (`int' or `void'). */ +#define RETSIGTYPE void + +/* Define to the type of arg 1 for `select'. */ +#define SELECT_TYPE_ARG1 int + +/* Define to the type of args 2, 3 and 4 for `select'. */ +#define SELECT_TYPE_ARG234 (fd_set *) + +/* Define to the type of arg 5 for `select'. */ +#define SELECT_TYPE_ARG5 (struct timeval *) + +/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */ +/* #undef STAT_MACROS_BROKEN */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */ +#define TIME_WITH_SYS_TIME 1 + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to rpl_malloc if the replacement function should be used. */ +/* #undef malloc */ + +/* Define to `int' if <sys/types.h> does not define. */ +/* #undef pid_t */ + +/* Define as `fork' if `vfork' does not work. */ +/* #undef vfork */ @@ -0,0 +1,19 @@ +/* Copyright (c) 1988 AT&T */ +/* All Rights Reserved */ + +/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ +/* The copyright notice above does not evidence any */ +/* actual or intended publication of such source code. */ + +#ifndef _POLL_H +#define _POLL_H + +#pragma ident "@(#)poll.h 1.7 92/07/14 SMI" /* SVr4.0 1.2 */ + +/* + * Poll system call interface definitions. + */ + +#include "syspoll.h" + +#endif /* _POLL_H */ diff --git a/syspoll.h b/syspoll.h new file mode 100644 index 0000000..68e4884 --- /dev/null +++ b/syspoll.h @@ -0,0 +1,153 @@ +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ +/* The copyright notice above does not evidence any */ +/* actual or intended publication of such source code. */ + +/* + * Copyright (c) 1995 by Sun Microsystems, Inc. + * All rights reserved. + */ + +#ifndef _SYS_POLL_H +#define _SYS_POLL_H + +#pragma ident "@(#)poll.h 1.24 97/04/18 SMI" /* SVr4.0 11.9 */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Structure of file descriptor/event pairs supplied in + * the poll arrays. + */ +typedef struct pollfd { + int fd; /* file desc to poll */ + short events; /* events of interest on fd */ + short revents; /* events that occurred on fd */ +} pollfd_t; + +typedef unsigned long nfds_t; + +/* + * Testable select events + */ +#define POLLIN 0x0001 /* fd is readable */ +#define POLLPRI 0x0002 /* high priority info at fd */ +#define POLLOUT 0x0004 /* fd is writeable (won't block) */ +#define POLLRDNORM 0x0040 /* normal data is readable */ +#define POLLWRNORM POLLOUT +#define POLLRDBAND 0x0080 /* out-of-band data is readable */ +#define POLLWRBAND 0x0100 /* out-of-band data is writeable */ + +#define POLLNORM POLLRDNORM + +/* + * Non-testable poll events (may not be specified in events field, + * but may be returned in revents field). + */ +#define POLLERR 0x0008 /* fd has error condition */ +#define POLLHUP 0x0010 /* fd has been hung up on */ +#define POLLNVAL 0x0020 /* invalid pollfd entry */ + +#ifdef _KERNEL + +/* + * Additional private poll flags supported only by strpoll(). + * Must be bit-wise distinct from the above POLL flags. + */ +#define POLLRDDATA 0x200 /* Wait for M_DATA; ignore M_PROTO only msgs */ +#define POLLNOERR 0x400 /* Ignore POLLERR conditions */ + +#endif /* _KERNEL */ + +#if defined(_KERNEL) || defined(_KMEMUSER) + +#include <sys/t_lock.h> +#include <sys/thread.h> + +/* + * Poll list head structure. A pointer to this is passed to + * pollwakeup() from the caller indicating an event has occurred. + * Only the ph_list field is used, but for DDI compliance, we can't + * change the size of the structure. + */ +typedef struct pollhead { + struct polldat *ph_list; /* list of pollers */ + struct polldat *ph_dummy; /* unused -- see above */ + short ph_events; /* unused -- see above */ +} pollhead_t; + +/* + * Data necessary to notify process sleeping in poll(2) + * when an event has occurred. + */ +typedef struct polldat { + kthread_t *pd_thread; /* thread doing poll */ + int pd_events; /* events being polled */ + struct polldat *pd_next; /* next in poll list */ + struct polldat *pd_prev; /* previous in poll list */ + struct pollhead *pd_headp; /* backpointer to head of list */ + struct pollhead *pd_sphp; /* stored pollhead struct pointer */ +} polldat_t; + +/* + * State information kept by each polling thread + */ +typedef struct pollstate { + int ps_nfds; + int ps_flag; + pollfd_t *ps_pollfd; + polldat_t *ps_polldat; + kmutex_t ps_lock; /* mutex for the polling thread */ + kmutex_t ps_no_exit; /* protects ps_busy*, can't be nested */ + int ps_busy; /* can only exit when its 0 */ + kcondvar_t ps_busy_cv; /* cv to wait on if ps_busy != 0 */ + kcondvar_t ps_cv; /* cv to wait on if needed */ +} pollstate_t; + +/* ps_flag */ +#define T_POLLTIME 0x01 /* poll timeout pending */ +#define T_POLLWAKE 0x02 /* pollwakeup() occurred */ + +#if defined(_KERNEL) + +/* + * Routine called to notify a process of the occurrence + * of an event. + */ +extern void pollwakeup(pollhead_t *, short); +/* + * pollwakeup_safe will replace pollwakeup when the support + * for unsafe drivers is removed. + */ +extern void pollwakeup_safe(pollhead_t *, short); + +/* + * Internal routines. + */ +extern void polltime(kthread_t *); +extern void pollrun(kthread_t *); +extern void polldel(pollstate_t *); +extern void polllock(pollhead_t *, kmutex_t *); +extern int pollunlock(pollhead_t *); +extern void pollrelock(pollhead_t *, int); +extern void pollcleanup(kthread_t *); + +extern void ppwaitemptylist(pollhead_t *); + +#endif /* defined(_KERNEL) */ + +#endif /* defined(_KERNEL) || defined(_KMEMUSER) */ + +#if defined(__STDC__) && !defined(_KERNEL) +int poll(struct pollfd *, nfds_t, int); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_POLL_H */ diff --git a/tecbuf.c b/tecbuf.c new file mode 100644 index 0000000..1dd93e9 --- /dev/null +++ b/tecbuf.c @@ -0,0 +1,2710 @@ +char *tecbuf_c_version = "tecbuf.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecbuf.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecbuf.c + * Subroutines to handle the edit buffers + * + * COPYRIGHT (c) 1985-2004 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +char rcs_date[] = AUTO_DATE; + +/* + * Global Storage is defined here for lack of a better place. + * First off are all the flags. + */ + +/* + * Global Variables + */ + struct buff_header *buffer_headers; + struct buff_header *curbuf; + struct buff_header *qregister_push_down_list; + +/* + * Global structures + */ + struct buff_line *line_buffer_lookaside_list = NULL; +/* + * Forward References + */ + struct buff_line *allocate_line_buffer(int); + void movc3(char *,char *,int); + void buff_buffer_map( void ); + unsigned int stringHash( char *str ); + + extern FILE *teco_debug_log; + extern int term_columns; + extern struct window *curwin; + extern char checkpoint_modified; + +/* BUFF_FIND - Find the named buffer + * + * Function: + * + * This routine is called with the name of the buffer that we want to + * find. It searches all the buffer headers until it finds the name. + * It will either return the address of the buffer header, or null if + * it could not find one with the proper name. + */ +struct buff_header * +buff_find(name) +char *name; +{ +register struct buff_header *bp; +register char *cp1,*cp2; +unsigned int hash = stringHash( name ); + + PREAMBLE(); + for(bp = buffer_headers; bp != NULL; bp = bp->next_header){ + if( hash != bp->buf_hash ) + { +// printf("skipping because hash 0x%08x != 0x%08x %s %s\n", +// hash, bp->buf_hash, name, bp->name); + continue; + } + + printf("hash match! %s %s\n",name,bp->name); +/* + * This loop implements the equivalent of strcmp, except that in the + * case of VMS, it is case insensitive. + */ + for(cp1 = name, cp2 = bp->name;;cp1++,cp2++){ +#ifdef VMS + if(UPCASE(*cp1) != UPCASE(*cp2)) break; +#else + if(*cp1 != *cp2) break; +#endif + if(*cp1 == '\0') return(bp); + }/* End FOR */ + + }/* End FOR */ + + return(NULL); + +}/* End Routine */ + + + +/* BUFF_QFIND - Find the named Q register + * + * Function: + * + * This routine is called with the name of the Q register that we + * want to find. It constructs the internal name of the Q register + * and then calls buff_find to find the buffer. If the buffer is not + * found and the create flag is set, we call buff_create to cause + * the Q register to be created. + */ +struct buff_header * +buff_qfind(name,create_flag) +char name; +char create_flag; +{ +register struct buff_header *hbp; +register int i; +char tmp_buffer[LINE_BUFFER_SIZE],tmp_message[LINE_BUFFER_SIZE]; +struct buff_header *buff_create(); + + PREAMBLE(); + (void) strcpy(tmp_buffer,TECO_INTERNAL_BUFFER_NAME); + i = strlen(tmp_buffer); + name = UPCASE((int)name); + tmp_buffer[i] = name; tmp_buffer[i+1] = '\0'; + hbp = buff_find(tmp_buffer); + if(hbp == NULL){ + if(create_flag){ + hbp = buff_create(tmp_buffer,1); + if(hbp == (struct buff_header *)NULL){ + sprintf(tmp_message,"?Cannot create Q-register %c",name); + error_message(tmp_message); + return((struct buff_header *)NULL); + }/* End IF */ + }/* End IF */ + }/* End IF */ + + return(hbp); + +}/* End Routine */ + + + +/* BUFF_CREATE - Create a new buffer + * + * Function: + * + * This routine is called when we need to create a new buffer. The caller + * should have already verified that this doesn't already exist. The + * routine returns the address of the new buffer, or NULL if there is a + * problem of some sort. + */ +struct buff_header * +buff_create(name,internal_flag) +char *name; +char internal_flag; +{ +register struct buff_header *bp; +register struct buff_header *obp; +register struct buff_line *lbp; +register int i = 0; + + PREAMBLE(); + + if(buff_find(name)) return(NULL); + +/* + * Create the buffer header itself. + */ + bp = (struct buff_header *)tec_alloc( + TYPE_C_BHDR, + sizeof(struct buff_header) + ); + + if(bp == NULL) return(NULL); + bp->buf_magic = MAGIC_BUFFER; + bp->buf_hash = stringHash( name ); + + bp->name = tec_alloc(TYPE_C_CBUFF,strlen(name)+1); + if(bp->name == NULL){ + bp->buf_magic = 0; + tec_release(TYPE_C_BHDR,(char *)bp); + return(NULL); + }/* End IF */ + (void) strcpy(bp->name,name); + + obp = buffer_headers; + if(obp) i = obp->buffer_number; + while(obp){ + if(internal_flag){ + if(obp->buffer_number < i) i = obp->buffer_number; + } + else { + if(obp->buffer_number > i) i = obp->buffer_number; + } + obp = obp->next_header; + }/* End While */ + + if(internal_flag) bp->buffer_number = i - 1; + else bp->buffer_number = i + 1; + + bp->ismodified = NO; + bp->isreadonly = NO; + bp->isbackedup = NO; + bp->dot = 0; + bp->zee = 0; + +/* + * Create the first line buffer structure + */ + lbp = allocate_line_buffer(1); + if(lbp == NULL){ + bp->buf_magic = 0; + tec_release(TYPE_C_CBUFF,bp->name); + tec_release(TYPE_C_BHDR,(char *)bp); + return((struct buff_header *)NULL); + }/* End IF */ + bp->first_line = lbp; +/* + * Initialize the rest of the special locations + */ + bp->pos_cache.lbp = NULL; + bp->pos_cache.base = 0; + +/* + * Link it into the list of buffer headers, in order by buffer number + */ + obp = buffer_headers; + while(obp){ + if(obp->next_header == NULL) break; + if(obp->next_header->buffer_number > bp->buffer_number) break; + obp = obp->next_header; + }/* End While */ + + if(obp == NULL || buffer_headers->buffer_number > bp->buffer_number){ + bp->next_header = buffer_headers; + buffer_headers = bp; + }/* End IF */ + + else { + bp->next_header = obp->next_header; + obp->next_header = bp; + }/* End Else */ + + return(bp); + +}/* End Routine */ + + + +/* BUFF_DUPLICATE - Make a duplicate of a buffer + * + * Function: + * + * This routine is called to duplicate the specified buffer and return + * a pointer to the duplicate. The current requirement for this is for + * the push Q register command ('['). + */ +struct buff_header * +buff_duplicate(sbp) +register struct buff_header *sbp; +{ +register struct buff_header *dbp; +register struct buff_line *slp; +register struct buff_line *dlp; + + PREAMBLE(); +/* + * Create the buffer header itself. + */ + dbp = (struct buff_header *) + tec_alloc(TYPE_C_BHDR,sizeof(struct buff_header)); + if(dbp == NULL){ + return((struct buff_header *)NULL); + }/* End IF */ + + dbp->name = tec_alloc(TYPE_C_CBUFF,strlen(sbp->name)+1); + if(dbp->name == NULL){ + tec_release(TYPE_C_BHDR,(char *)dbp); + return((struct buff_header *)NULL); + }/* End IF */ + + (void) strcpy(dbp->name,sbp->name); + + dbp->buf_magic = MAGIC_BUFFER; + dbp->ismodified = sbp->ismodified; + dbp->isreadonly = sbp->isreadonly; + dbp->isbackedup = sbp->isbackedup; + dbp->dot = sbp->dot; + dbp->zee = sbp->zee; + dbp->ivalue = sbp->ivalue; + dbp->pos_cache.lbp = NULL; + dbp->pos_cache.base = 0; + dbp->first_line = NULL; + +/* + * Copy the line buffer structures. If an allocation error occurs, we + * have to return all the allocated memory, and return an error. + */ + slp = sbp->first_line; + + if(slp){ + dbp->first_line = dlp = allocate_line_buffer(slp->byte_count); + if(dlp == NULL){ + buff_destroy(dbp); + return((struct buff_header *)NULL); + }/* End IF */ + movc3(slp->buffer,dlp->buffer,slp->byte_count); + dlp->byte_count = slp->byte_count; + + while((slp = slp->next_line)){ + dlp->next_line = allocate_line_buffer(slp->byte_count); + if(dlp->next_line == NULL){ + buff_destroy(dbp); + return((struct buff_header *)NULL); + }/* End IF */ + dlp->next_line->prev_line = dlp; + dlp = dlp->next_line; + movc3(slp->buffer,dlp->buffer,slp->byte_count); + dlp->byte_count = slp->byte_count; + }/* End While */ + + }/* End IF */ + + return(dbp); + +}/* End Routine */ + + + +/* MOVC3 - Copy the specified number of bytes + * + * Function: + * + * This routine copies 'n' bytes from the source to the + * destination. + */ +void +movc3(source,dest,count) +register char *source; +register char *dest; +register int count; +{ + + PREAMBLE(); + while(count-- > 0) *dest++ = *source++; + +}/* End Routine */ + + + +/* BUFF_DESTROY - Delete an existing buffer + * + * Function: + * + * This routine is called to delete an existing buffer. The routine is + * supplied the address of the buffer structure. We have to clean up + * all the lines in the buffer, all the display lines, etc. + */ +void +buff_destroy(hbp) +register struct buff_header *hbp; +{ +register struct buff_header *bp; +register struct buff_line *lbp; + + PREAMBLE(); +/* + * There are certain buffers we don't allow to be destroyed + */ + if(strcmp(hbp->name,"TECO_INTERNAL-Main") == 0){ + return; + }/* End IF */ +/* + * Clean up all the data in the buffer first + */ + lbp = hbp->first_line; + hbp->pos_cache.lbp = NULL; + hbp->first_line = NULL; + buff_free_line_buffer_list(lbp); + +/* + * Now unlink this buffer header from the header list. + * Check to see if it is at the head of the list. + */ + bp = buffer_headers; + if(bp == hbp){ + buffer_headers = bp->next_header; + }/* End IF */ + +/* + * If not at the head of the list, we have to search the list till + * we find it's father. Then we make it's father's child be it's + * child. + */ + else { + while(bp){ + if(bp->next_header == hbp){ + bp->next_header = hbp->next_header; + break; + }/* End IF */ + bp = bp->next_header; + }/* End While */ + + }/* End Else */ + +/* + * Now give back the storage for the name, and for the structure itself. + */ + if(hbp->name) tec_release(TYPE_C_CBUFF,(char *)hbp->name); + tec_release(TYPE_C_BHDR,(char *)hbp); + + return; + +}/* End Routine */ + + + +/* BUFF_FIND_LINE - Find the line structure that <position> is on + * + * Function: + * + * This routine is called to find the line_buffer structure that + * the buffer position resides on. + */ +struct buff_line * +buff_find_line(hbp,position) +register struct buff_header *hbp; +int position; +{ +register struct buff_line *lbp; +register int i; + + PREAMBLE(); + + if(hbp == NULL) return(NULL); + +/* + * First step is to see if the current position cache in the buffer header will + * help us out. Our position must be on this line if it is to be of help to us. + */ + lbp = hbp->pos_cache.lbp; + if(lbp){ + + i = hbp->pos_cache.base + lbp->byte_count; +/* + * Test to see if it is a currently existing character position that is mapped + * by our position cache. + */ + if(position >= hbp->pos_cache.base && position < i){ + return(lbp); + }/* End IF */ +/* + * If we are inserting at the end of the buffer, the position doesn't really + * exist, but we can still resolve it if we happen to have the final line + * cached. The only case where we don't want to do this is when the line + * already has a trailing newline. Then we want to fall down into the code + * below which will recognize this and construct a new line buffer. + */ + if(position == hbp->zee && position == i){ + if(lbp->byte_count == 0) return(lbp); + if(lbp->buffer[lbp->byte_count - 1] != '\n') return(lbp); + }/* End IF */ + + }/* End IF */ + +/* + * We get here if the current cached location does not help us out. In this + * case we need to hunt till we find the correct place in the buffer. We also + * update our cache to remember this. + */ + i = position; + lbp = hbp->first_line; + + if(lbp == NULL){ + return(NULL); + }/* End IF */ + +/* + * Now, even though the cache did not give us the correct line, it can still + * help us out in the search if it maps a position before the one we are + * looking for, since we can begin our search at it's base. + */ + if(hbp->pos_cache.lbp && hbp->pos_cache.base < position){ + i -= hbp->pos_cache.base; + lbp = hbp->pos_cache.lbp; + }/* End IF */ + + while(1){ + if(lbp->byte_count >= i) break; + i -= lbp->byte_count; + if(lbp->next_line == NULL){ + char panic_string[LINE_BUFFER_SIZE]; + sprintf(panic_string, + "buff_find_line: position %d specified in buffer %s, z=%d", + position,hbp->name,hbp->zee); + tec_panic(panic_string); + printf("NULL ptr %d bytes still to go\n",i); + }/* End IF */ + lbp = lbp->next_line; + }/* End While */ + + if(i == lbp->byte_count && i && lbp->buffer[i-1] == '\n'){ + i = 0; + if(lbp->next_line == NULL){ + lbp->next_line = allocate_line_buffer(1); + if(lbp->next_line == NULL) return((struct buff_line *)NULL); + lbp->next_line->prev_line = lbp; + }/* End IF */ + lbp = lbp->next_line; + }/* End IF */ + + hbp->pos_cache.lbp = lbp; + hbp->pos_cache.base = position - i; + + return(lbp); + +}/* End Routine */ + + + +/* BUFF_FIND_OFFSET - Find offset onto line structure that <position> is on + * + * Function: + * + * This routine is called to find the offset into the line_buffer of + * the specified position. + */ +int +buff_find_offset(hbp,lbp,position) +register struct buff_header *hbp; +register struct buff_line *lbp; +int position; +{ + + PREAMBLE(); + if(hbp == NULL) return(-1); +/* + * First step is to see if the current position cache in the buffer header will + * help us out. Our position must be on this line if it is to be of help to us. + */ + if( lbp == NULL || hbp->pos_cache.lbp != lbp){ + lbp = buff_find_line(hbp,position); + if(lbp == NULL) return(-1); + }/* End IF */ + +/* + * Now we simply calculate the position's offset from the line structure base. + */ + return( position - hbp->pos_cache.base ); + +}/* End Routine */ + + + +/* BUFF_CONTENTS - Return the character at the specified position + * + * Function: + * + * This routine is called to fetch the single character which is at + * the specified buffer position. This routine is generally called + * by routines which are low frequence or only handle a small number + * of characters at any one time. + */ +int +buff_contents(hbp,position) +register struct buff_header *hbp; +register int position; +{ +register struct buff_line *lbp; +register int i; + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(position > hbp->zee || position < 0){ + char panic_string[LINE_BUFFER_SIZE]; + sprintf(panic_string, + "buff_contents: illegal position %d specified in buffer %s", + position,hbp->name); + tec_panic(panic_string); + }/* End IF */ + +/* + * Call buff_find_line to find the line structure that the specified + * position resides on. + */ + lbp = buff_find_line(hbp,position); + i = buff_find_offset(hbp,lbp,position); + if(i == -1) return(-1); + + return(lbp->buffer[i] & 0xFF); + +}/* End Routine */ + +/* BUFF_CACHED_CONTENTS - Return the character and positional information + * + * Function: + * + * This routine is called to fetch the single character which is at + * the specified buffer position. This routine is generally called + * by routines which are low frequence or only handle a small number + * of characters at any one time. + */ +int +buff_cached_contents(hbp,position,cache) +register struct buff_header *hbp; +int position; +struct position_cache *cache; +{ +register struct buff_line *lbp; +register int i; + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(position > hbp->zee || position < 0){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_contents: illegal position %d %s Z = %d\n", + position,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ + +/* + * Call buff_find_line to find the line structure that the specified + * position resides on. + */ + lbp = buff_find_line(hbp,position); + if(lbp == NULL) return(-1); + i = buff_find_offset(hbp,lbp,position); + if(i == -1) return(-1); +/* + * If he has specified a local position cache, copy that information back to + * his local cache. It's up to him to invalidate it if the buffer gets changed. + */ + if(cache){ + cache->lbp = lbp; + cache->base = i; + }/* End IF */ + + return(lbp->buffer[i] & 0xFF); + +}/* End Routine */ + + + +/* BUFF_INIT - Routine to initialize the buffer routines + * + * Function: + * + * This routine is called before the first buffer operations to + * initialize the buffer routines. + */ +int +buff_init() +{ + register struct buff_header *hbp; + char tmp_buffer[LINE_BUFFER_SIZE]; + + PREAMBLE(); +/* + * Create the buffer map buffer + */ + (void) strcpy(tmp_buffer,TECO_INTERNAL_BUFFER_NAME); + (void) strcat(tmp_buffer,"BUFFER-MAP"); + hbp = buff_create(tmp_buffer,1); + if(hbp == NULL) return(FAIL); + hbp->buffer_number = 0; + +/* + * Create a default edit buffer + */ + (void) strcpy(tmp_buffer,TECO_INTERNAL_BUFFER_NAME); + (void) strcat(tmp_buffer,"Main"); + curbuf = hbp = buff_create(tmp_buffer,1); + if(hbp == NULL) return(FAIL); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_OPENBUFFER - Routine to read a file into a TECO edit buffer + * + * Function: + * + * This routine is called to load a file into the named buffer. If the + * buffer already exists, we simply switch to it and don't modify it. + */ +int +buff_openbuffer(name,buffer_number,readonly_flag) +char *name; +int buffer_number; +int readonly_flag; +{ + register struct buff_header *hbp = NULL; + + PREAMBLE(); +/* + * If no name is supplied then the buffer_number argument is significant, + * otherwise we ignore it. + */ + if(name == NULL){ + hbp = buffer_headers; + while(hbp){ + if(hbp->buffer_number == buffer_number){ + name = hbp->name; + break; + }/* End IF */ + hbp = hbp->next_header; + }/* End While */ + }/* End IF */ +/* + * If no name is supplied, the buffer_number argument must already exist + * or an error is declared. + */ + if(name == NULL){ + error_message("?No such buffer number exists"); + return(FAIL); + }/* End IF */ +/* + * First determine if a buffer of that name already exists + */ + if(hbp == NULL){ + hbp = buff_find(name); + }/* End IF */ + + if(hbp){ + buff_switch(hbp,1); + return(SUCCESS); + }/* End IF */ + +/* + * If there is not such buffer in existence, we need to create one + */ + hbp = buff_create(name,0); + if(hbp == NULL) return(FAIL); + if(readonly_flag == YES) hbp->isreadonly = YES; + +/* + * Ignore possible open error, since he may be creating a new file + */ + buff_read(hbp,name); + + hbp->dot = 0; + hbp->ismodified = NO; + + buff_switch(hbp,1); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_OPENBUFFNUM - Routine to open buffer number 'n' + * + * Function: + * + * This routine is called with the number of a buffer which is to be + * made the 'current' edit buffer. + */ +int +buff_openbuffnum(buffer_number,map_flag) +int buffer_number; +int map_flag; +{ + register struct buff_header *hbp; + + PREAMBLE(); +/* + * Search the list of buffers for the specified buffer number + */ + hbp = buffer_headers; + while(hbp){ + if(hbp->buffer_number == buffer_number){ + buff_switch(hbp,map_flag); + return(SUCCESS); + }/* End IF */ + hbp = hbp->next_header; + }/* End While */ + + return(FAIL); + +}/* End Routine */ + + + +/* BUFF_REOPENBUFF - Called by undo to re-create an edit buffer + * + * Function: + * + * This routine is called to place a buffer back onto the buffer + * list. + */ +void +buff_reopenbuff(bp) +register struct buff_header *bp; +{ +register struct buff_header *obp; + + PREAMBLE(); +/* + * Make sure there isn't another buffer of this name already here. The + * way this can happen is automatically created Q-registers like the + * search string and rubout string Q-registers might be created. Since + * they don't get undone, they would stick around and then we might + * try to undo the removal of an earlier copy of one of them. This way, + * we make sure that any earlier copy replaces any more recent copy. + */ + obp = buffer_headers; + while(obp){ + if(strcmp(obp->name,bp->name) == 0){ + buff_destroy(obp); + break; + }/* End IF */ + obp = obp->next_header; + }/* End While */ +/* + * Insert this buffer in the proper place on the buffer list. + */ + obp = buffer_headers; + while(obp){ + if(obp->next_header == NULL) break; + if(obp->next_header->buffer_number > bp->buffer_number) break; + obp = obp->next_header; + }/* End While */ + + if(obp == NULL || buffer_headers->buffer_number > bp->buffer_number){ + bp->next_header = buffer_headers; + buffer_headers = bp; + }/* End IF */ + + else { + bp->next_header = obp->next_header; + obp->next_header = bp; + }/* End Else */ + +}/* End Routine */ + + + +/* BUFF_READ - Routine to read a file into an existing TECO edit buffer + * + * Function: + * + * This routine is called to read a file into the specified edit buffer. + * The edit buffer must already exist. + */ +int +buff_read(hbp,name) +struct buff_header *hbp; +char *name; +{ + int iochan; + char tmp_message[LINE_BUFFER_SIZE]; + int status; + + PREAMBLE(); +/* + * Attempt to open the file + */ + iochan = open(name,O_RDONLY); + if(iochan < 0){ + sprintf(tmp_message,"?Cannot open <%s>: %s",name,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + status = buff_readfd(hbp,name,iochan); + close(iochan); + return(status); + +}/* End Routine */ + + + +/* BUFF_READFD - Reads the file descriptor into the specified buffer + * + * Function: + * + * This routine is called with a buffer and a file descriptor. The + * entire contents of the file descriptor are read into the specified + * buffer. + */ +int +buff_readfd(hbp,name,iochan) +struct buff_header *hbp; +char *name; +int iochan; +{ + char iobuf[IO_BUFFER_SIZE]; + char linebuf[IO_BUFFER_SIZE]; + int linebuf_cnt; + char tmp_message[LINE_BUFFER_SIZE]; + register int bcount; + register char *cp,*iop = 0; + register int i; + register struct buff_line *lbp; + struct buff_line *olbp; + int original_zee = hbp->zee; + int error_status = SUCCESS; + + PREAMBLE(); +/* + * Read the file and insert the characters into the buffer + */ + cp = linebuf; + linebuf_cnt = bcount = 0; + + while(1){ + if(bcount <= 0){ + bcount = read(iochan,iobuf,sizeof(iobuf)); + iop = iobuf; + if(bcount == 0) break; + if(bcount < 0){ + sprintf(tmp_message,"?Error reading <%s>: %s", + name,error_text(errno)); + error_message(tmp_message); + error_status = FAIL; + break; + }/* End IF */ + }/* End IF */ + + linebuf_cnt += 1; + bcount -= 1; + if((*cp++ = *iop++) == '\n' || linebuf_cnt >= sizeof(linebuf)){ + lbp = buff_find_line(hbp,hbp->dot); + if(lbp == NULL){ + error_status = FAIL; + break; + }/* End IF */ + i = buff_find_offset(hbp,lbp,hbp->dot); + if(i == -1){ + error_status = FAIL; + break; + }/* End IF */ + if(i == 0){ + olbp = lbp; + lbp = allocate_line_buffer(linebuf_cnt); + if(lbp == NULL){ + error_status = FAIL; + break; + }/* End IF */ + + lbp->next_line = olbp; + lbp->prev_line = olbp->prev_line; + olbp->prev_line = lbp; + if(lbp->prev_line) lbp->prev_line->next_line = lbp; + if(hbp->first_line == olbp) hbp->first_line = lbp; + movc3(linebuf,lbp->buffer,linebuf_cnt); + lbp->byte_count = linebuf_cnt; + hbp->pos_cache.lbp = lbp; + hbp->pos_cache.base = hbp->dot; + hbp->dot += linebuf_cnt; + hbp->zee += linebuf_cnt; + }/* End IF */ + + else { + if(buff_insert(hbp,hbp->dot,linebuf,linebuf_cnt) == FAIL){ + error_status = FAIL; + break; + }/* End IF */ + }/* End Else */ + + cp = linebuf; + linebuf_cnt = 0; + continue; + }/* End IF */ + + }/* End While */ + + if(error_status == SUCCESS && linebuf_cnt != 0){ + if(buff_insert(hbp,hbp->dot,linebuf,linebuf_cnt) == FAIL){ + error_status = FAIL; + }/* End IF */ + }/* End IF */ + +/* + * If the buffer just became modified for the first time, we need to change the + * label line to reflect this. + */ + if(hbp->ismodified == NO && hbp->zee != original_zee){ + if(hbp == curbuf){ + if(screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED) == FAIL){ + error_status = FAIL; + }/* End IF */ + }/* End IF */ + hbp->ismodified = YES; + }/* End IF */ + + checkpoint_modified = YES; + + return(error_status); + +}/* End Routine */ + + + +/* BUFF_WRITE - Write the specified buffer out to a file + * + * Function: + * + * This routine is generally called on an EW command when the user + * wishes to write out the contents of the buffer. + */ +int +buff_write(hbp,chan,start,end) +struct buff_header *hbp; +int chan; +int start; +int end; +{ +register int bcount; +register struct buff_line *lbp; +register char *cp1,*cp2; +register int line_bcount; +char iobuf[IO_BUFFER_SIZE]; +int status; + + PREAMBLE(); + + bcount = 0; + cp2 = iobuf; + + lbp = buff_find_line(hbp,start); + line_bcount = buff_find_offset(hbp,lbp,start); + cp1 = lbp->buffer + line_bcount; + line_bcount = lbp->byte_count - line_bcount; + + while(start < end){ + if(bcount == sizeof(iobuf)){ + status = write(chan,iobuf,sizeof(iobuf)); + if(status != sizeof(iobuf)){ + return(FAIL); + }/* End IF */ + bcount = 0; + cp2 = iobuf; + }/* End IF */ + + if(line_bcount == 0){ + lbp = lbp->next_line; + cp1 = lbp->buffer; + line_bcount = lbp->byte_count; + }/* End IF */ + + *cp2++ = *cp1++; + bcount += 1; + line_bcount -= 1; + start += 1; + + }/* End While */ + + if(bcount > 0){ + status = write(chan,iobuf,(unsigned)bcount); + if(status != bcount){ + return(FAIL); + }/* End IF */ + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_SWITCH - Switch the current edit buffer + * + * Function: + * + * This routine is called when we wish to switch the current buffer + * to some other edit buffer. + */ +int +buff_switch(hbp,map_flag) +struct buff_header *hbp; +int map_flag; +{ +char tmp_message[LINE_BUFFER_SIZE]; +char *cp; + + PREAMBLE(); + + curbuf = curwin->win_buffer = hbp; + + sprintf(tmp_message,"<Buffer %d> %s",hbp->buffer_number,hbp->name); + screen_label_line(hbp,tmp_message,LABEL_C_FILENAME); + + cp = ""; + if(hbp->isreadonly) cp = "(READONLY)"; + screen_label_line(hbp,cp,LABEL_C_READONLY); + + cp = ""; + if(hbp->ismodified) cp = "(modified)"; + screen_label_line(hbp,cp,LABEL_C_MODIFIED); + + if(hbp->buffer_number == 0 && map_flag){ + buff_buffer_map(); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_BUFFER_MAP - Build a map of the existing buffers. + * + * Function: + * + * This routine is called when the buffer map buffer is entered. It is + * used to fill this routine with the appropriate text. + */ +void +buff_buffer_map() +{ +register struct buff_header *hbp; +char tmp_buffer[LINE_BUFFER_SIZE],padd_buffer[LINE_BUFFER_SIZE]; +register int i; +int count; +register char *cp; +int max_length; + + PREAMBLE(); + + buff_delete(curbuf,0,curbuf->zee); + + for(i = 0; i < sizeof(padd_buffer); i++){ + padd_buffer[i] = ' '; + }/* End FOR */ + padd_buffer[sizeof(padd_buffer)-1] = '\0'; + + max_length = 0; + for(hbp = buffer_headers; hbp != NULL ; hbp = hbp->next_header){ + if(hbp->buffer_number == 0) continue; + i = strlen(hbp->name); + if(i > max_length) max_length = i; + }/* End FOR */ + + for(hbp = buffer_headers; hbp != NULL ; hbp = hbp->next_header){ + if(hbp->buffer_number <= 0) continue; + i = max_length - strlen(hbp->name); + sprintf(tmp_buffer,"<Buffer %-4d> %s%s %s %6d bytes\n", + hbp->buffer_number,hbp->name,&padd_buffer[sizeof(padd_buffer)-1-i], + hbp->ismodified ? "(modified)" : " ",hbp->zee); + i = strlen(tmp_buffer); + buff_insert(curbuf,curbuf->dot,tmp_buffer,i); + }/* End FOR */ + + buff_insert(curbuf,curbuf->dot,"\n",1); + + for(hbp = buffer_headers; hbp != NULL ; hbp = hbp->next_header){ + if(hbp->buffer_number >= 0) continue; + i = max_length - strlen(hbp->name); + sprintf(tmp_buffer,"<Buffer %-4d> %s%s %s %6d bytes\n", + hbp->buffer_number,hbp->name,&padd_buffer[sizeof(padd_buffer)-1-i], + hbp->ismodified ? "(modified)" : " ",hbp->zee); + i = strlen(tmp_buffer); + buff_insert(curbuf,curbuf->dot,tmp_buffer,i); + }/* End FOR */ + +/* + * Here to tell him if any q registers are currently on the push down + * list. + */ + i = count = 0; + for(hbp = qregister_push_down_list; hbp != NULL ; hbp = hbp->next_header){ + i += 1; + count += hbp->zee; + }/* End FOR */ + + if(i > 0){ + sprintf(tmp_buffer, + "\nQ register push down list contains %d register%s, %d bytes\n", + i,(i > 1)?"s":"",count); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + }/* End IF */ + +/* + * Insert our TITLE + */ + max_length = term_columns <= 80 ? term_columns : 80; + + buff_insert(curbuf,curbuf->dot,"\n\n",2); + (void) strcpy(tmp_buffer,"Video TECO"); + i = (max_length - strlen(tmp_buffer))/2; + if(i < 0) i = 0; + (void) strcat(tmp_buffer,"\n"); + buff_insert(curbuf,curbuf->dot,padd_buffer,i); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + (void) strcpy(tmp_buffer," by Paul Cantrell & Joyce Nishinaga"); + i = (max_length - strlen(tmp_buffer))/2; + if(i < 0) i = 0; + (void) strcat(tmp_buffer,"\n"); + buff_insert(curbuf,curbuf->dot,padd_buffer,i); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + +#if 0 + (void) strcpy(tmp_buffer,id_string); + if(tmp_buffer[0] == '%'){ + tmp_buffer[0] = '\0'; + cp = rcs_id; + i = 0; + if(*cp == '$'){ + cp++; + while(cp[i] == "Revision: "[i]) i += 1; + cp += i; + }/* End IF */ + i = 0; + while(*cp != '$' && *cp != '\0'){ + tmp_buffer[i++] = *cp++; + }/* End While */ + tmp_buffer[i] = '\0'; + if(i <= 2) sprintf(tmp_buffer,"*%d.%d* ",VMAJOR,VMINOR); + + cp = rcs_date; + i = 0; + if(*cp == '$'){ + cp++; + while(cp[i] == "Date: "[i]) i += 1; + cp += i; + }/* End IF */ + i = strlen(tmp_buffer); + while(*cp != '$' && *cp != '\0'){ + tmp_buffer[i++] = *cp++; + }/* End While */ + tmp_buffer[i] = '\0'; + + }/* End IF */ +#endif + sprintf(tmp_buffer,"*%d.%d* ",VMAJOR,VMINOR); + cp = rcs_date; + i = 0; + if(*cp == '$'){ + cp++; + while(cp[i] == "Date: "[i]) i += 1; + cp += i; + }/* End IF */ + i = strlen(tmp_buffer); + while(*cp != '$' && *cp != '\0'){ + tmp_buffer[i++] = *cp++; + }/* End While */ + tmp_buffer[i] = '\0'; + + i = (max_length - strlen(tmp_buffer))/2; + if(i < 0) i = 0; + (void) strcat(tmp_buffer,"\n"); + buff_insert(curbuf,curbuf->dot,padd_buffer,i); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + tecmem_stats(); + + curbuf->dot = 0; + return; + +}/* End Routine */ + + + +/* BUFF_INSERT - Insert a string at current position + * + * Function: + * + * This routine is called to insert a string into the specified buffer + * at the buffer's current location. + */ +int +buff_insert(hbp,position,buffer,length) +struct buff_header *hbp; +register int position; +register char *buffer; +register int length; +{ +struct buff_header fake_header; +struct buff_line *fake_line; +register char *cp; + + PREAMBLE(); + + fake_line = + (struct buff_line *)tec_alloc(TYPE_C_LINE,sizeof(struct buff_line)); + + if(fake_line == NULL) return(FAIL); + + fake_header.dot = 0; + fake_header.zee = 0; + fake_header.pos_cache.lbp = fake_line; + fake_header.pos_cache.base = 0; + fake_header.first_line = fake_line; + + fake_line->buffer_size = LINE_BUFFER_SIZE; + fake_line->buffer = buffer; + fake_line->next_line = NULL; + fake_line->prev_line = NULL; + fake_line->format_line = NULL; + + while(length){ + cp = fake_line->buffer; + fake_line->byte_count = 0; + while(1){ + fake_line->byte_count += 1; + length -= 1; + if(*cp == '\n' || length == 0){ + buff_insert_from_buffer_with_undo( + NULL, + hbp, + position, + &fake_header, + 0, + fake_line->byte_count + ); +/* + * The following is a terrible kludge. The buff_find_line code ends up + * inserting a null line buffer after our fake line, in order to + * represent the location following the last character. If we don't + * free it, it will get lost and consume memory... + */ + if(fake_line->next_line){ + buff_free_line_buffer(fake_line->next_line); + fake_line->next_line = NULL; + }/* End IF */ + fake_line->buffer += fake_line->byte_count; + position += fake_line->byte_count; + break; + }/* End IF */ + cp++; + }/* End While */ + }/* End While */ + + tec_release(TYPE_C_LINE,(char *)fake_line); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_INSERT_FROM_BUFFER_WITH_UNDO - Copy bytes from another buffer + * + * Function: + * + * This routine copies bytes from one buffer to another, arranging + * undo capability so that we can reverse the process. Because it + * knows the lengths of each line, this should be a very efficient + * method of inserting data, and should certainly have preference + * over using the byte-at-a-time routines. + */ +int +buff_insert_from_buffer_with_undo(ct,dbp,dest_position,sbp,src_position,length) +struct cmd_token *ct; +struct buff_header *dbp; +int dest_position; +struct buff_header *sbp; +int src_position; +int length; +{ +register int i; +struct undo_token *ut = 0; +struct buff_line *dlbp,*sb_lbp,*se_lbp,*nlbp; +int doff,sb_off,se_off; +int newline_included_in_src_data; +int bytes_to_copy_from_source; +int bytes_required_for_line,bytes_to_allocate; +char *new_buffer; +register char *dcp,*scp; +int bytes_inserted_so_far = 0; + +#ifdef DEBUG +char outbuf[1024]; +char inbuf[10]; +extern int tty_input_chan; + term_goto(0,0); + term_clrtobot(); + term_flush(); +#endif /* DEBUG */ + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(src_position < 0 || src_position > sbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo: bad src pos %d %s Z = %d\n", + src_position, + sbp->name,sbp->zee + ); + tec_panic(tmp_message); + }/* End IF */ + if(dest_position < 0 || dest_position > dbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo: bad dest pos %d %s Z = %d\n", + dest_position, + dbp->name,dbp->zee + ); + tec_panic(tmp_message); + }/* End IF */ + + if(ct){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + }/* End IF */ + +/* + * Determine which line the current insert point is on by calling the + * find routines. + */ + dlbp = buff_find_line(dbp,dest_position); + doff = buff_find_offset(dbp,dlbp,dest_position); + + sb_lbp = buff_find_line(sbp,src_position); + sb_off = buff_find_offset(sbp,sb_lbp,src_position); + + se_lbp = buff_find_line(sbp,src_position+length); + se_off = buff_find_offset(sbp,se_lbp,src_position+length); + +#ifdef DEBUG + movc3(dlbp->buffer,outbuf,dlbp->byte_count); + outbuf[dlbp->byte_count] = '\0'; + if(outbuf[dlbp->byte_count-1] == '\n') outbuf[dlbp->byte_count-1] = '\0'; + fprintf(stderr,"'%s' dest begin doff %d\n",outbuf,doff); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[doff+1] = '^'; outbuf[doff+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); + + movc3(sb_lbp->buffer,outbuf,sb_lbp->byte_count); + outbuf[sb_lbp->byte_count] = '\0'; + if(outbuf[sb_lbp->byte_count-1] == '\n'){ + outbuf[sb_lbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' source begin sb_off %d\n",outbuf,sb_off); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[sb_off+1] = '^'; outbuf[sb_off+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); + + movc3(se_lbp->buffer,outbuf,se_lbp->byte_count); + outbuf[se_lbp->byte_count] = '\0'; + if(outbuf[se_lbp->byte_count-1] == '\n'){ + outbuf[se_lbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' source end se_off %d\n",outbuf,se_off); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[se_off+1] = '^'; outbuf[se_off+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); +#endif /* DEBUG */ + +/* + * If the buffer just became modified for the first time, we need to change the + * label line to reflect this. + */ + if(dbp->ismodified == NO){ + if(dbp == curbuf){ + screen_label_line(dbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + dbp->ismodified = YES; + }/* End IF */ + + checkpoint_modified = YES; + +/* + * Since we are modifying the destination line buffer structure, we + * want to delete any format structures so that the screen display + * code will know to reformat it. + */ + if(dlbp->format_line){ + screen_free_format_lines(dlbp->format_line); + dlbp->format_line = NULL; + }/* End IF */ +/* + * The cases we have to handle are much more complex if there are newlines + * being inserted, so we determine now whether this is the case or not. + */ + newline_included_in_src_data = 1; + if(sb_lbp->buffer[sb_lbp->byte_count-1] != '\n'){ + newline_included_in_src_data = 0; + }/* End IF */ + +#ifdef DEBUG + fprintf( + stderr, + "newline_included_in_src_data = %d\n", + newline_included_in_src_data + ); +#endif /* DEBUG */ + + bytes_to_copy_from_source = sb_lbp->byte_count - sb_off; + if(bytes_to_copy_from_source > length){ +#ifdef DEBUG + fprintf( + stderr, + "bytes_to_copy_from_source %d > length %d\n", + bytes_to_copy_from_source, + length + ); +#endif /* DEBUG */ + bytes_to_copy_from_source = length; + newline_included_in_src_data = 0; + }/* End IF */ +#ifdef DEBUG + fprintf( + stderr, + "bytes_to_copy_from_source is now %d\n", + bytes_to_copy_from_source + ); +#endif /* DEBUG */ + +/* + * Test for a simple case - one in which there are no newlines in the source + * data. In this case, we know that we simply have to insert it into the + * current line buffer. If the buffer area is not big enough, we may have + * to allocate a new buffer. + */ + if(newline_included_in_src_data == 0){ +/* + * The total line length will be the current line length, plus the amount + * of data being inserted. + */ + bytes_required_for_line = dlbp->byte_count + bytes_to_copy_from_source; + +#ifdef DEBUG + fprintf( + stderr, + "no newline, bytes_required_for_line %d \n", + bytes_required_for_line + ); +#endif /* DEBUG */ + +/* + * If the current buffer is too small, we allocate a new buffer and copy + * all the data into it, and then free the old buffer. + */ + if(bytes_required_for_line > dlbp->buffer_size){ +#ifdef DEBUG + fprintf( + stderr, + "new allocation required\n" + ); +#endif /* DEBUG */ + bytes_to_allocate = + bytes_required_for_line + INCREMENTAL_LINE_BUFFER_SIZE - 1; + bytes_to_allocate -= + bytes_to_allocate % INCREMENTAL_LINE_BUFFER_SIZE; + if(dlbp->buffer_size > LARGEST_LOOKASIDE_BLOCK){ + bytes_to_allocate = dlbp->buffer_size * 2; + }/* End IF */ + + if(bytes_to_allocate < bytes_required_for_line){ + char tmp_message[1024]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo need %d alloced %d\n", + bytes_required_for_line, + bytes_to_allocate + ); + tec_panic(tmp_message); + }/* End IF */ + + new_buffer = + tec_alloc( + TYPE_C_LINEBUF, + bytes_to_allocate + ); + + if(new_buffer == NULL){ + return(FAIL); + }/* End IF */ + + dcp = new_buffer; +/* + * Copy up to the current insertion point in the new buffer, then the + * bytes coming from the source line, then if the source line didn't + * contribute a newline, the rest of the bytes coming from the original + * destination line. + */ + movc3(dlbp->buffer,dcp,doff); + dcp += doff; + movc3(sb_lbp->buffer+sb_off,dcp,bytes_to_copy_from_source); + dcp += bytes_to_copy_from_source; + movc3(&dlbp->buffer[doff],dcp,dlbp->byte_count-doff); + bytes_inserted_so_far += bytes_to_copy_from_source; + + tec_release(TYPE_C_LINEBUF,dlbp->buffer); + dlbp->buffer = new_buffer; + dlbp->buffer_size = bytes_to_allocate; + dlbp->byte_count = bytes_required_for_line; + }/* End IF */ +/* + * If the current buffer is large enough, make a hole to insert the new + * data into, and copy in the new data. + */ + else { +#ifdef DEBUG + fprintf( + stderr, + "it will fit into the current size of %d\n", + dlbp->buffer_size + ); +#endif /* DEBUG */ + dcp = &dlbp->buffer[bytes_required_for_line-1]; + scp = &dlbp->buffer[dlbp->byte_count-1]; + for(i = 0; i < dlbp->byte_count - doff; i++){ + *dcp-- = *scp--; + }/* End FOR */ + movc3( + &sb_lbp->buffer[sb_off], + &dlbp->buffer[doff], + bytes_to_copy_from_source + ); + dlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += bytes_to_copy_from_source; + }/* End Else */ + +#ifdef DEBUG + doff += bytes_to_copy_from_source; + movc3(dlbp->buffer,outbuf,dlbp->byte_count); + outbuf[dlbp->byte_count] = '\0'; + if(outbuf[dlbp->byte_count-1] == '\n'){ + outbuf[dlbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' new dest doff %d\n",outbuf,doff); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[doff+1] = '^'; outbuf[doff+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); +#endif /* DEBUG */ + }/* End IF */ + +/* + * Else, if there is a newline involved, things get more complicated... + * The first thing we do is construct a line buffer which holds any + * trailing part of the source data (i.e. not terminated by a newline) + * along with the trailing part of the line we are inserting into. + */ + else { + bytes_required_for_line = dlbp->byte_count - doff + se_off; + if(bytes_required_for_line > 0){ + nlbp = allocate_line_buffer(bytes_required_for_line); + if(nlbp == NULL) return(FAIL); + + nlbp->next_line = dlbp->next_line; + dlbp->next_line = nlbp; + if(nlbp->next_line) nlbp->next_line->prev_line = nlbp; + nlbp->prev_line = dlbp; + movc3(se_lbp->buffer,nlbp->buffer,se_off); + movc3( + &dlbp->buffer[doff], + &nlbp->buffer[se_off], + dlbp->byte_count-doff + ); + nlbp->byte_count = bytes_required_for_line; + dlbp->byte_count -= ( dlbp->byte_count - doff ); + bytes_inserted_so_far += se_off; + }/* End IF */ +/* + * Now we want to merge the data from the begining of the original + * destinatio line, and the data up to the first newline from the + * source data. + */ + bytes_required_for_line = + dlbp->byte_count + sb_lbp->byte_count - sb_off; + if(bytes_required_for_line > dlbp->buffer_size){ + bytes_to_allocate = + bytes_required_for_line + INCREMENTAL_LINE_BUFFER_SIZE - 1; + bytes_to_allocate -= + bytes_to_allocate % INCREMENTAL_LINE_BUFFER_SIZE; + if(dlbp->buffer_size > LARGEST_LOOKASIDE_BLOCK){ + bytes_to_allocate = dlbp->buffer_size * 2; + }/* End IF */ + + if(bytes_to_allocate < bytes_required_for_line){ + char tmp_message[1024]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo needs %d alloced %d\n", + bytes_required_for_line, + bytes_to_allocate + ); + tec_panic(tmp_message); + }/* End IF */ + + new_buffer = + tec_alloc( + TYPE_C_LINEBUF, + bytes_to_allocate + ); + if(new_buffer != NULL){ + dcp = new_buffer; + movc3(dlbp->buffer,dcp,dlbp->byte_count); + dcp += dlbp->byte_count; + movc3(&sb_lbp->buffer[sb_off],dcp,sb_lbp->byte_count - sb_off); + tec_release(TYPE_C_LINEBUF,dlbp->buffer); + dlbp->buffer = new_buffer; + dlbp->buffer_size = bytes_to_allocate; + dlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += (sb_lbp->byte_count - sb_off); + }/* End IF */ + }/* End IF */ + + else { + movc3( + &sb_lbp->buffer[sb_off], + &dlbp->buffer[doff], + sb_lbp->byte_count - sb_off + ); + dlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += (sb_lbp->byte_count - sb_off); + }/* End Else */ + +/* + * Now we want to copy all the full-length lines in between the starting + * and ending line buffers. + */ + sb_lbp = sb_lbp->next_line; + while(sb_lbp){ + if(sb_lbp == se_lbp) break; + bytes_required_for_line = sb_lbp->byte_count; + nlbp = allocate_line_buffer(bytes_required_for_line); + if(nlbp == NULL) break; + nlbp->next_line = dlbp->next_line; + dlbp->next_line = nlbp; + if(nlbp->next_line) nlbp->next_line->prev_line = nlbp; + nlbp->prev_line = dlbp; + movc3(sb_lbp->buffer,nlbp->buffer,bytes_required_for_line); + nlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += bytes_required_for_line; + dlbp = nlbp; + sb_lbp = sb_lbp->next_line; + }/* End While */ + +#ifdef DEBUG + movc3(dlbp->buffer,outbuf,dlbp->byte_count); + outbuf[dlbp->byte_count] = '\0'; + if(outbuf[dlbp->byte_count-1] == '\n'){ + outbuf[dlbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' new dest doff %d\n",outbuf,doff); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[doff+1] = '^'; outbuf[doff+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); + + movc3(nlbp->buffer,outbuf,nlbp->byte_count); + outbuf[nlbp->byte_count] = '\0'; + if(outbuf[nlbp->byte_count-1] == '\n'){ + outbuf[nlbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' new line\n",outbuf); +#endif /* DEBUG */ + }/* End Else */ + + + if(ct){ + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)dbp; + ut->iarg1 = dest_position; + ut->iarg2 = bytes_inserted_so_far; + }/* End IF */ + +/* + * Depending on whether or not the insert position precedes dot or not, we + * may have to increment dot. + */ + if(dest_position <= dbp->dot) dbp->dot += bytes_inserted_so_far; + dbp->zee += bytes_inserted_so_far; +/* + * If we are inserting characters before the position cache, it will + * shift our cached position down by the length, so we adjust the cache. + */ + if(dest_position < dbp->pos_cache.base){ + dbp->pos_cache.base += bytes_inserted_so_far; + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + +/* BUFF_INSERT_WITH_UNDO - Insert a string and arrange for undo capability + * + * Function: + * + * This routine is called when characters need to be inserted, and + * also need to be un-inserted if the command is undone. + */ +int +buff_insert_with_undo(ct,hbp,position,buffer,length) +struct cmd_token *ct; +struct buff_header *hbp; +register int position; +register char *buffer; +register int length; +{ +struct undo_token *ut; + + PREAMBLE(); + + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)hbp; + ut->iarg1 = position; + ut->iarg2 = length; + + if(buff_insert(hbp,position,buffer,length) == FAIL){ + ut->iarg2 = 0; + return(FAIL); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_INSERT_CHAR - Insert the specified character into the current position + * + * Function: + * + * This routine is called to insert the single character into the buffer + * at the current position (dot). It has to worry about such things as + * noting that the buffer has been modified, the screen display may be + * invalid, etc. + */ +int +buff_insert_char(hbp,position,data) +register struct buff_header *hbp; +int position; +char data; +{ +register struct buff_line *lbp; +struct buff_line *nlbp; +register char *cp,*ocp; +register int i,j; + + PREAMBLE(); + +/* + * Insure that the specified position is legal + */ + if(position > hbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_insert_char: bad position %d %s Z = %d\n", + position,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ +/* + * Determine which line the current insert point is on by calling the + * find routines. + */ + lbp = buff_find_line(hbp,position); + i = buff_find_offset(hbp,lbp,position); +/* + * Depending on whether or not the specified position precedes dot or not, we + * may have to decrement dot. + */ + if(position <= hbp->dot) hbp->dot += 1; + hbp->zee += 1; +/* + * If the buffer just became modified for the first time, we need to change the + * label line to reflect this. + */ + if(hbp->ismodified == NO){ + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + hbp->ismodified = YES; + }/* End IF */ + + checkpoint_modified = YES; + +/* + * If we are inserting a character before the position cache, it will + * shift our cached position down by one, so we adjust the cache. + */ + if(position < hbp->pos_cache.base){ + hbp->pos_cache.base += 1; + }/* End IF */ +/* + * If there is a format_line structure associated with this line, we make it + * go away so that the screen display routine will rebuild the display contents + * of this line. + */ + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ + +/* + * If this is a newline, we do things a little differently because this will + * actually cause the line to be broken up. In this case we actually have to + * create a new line buffer structure. + */ + if(data == '\n'){ +/* + * Calculate how many bytes are to move to the new line + */ + j = lbp->byte_count - i; + if(j > 0){ + nlbp = allocate_line_buffer(j); + if(nlbp == NULL) return(FAIL); + nlbp->next_line = lbp->next_line; + lbp->next_line = nlbp; + if(nlbp->next_line) nlbp->next_line->prev_line = nlbp; + nlbp->prev_line = lbp; + +/* + * Now copy the data into the new line buffer + */ + cp = lbp->buffer + i; + ocp = nlbp->buffer; + + while(nlbp->byte_count < j){ + *ocp++ = *cp++; + nlbp->byte_count++; + lbp->byte_count--; + }/* End While */ + }/* End IF */ + }/* End IF */ + +/* + * If this line buffer does not have room for the data, we need to expand it + */ + if(lbp->byte_count == lbp->buffer_size){ + ocp = + tec_alloc( + TYPE_C_LINEBUF, + lbp->buffer_size + INCREMENTAL_LINE_BUFFER_SIZE + ); + if(ocp == NULL) return(FAIL); + movc3(lbp->buffer,ocp,lbp->buffer_size); + tec_release(TYPE_C_LINEBUF,lbp->buffer); + lbp->buffer = ocp; + lbp->buffer_size += INCREMENTAL_LINE_BUFFER_SIZE; + }/* End IF */ + + +/* + * Make a hole in which to place the byte. + */ + if(lbp->byte_count){ + ocp = lbp->buffer + lbp->byte_count; + cp = ocp - 1; + j = lbp->byte_count - i; + while(j--){ + *ocp-- = *cp--; + }/* End While */ + }/* End IF */ + + cp = lbp->buffer + i; +/* + * Ok, now insert the data into the hole + */ + *cp = data; + lbp->byte_count += 1; + + return(SUCCESS); + +}/* End Routine */ + +/* BUFF_INSERT_CHAR_WITH_UNDO - Insert a single char with undo capability + * + * Function: + * + * This is an envelope routine which guarentees that the character + * to be input can be undone if necessary. + */ +int +buff_insert_char_with_undo(ct,hbp,position,data) +struct cmd_token *ct; +register struct buff_header *hbp; +int position; +char data; +{ +struct undo_token *ut; + + PREAMBLE(); + + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)hbp; + ut->iarg1 = position; + ut->iarg2 = 1; + + if(buff_insert_char(hbp,position,data) == FAIL){ + ut->iarg2 = 0; + return(FAIL); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_DELETE - Delete specified number of characters + * + * Function: + * + * This routine is called to delete the specified number of characters + * at the specified location in the buffer. + */ +void +buff_delete(hbp,position,count) +struct buff_header *hbp; +int position; +register int count; +{ + + PREAMBLE(); +/* + * Insure that there are that many character in the buffer after the specified + * position. + */ + if((position + count) > hbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_delete: illegal range %d-%d %s Z = %d\n", + position,position+count,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ +/* + * For now we are cheap and just repeatedly call the single character delete + * routine, because this is simple. It is also very expensive and will have to + * be fixed eventually. + */ + while(count){ + buff_delete_char(hbp,position); + count -= 1; + }/* End While */ + +}/* End Routine */ + + + +/* BUFF_DELETE_CHAR - Delete the character at the current position + * + * Function: + * + * This routine is called to delete the single character which is at + * the current buffer position. + */ +int +buff_delete_char(hbp,position) +register struct buff_header *hbp; +int position; +{ +register struct buff_line *lbp; +struct buff_line *nlbp; +register char *cp,*ocp; +register int i,j; + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(position >= hbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_delete_char: bad position %d %s Z = %d\n", + position,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ +/* + * Call buff_find_line to find the line structure that the specified + * position resides on. + */ + lbp = buff_find_line(hbp,position); + i = buff_find_offset(hbp,lbp,position); +/* + * If there is a format_line structure associated with this line, we make it + * go away so that the screen display routine will rebuild the display contents + * of this line. + */ + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ +/* + * Get a pointer to the character that is about to be deleted + */ + cp = lbp->buffer + i; +/* + * Depending on whether or not the specified position precedes dot or not, we + * may have to decrement dot. + */ + if(position < hbp->dot) hbp->dot -= 1; + hbp->zee -= 1; +/* + * If this has modified the buffer for the first time, it needs to be displayed + * in the label line. + */ + if(hbp->ismodified == NO){ + hbp->ismodified = YES; + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + }/* End IF */ + + checkpoint_modified = YES; + +/* + * If we are deleting a character before the position cache, it will + * shift our cached position up by one, so we adjust the cache. + */ + if(position < hbp->pos_cache.base){ + hbp->pos_cache.base -= 1; + }/* End IF */ +/* + * If this is not a newline, then things are pretty simple. Just + * make the character go away... + */ + if(*cp != '\n'){ + movc3(cp+1,cp,lbp->byte_count-i-1); + lbp->byte_count -= 1; + return(SUCCESS); + }/* End IF */ + +/* + * If this is a newline, we do things a little differently because this + * actually causes the two lines to become concatenated (unless this is the + * final line in the buffer). If the buffer area is not big enough to hold + * both lines, we have to create a bigger area. + */ + nlbp = lbp->next_line; + if(nlbp == NULL){ + lbp->byte_count -= 1; + return(SUCCESS); + }/* End IF */ + +/* + * If there is a format_line structure associated with the second line, we make + * it go away since this line is about to be trashed. + */ + if(nlbp->format_line){ + screen_free_format_lines(nlbp->format_line); + nlbp->format_line = NULL; + }/* End IF */ + +/* + * Test whether or not the buffer is big enough to hold the data from both + * lines. + */ + j = lbp->byte_count + nlbp->byte_count - 1; + if(lbp->buffer_size < j){ + i = j + INCREMENTAL_LINE_BUFFER_SIZE - 1; + i -= i % INCREMENTAL_LINE_BUFFER_SIZE; + cp = tec_alloc(TYPE_C_LINEBUF,i); + if(cp == NULL) return(FAIL); + movc3(lbp->buffer,cp,lbp->buffer_size); + tec_release(TYPE_C_LINEBUF,lbp->buffer); + lbp->buffer = cp; + lbp->buffer_size = i; + }/* End IF */ + +/* + * Ok, now concatenate the two line buffers by copying the second line + * into the first. Because we copy on top of the newline, it effectively + * goes away, so we have to decrement the line count by one. + */ + lbp->byte_count -= 1; + cp = lbp->buffer + lbp->byte_count; + ocp = nlbp->buffer; + i = nlbp->byte_count; + while(i--){ + *cp++ = *ocp++; + lbp->byte_count++; + }/* End While */ + +/* + * Fixup the next and previous pointers so that they point around the + * line that we are about to remove. + */ + lbp->next_line = nlbp->next_line; + if(nlbp->next_line) nlbp->next_line->prev_line = lbp; + + if(hbp->pos_cache.lbp == nlbp) hbp->pos_cache.lbp = NULL; + buff_free_line_buffer(nlbp); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_DELETE_WITH_UNDO - Clobber buffer positions and build undo list + * + * Function: + * + * This routine is called when we want to delete a range of bytes in the + * buffer, and build an undo list so that we could replace them if we need + * to. This happens to be pretty efficient for a bulk delete since we can + * just move the line buffers over to the undo list. + */ +int +buff_delete_with_undo(ct,hbp,position,count) +struct cmd_token *ct; +struct buff_header *hbp; +int position; +int count; +{ +register char *cp; +struct undo_token *ut; +register struct buff_line *lbp; +struct buff_line *top_lbp; +int top_offset,local_count,bulk_position; +int bytes_deleted_so_far = 0; + + PREAMBLE(); + +/* ??? + * I think I may need this to insure that dot doesn't get changed. For + * instance, if we had <dot>abc and deleted 'a', we would have <dot>bc. + * Then if we undo, it's an insert of 'a' at position zero which causes + * dot to shift so we would now have a<dot>bc. + */ + if(hbp->dot == (position+count)){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + }/* End IF */ + +/* + * Because some of the undo functions only work in the 'current' buffer, if + * the user is deleting from a buffer other than the 'current' one, we have + * to set up some undo tokens to switch the current buffer back and forth + * during the undo process, should it occur. + */ + if(hbp != curbuf){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = curbuf->buffer_number; + }/* End IF */ + + top_lbp = buff_find_line(hbp,position); + top_offset = buff_find_offset(hbp,top_lbp,position); + +/* + * First step will be to delete the (hopefully) large block of line buffer + * structures in the middle. This will leave a partial line at the top and + * also at the bottom. To start, if the begining is in the middle of a line, + * we need to skip over the bytes on that line. + */ + local_count = count; + lbp = top_lbp; + bulk_position = position; + if(top_offset){ + local_count -= lbp->byte_count - top_offset; + bulk_position = position + lbp->byte_count - top_offset; + lbp = lbp->next_line; + }/* End IF */ + if(lbp) top_lbp = lbp->prev_line; + +/* + * Ok, now we want to chain over all the line buffer structures which can be + * removed in bulk. lbp gets left pointing at the final line which can be bulk + * removed. + */ + if(lbp && lbp->byte_count <= local_count){ +/* + * We will get to bulk delete at least one line, so allocate an undo buffer. + * Point the undo token at the head of the list of line buffers. Remember + * the buffer position of the first byte of bulk data in iarg1, and keep a + * running count of the number of bytes of bulk data in iarg2. + */ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_BULK_INSERT; + ut->carg1 = (char *)lbp; + ut->iarg1 = bulk_position; + ut->iarg2 = 0; +/* + * Now scan down until we reach the bottom of the bulk region. Keep track of + * how many characters this all represents, and clean up any associated format + * line structures. + */ + while(1){ + ut->iarg2 += lbp->byte_count; + local_count -= lbp->byte_count; + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ + if(lbp->next_line == NULL) break; + if(lbp->next_line->byte_count > local_count) break; + lbp = lbp->next_line; + }/* End While */ +/* + * Unchain the bulk area from the rest of the buffer. First, make the parent + * structure's forward pointer point to the next line buffer after the final + * one in the bulk deletion region. + */ + if(top_lbp) top_lbp->next_line = lbp->next_line; + else hbp->first_line = lbp->next_line; + if(hbp->first_line == NULL) hbp->first_line = allocate_line_buffer(1); +/* + * Now we have to fixup the backwards pointer of the trailing line buffer + * structure, if it exists. + */ + if(lbp->next_line){ + lbp->next_line->prev_line = top_lbp; + }/* End IF */ +/* + * Finally, clobber the 'next' pointer in lbp which is the end of the bulk + * area. + */ + lbp->next_line = NULL; +/* + * Update the remaining count of bytes to be deleted. + */ + bytes_deleted_so_far += ut->iarg2; +/* + * Depending on whether or not the bulk position precedes dot or not, we + * may have to decrement dot. In any case, we have to update zee. + */ + if(bulk_position < hbp->dot){ + if(hbp->dot > bulk_position + ut->iarg2) hbp->dot -= ut->iarg2; + else hbp->dot = bulk_position; + }/* End IF */ + hbp->zee -= ut->iarg2; +/* + * If this has modified the buffer for the first time, it needs to be displayed + * in the label line. + */ + if(hbp->ismodified == NO){ + hbp->ismodified = YES; + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + }/* End IF */ + + checkpoint_modified = YES; + +/* + * If we are deleting from before the position cache, it will invalidate the + * information there, so we have to set it invalid so it does not get used. + */ + if(hbp->pos_cache.base >= bulk_position){ + hbp->pos_cache.lbp = NULL; + }/* End IF */ + + }/* End IF */ + +/* + * Now, just brute force clobber the remaining few bytes + */ + if(bytes_deleted_so_far < count){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_MEMFREE; + ut->carg1 = cp = tec_alloc(TYPE_C_CBUFF,count-bytes_deleted_so_far); + if(cp != NULL){ +/* + * Set up the undo token to put the text back into the q-register, and copy + * it into our save buffer as we delete it from the q-register. + */ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_INSERT; + ut->iarg1 = position; + ut->iarg2 = 0; + ut->carg1 = cp; + while(count > bytes_deleted_so_far){ + *cp++ = buff_contents(hbp,position); + if(buff_delete_char(hbp,position) == FAIL) return(FAIL); + bytes_deleted_so_far += 1; + ut->iarg2 += 1; + }/* End While */ + }/* End IF */ + }/* End IF */ + +/* + * Insure that the undo commands are working on the buffer the user specified + */ + if(hbp != curbuf){ + ut = allocate_undo_token(ct); + if(ut == FAIL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = hbp->buffer_number; + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_BULK_INSERT - Insert a list of line buffer structures + * + * Function: + * + * This routine is used to insert a list of line buffer structures at + * the specified position in the buffer. + */ +void +buff_bulk_insert(hbp,position,count,lbp) +struct buff_header *hbp; +int position; +int count; +register struct buff_line *lbp; +{ +register struct buff_line *olbp; +int offset; + + PREAMBLE(); +/* + * If this will modify the buffer for the first time, it needs to be displayed + * in the label line. + */ + if(hbp->ismodified == NO){ + hbp->ismodified = YES; + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + }/* End IF */ + + checkpoint_modified = YES; + +/* + * We special case position zero because this is an easy case, and is common + * when HK is being used a lot + */ + if(position == 0){ + if((olbp = hbp->first_line) != NULL){ + if(olbp->byte_count == 0){ + olbp = olbp->next_line; + buff_free_line_buffer(hbp->first_line); + }/* End IF */ + }/* End IF */ + hbp->first_line = lbp; + while(lbp->next_line) lbp = lbp->next_line; + lbp->next_line = olbp; + if(olbp) olbp->prev_line = lbp; + hbp->zee += count; + hbp->dot += count; + hbp->pos_cache.lbp = NULL; + return; + }/* End IF */ + +/* + * Also special check for position zee since this is also an easy case + */ + if(position == hbp->zee){ + olbp = hbp->first_line; + if(hbp->pos_cache.lbp) olbp = hbp->pos_cache.lbp; + while(olbp->next_line) olbp = olbp->next_line; + if(olbp->byte_count == 0){ + olbp = olbp->prev_line; + buff_free_line_buffer(olbp->next_line); + }/* End IF */ + olbp->next_line = lbp; + lbp->prev_line = olbp; + if(hbp->dot == hbp->zee) hbp->dot += count; + hbp->zee += count; + hbp->pos_cache.lbp = NULL; + return; + }/* End IF */ + +/* + * Ok, if the insertion is not at the begining or the end of the buffer, then + * we just have to do it the hard way... + */ + olbp = buff_find_line(hbp,position); + offset = buff_find_offset(hbp,olbp,position); + +/* + * Bulk inserts should always be guarenteed aligned. + */ + if(offset){ + error_message("BULK_INSERT: internal error! non-aligned bulk insert"); + return; + }/* End IF */ + + if(olbp->prev_line) olbp->prev_line->next_line = lbp; + lbp->prev_line = olbp->prev_line; + + while(lbp->next_line) lbp = lbp->next_line; + lbp->next_line = olbp; + olbp->prev_line = lbp; + + if(hbp->dot >= position) hbp->dot += count; + if(hbp->pos_cache.base >= position) hbp->pos_cache.base += count; + hbp->zee += count; + + return; + +}/* End Routine */ + + + +/* ALLOCATE_LINE_BUFFER - Routine to allocate a line buffer structure + * + * Function: + * + * This routine is used to allocate a line buffer structure with + * a data buffer large enough to accommodate the specified number + * of bytes. + */ +struct buff_line * +allocate_line_buffer(size) +register int size; +{ +register struct buff_line *lbp; + + PREAMBLE(); +/* + * If he is asking for a size which will result in the minimum allocation, try + * to satisfy the request off of the lookaside list. + */ + if(size <= INITIAL_LINE_BUFFER_SIZE){ + if((lbp = line_buffer_lookaside_list) != NULL){ + line_buffer_lookaside_list = lbp->next_line; + lbp->next_line = NULL; + lbp->prev_line = NULL; + lbp->format_line = NULL; + lbp->byte_count = 0; + lbp->lin_magic = MAGIC_LINE; + return(lbp); + }/* End IF */ + }/* End IF */ + +/* + * Lookaside list is empty, or he is asking for a non-standard size + */ + lbp = (struct buff_line *)tec_alloc(TYPE_C_LINE,sizeof(struct buff_line)); + if(lbp == NULL) return(NULL); + + if(size <= INITIAL_LINE_BUFFER_SIZE){ + lbp->buffer_size = INITIAL_LINE_BUFFER_SIZE; + }/* End IF */ + else if(size < INITIAL_LINE_BUFFER_SIZE + INCREMENTAL_LINE_BUFFER_SIZE){ + lbp->buffer_size = INITIAL_LINE_BUFFER_SIZE + + INCREMENTAL_LINE_BUFFER_SIZE; + } + else { + lbp->buffer_size = size + INCREMENTAL_LINE_BUFFER_SIZE - 1; + lbp->buffer_size -= lbp->buffer_size % INCREMENTAL_LINE_BUFFER_SIZE; + }/* End IF */ + + lbp->buffer = tec_alloc(TYPE_C_LINEBUF,lbp->buffer_size); + if(lbp->buffer == NULL){ + tec_release(TYPE_C_LINE,(char *)lbp); + return(NULL); + }/* End IF */ + + lbp->byte_count = 0; + lbp->next_line = NULL; + lbp->prev_line = NULL; + lbp->format_line = NULL; + lbp->lin_magic = MAGIC_LINE; + return(lbp); + +}/* End Routine */ + + +/* BUFF_FREE_LINE_BUFFER - Free up the associated storage + * + * Function: + * + * This routine is called when a line buffer is deleted. It cleans up any + * associated storage as well as the buffer itself. + */ +void +buff_free_line_buffer(lbp) +register struct buff_line *lbp; +{ + + PREAMBLE(); + + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ + +/* + * If this is a standard size line buffer, place it back on the lookaside list + * rather than giving it back to tec_alloc. + */ + lbp->lin_magic = 0; + + if(lbp->buffer_size == INITIAL_LINE_BUFFER_SIZE){ + lbp->lin_magic = MAGIC_LINE_LOOKASIDE; + lbp->next_line = line_buffer_lookaside_list; + line_buffer_lookaside_list = lbp; + return; + }/* End IF */ + + tec_release(TYPE_C_LINEBUF,lbp->buffer); + tec_release(TYPE_C_LINE,(char *)lbp); + +}/* End Routine */ + +/* BUFF_FREE_LINE_BUFFER_LIST - Release a whole list of line buffers + * + * Function: + * + * This routine will delete an entire list of line buffers + */ +void +buff_free_line_buffer_list(lbp) +register struct buff_line *lbp; +{ +register struct buff_line *olbp; + + PREAMBLE(); + + while((olbp = lbp)){ + lbp = lbp->next_line; + buff_free_line_buffer(olbp); + }/* End While */ + +}/* End Routine */ + +/* BUFF_DEALLOCATE_LINE_BUFFER_LOOKASIDE_LIST - Free up lookaside list + * + * Function: + * + * This routine frees up any format_lines we've cached on our local + * lookaside list. + */ +void +buff_deallocate_line_buffer_lookaside_list() +{ +register struct buff_line *lbp; + + PREAMBLE(); + + while((lbp = line_buffer_lookaside_list) != NULL){ + line_buffer_lookaside_list = lbp->next_line; + lbp->lin_magic = 0; + tec_release(TYPE_C_LINEBUF,lbp->buffer); + tec_release(TYPE_C_LINE,(char *)lbp); + }/* End While */ + +}/* End Routine */ + + + +#ifdef DEBUG_DUMPING +buff_dump_buffer(who_string,hbp,channel) +char *who_string; +register struct buff_header *hbp; +FILE *channel; +{ +register struct buff_line *lbp; +register int i,j; + + PREAMBLE(); + + fprintf(channel,"%s requested buffer dump:\n"); + + for(i = 0, lbp = hbp->first_line; lbp != NULL; i++,lbp = lbp->next_line){ + fprintf(channel,"line %4d (count %4d)'",i,lbp->byte_count); + buff_dump_line(lbp,channel); + fprintf(channel,"'\n"); + }/* End FOR */ + + fflush(debug_chan); + +}/* End Routine */ + +buff_dump_line(lbp,channel) +register struct buff_line *lbp; +FILE *channel; +{ +register struct buff_header *hbp = curbuf; +register int i,j; + + PREAMBLE(); + + for(j = 0; j < lbp->byte_count; j++){ + if(lbp->buffer[j] == '\n'){ + fprintf(channel,"<newline>"); + }/* End IF */ + else fprintf(channel,"%c",lbp->buffer[j]); + }/* End FOR */ + +}/* End Routine */ + +#endif + +unsigned int +stringHash( char *str ) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++) & 0xFF ) + { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + + return hash; +} diff --git a/teccmd.c b/teccmd.c new file mode 100644 index 0000000..f55af95 --- /dev/null +++ b/teccmd.c @@ -0,0 +1,2668 @@ +char *teccmd_c_version = "teccmd.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/teccmd.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* teccmd.c + * Subroutines which implement (usually large) TECO commands + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +/* + * Define constants for search table code + */ +#define SEARCH_M_MATCHREPEAT 0x80 +#define SEARCH_M_NOT 0x40 + +#define SEARCH_M_TYPE 0x0F +#define SEARCH_C_BYMASK 1 +#define SEARCH_C_ATLEASTONE 2 +#define SEARCH_C_QREGNUM 3 +#define SEARCH_C_QREGCHARS 4 + +/* + * Globals + */ + int last_search_pos1; + int last_search_pos2; + int last_search_status; + struct tags *current_tags; + +#ifdef CHECKPOINT + static char *checkpoint_filename = NULL; +#endif /* CHECKPOINT */ + +/* + * External Routines, etc. + */ + extern int forced_height,forced_width; + extern char eight_bit_output_flag,hide_cr_flag; + +#ifdef CHECKPOINT + void cmd_checkpoint(void); +#endif /* CHECKPOINT */ + + struct tags *tag_load_file(); + + void srch_setbits(int *bit_table,char *string); + void srch_setbit(int *bit_table,unsigned char value); + void srch_setallbits(int *bit_table); + void srch_invert_sense(int *bit_table); + int cmd_writebak(int,char *,char *,char *,int); + + extern struct buff_header *curbuf; + extern struct buff_header *buffer_headers; + extern char waiting_for_input_flag; + extern char alternate_escape_character; + extern char alternate_delete_character; + extern char intr_flag; + extern int term_lines; + extern char checkpoint_flag; + extern char checkpoint_enabled; + extern int checkpoint_interval; + extern char checkpoint_modified; + extern char resize_flag; + extern unsigned int IntBits[BITS_PER_INT]; + extern struct search_buff search_string; + extern char suspend_is_okay_flag; + +/* CMD_SEARCH - Execute a search command + * + * Function: + * + * This is the runtime execution of the search command. ARG1 and ARG2 may + * specify a buffer range a,bS<mumble> in which case the search is + * constrained within the range. In this case, the direction of the search + * is determined by whether ARG1 is greater than or less than ARG2. + * If ARG2 == -1, then it is an ignored argument and ARG1 specifies the + * direction of search and the number of times to search. + */ +int +cmd_search(arg1,arg2,search_tbl) +int arg1; +int arg2; +struct search_buff *search_tbl; +{ +int count; +char forwards; +int status; +int pos1; +int pos2; +int original_dot; + + PREAMBLE(); + +/* + * Compile the search Q-register into a search table + */ + if(compile_search_string(search_tbl) == FAIL) return(FAIL); + +/* + * Before looking at the arguments, set up the defaults + */ + original_dot = curbuf->dot; + count = 1; + forwards = 1; + pos1 = 0; + pos2 = curbuf->zee; + +/* + * If ARG2 is -1, then he did not specify a range, but rather a count + */ + if(arg2 == -1){ + if(arg1 < 0){ + forwards = 0; + count = -arg1; + }/* End IF */ + + else count = arg1; + }/* End IF */ +/* + * Else, if ARG2 != -1, then he specified a range for the search + */ + else { + if(arg1 < arg2){ + curbuf->dot = pos1 = arg1; + pos2 = arg2; + }/* End IF */ + + else { + forwards = 0; + pos1 = arg2; + curbuf->dot = pos2 = arg1; + }/* End Else */ + }/* End Else */ + +/* + * Now implement the search + */ + while(count--){ + if(forwards){ + status = cmd_forward_search(pos1,pos2,search_tbl); + }/* End IF */ + + else { + status = cmd_reverse_search(pos1,pos2,search_tbl); + }/* End IF */ + + if(status == FAIL){ + curbuf->dot = original_dot; + last_search_pos1 = last_search_pos2 = -1; + last_search_status = 0; + return(FAIL); + }/* End IF */ + }/* End While */ + + last_search_status = -1; + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_FORWARD_SEARCH - Search in a forward direction + * + * Function: + * + * This routine is used when the search progresses forward + */ +int +cmd_forward_search(pos1,pos2,search_tbl) +int pos1; +int pos2; +struct search_buff *search_tbl; +{ +register struct search_element *ep; +register unsigned char buffer_char; +register int table_position; +int dotpos; +char token_match; +char tmp_message[LINE_BUFFER_SIZE]; +struct position_cache base_position; +struct position_cache running_position; + + PREAMBLE(); + + dotpos = curbuf->dot; +/* + * Insure the search string is non-null + */ + if(search_tbl->length <= 0){ + error_message("?Null Search String"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + if(dotpos < pos1) return(FAIL); + + table_position = 0; + ep = &search_tbl->data[table_position]; + buffer_char = buff_cached_contents(curbuf,dotpos,&base_position); + running_position = base_position; + + while(dotpos < pos2){ + + if(table_position == 0){ + last_search_pos1 = dotpos; + base_position = running_position; + }/* End IF */ + + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + BUFF_INCREMENT_CACHE_POSITION(&running_position); + dotpos++; + token_match = 1; + + switch(ep->type & SEARCH_M_TYPE){ + case SEARCH_C_BYMASK: + if((ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) == 0){ + token_match = 0; + break; + }/* End IF */ + if(ep->type & SEARCH_M_MATCHREPEAT){ + while(dotpos < pos2){ + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + if((ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) == 0) break; + BUFF_INCREMENT_CACHE_POSITION(&running_position); + dotpos++; + }/* End While */ + }/* End IF */ + break; + case SEARCH_C_QREGCHARS: + { struct buff_header *qbp; + register int i; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + for(i = 0; i < qbp->zee; i++){ + if(buffer_char == buff_contents(qbp,i)) token_match = 1; + }/* End FOR */ + if(ep->type & SEARCH_M_NOT){ + token_match = token_match ? 0 : 1; + } + }/* End Block */ + break; + case SEARCH_C_ATLEASTONE: + error_message("?^EM not implemented..."); + search_tbl->error_message_given = YES; + return(FAIL); + case SEARCH_C_QREGNUM: + { struct buff_header *qbp; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + if(buffer_char == qbp->ivalue) token_match = 1; + if(ep->type & SEARCH_M_NOT){ + token_match = token_match ? 0 : 1; + }/* End IF */ + }/* End Block */ + break; + default: + sprintf(tmp_message, + "SEARCH: Illegal token type %d - internal error", + ep->type); + tec_panic(tmp_message); + return(FAIL); + }/* End Switch */ + + if(token_match){ + table_position += 1; + ep++; + if(table_position == search_tbl->length){ + curbuf->dot = last_search_pos2 = dotpos; + {/* Local Block */ + register struct buff_header *hbp; + hbp = buff_qfind('_',1); + if(hbp == NULL) return(FAIL); + hbp->ivalue = last_search_pos1; + }/* End Block */ + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + else { + if(intr_flag){ + error_message("?Search aborted"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + table_position = 0; + ep = &search_tbl->data[table_position]; + dotpos = last_search_pos1 + 1; + running_position = base_position; + BUFF_INCREMENT_CACHE_POSITION(&running_position); + }/* End IF */ + + }/* End While */ + + return(FAIL); + +}/* End Routine */ + + + +/* CMD_REVERSE_SEARCH - Search in a reverse direction + * + * Function: + * + * This routine is used when the search progresses backwards + */ +int +cmd_reverse_search(pos1,pos2,search_tbl) +int pos1; +int pos2; +struct search_buff *search_tbl; +{ +register struct search_element *ep; +register unsigned char buffer_char; +register int table_position; +int dotpos; +char token_match; +char tmp_message[LINE_BUFFER_SIZE]; +struct position_cache base_position; +struct position_cache running_position; + + PREAMBLE(); + + dotpos = curbuf->dot; +/* + * Insure the search string is non-null + */ + if(search_tbl->length <= 0){ + error_message("?Null Search String"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + if(dotpos > pos2) return(FAIL); + + table_position = search_tbl->length - 1; + ep = &search_tbl->data[table_position]; + buffer_char = buff_cached_contents(curbuf,dotpos,&base_position); + running_position = base_position; + + while(dotpos > pos1){ + + if(table_position == (search_tbl->length - 1)){ + last_search_pos2 = dotpos; + base_position = running_position; + }/* End IF */ + + dotpos--; + BUFF_DECREMENT_CACHE_POSITION(&running_position); + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + token_match = 1; + + switch(ep->type & SEARCH_M_TYPE){ + case SEARCH_C_BYMASK: + if((ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) == 0){ + token_match = 0; + break; + }/* End IF */ + if(ep->type & SEARCH_M_MATCHREPEAT){ + while(dotpos > pos1){ + BUFF_DECREMENT_CACHE_POSITION(&running_position); + dotpos--; + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + if(ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) continue; + dotpos++; + BUFF_INCREMENT_CACHE_POSITION(&running_position); + break; + }/* End While */ + }/* End IF */ + break; + case SEARCH_C_QREGCHARS: + { struct buff_header *qbp; + register int i; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + for(i = 0; i < qbp->zee; i++){ + if(buffer_char == buff_contents(qbp,i)) token_match = 1; + }/* End FOR */ + }/* End Block */ + break; + case SEARCH_C_ATLEASTONE: + error_message("?^EM not implemented..."); + search_tbl->error_message_given = YES; + return(FAIL); + case SEARCH_C_QREGNUM: + { struct buff_header *qbp; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + if(buffer_char == qbp->ivalue) token_match = 1; + }/* End Block */ + break; + default: + sprintf(tmp_message, + "SEARCH: Illegal token type %d - internal error", + ep->type); + tec_panic(tmp_message); + return(FAIL); + }/* End Switch */ + + if(token_match){ + table_position -= 1; + ep--; + if(table_position < 0){ + curbuf->dot = last_search_pos1 = dotpos; + {/* Local Block */ + register struct buff_header *hbp; + hbp = buff_qfind('_',1); + if(hbp == NULL) return(FAIL); + hbp->ivalue = last_search_pos2; + }/* End Block */ + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + else { + if(intr_flag){ + error_message("?Search aborted\n"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + table_position = search_tbl->length - 1; + ep = &search_tbl->data[table_position]; + dotpos = last_search_pos2 - 1; + running_position = base_position; + BUFF_DECREMENT_CACHE_POSITION(&running_position); + }/* End IF */ + + }/* End While */ + + return(FAIL); + +}/* End Routine */ + + + +/* SET_SEARCH_STRING_WITH_UNDO - Sets Q-register '_' to the new search string + * + * Function: + * + * This routine is called from the exec functions when a search string + * is to be set. It takes care of loading Q-register '_' with the new + * search string, as well as setting up all the undo tokens to insure + * that this is all reversable. + */ +int +set_search_string_with_undo(string,uct) +char *string; +struct cmd_token *uct; +{ +register struct buff_header *qbp; +register struct undo_token *ut; +register int i,c; +register char *cp; +int new_length; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the search string. If + * it does not exist, have it be created automatically. + */ + qbp = buff_qfind('_',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + +/* + * This code allows us to load the numeric half of the search string + * Q-register with the length of the string we just found. In order + * to be able to undo it, we have to protect the previous value, and + * it just happens that by doing it here we probably catch all the + * places where the search could have taken place. Then again, maybe + * not (search really should not be calling this routine repeatedly + * in an iteration or macro, for instance). If this gets cleaned up, + * then this code might not be appropriate... XXX + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = '_'; + ut->iarg2 = qbp->ivalue; + + if(string == NULL) return(SUCCESS); + if((new_length = strlen(string)) == 0) return(SUCCESS); + +/* + * Set up undo tokens for the search_state and search_position information + * which typically gets used to pass search information between halves of + * a search / replace type command. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_SEARCH_GLOBALS; + ut->iarg1 = last_search_pos1; + ut->iarg2 = last_search_pos2; + ut->carg1 = (char *)last_search_status; + +/* + * If the new length is the same as the old length, there is a chance we are + * just setting the same search string over again. To find out, we actually + * have to loop through comparing bytes in the Q-register to bytes in the + * supplied string. If none of them are different, we can just return without + * changing the search string. This happens often in iterations... + */ + if(new_length == qbp->zee){ + cp = string; + for(i = 0; i < new_length; i++){ + c = buff_contents(qbp,i); + if(*cp++ != c) break; + }/* End FOR */ + + if(i == new_length) return(SUCCESS); + + }/* End IF */ +/* + * At this point we set an undo token so that if the following stuff gets + * undone, we will re-compile the search string. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_SEARCH_STRING; +/* + * Delete the current contents of the search string Q-register to make room + * for the new contents. + */ + if(qbp->zee > 0) buff_delete_with_undo(uct,qbp,0,qbp->zee); +/* + * Set up the undo token to delete the text we are going to insert into the + * search string Q-register. + */ + buff_insert_with_undo(uct,qbp,0,string,new_length); + + return(SUCCESS); + +}/* End Routine */ + + + +/* COMPILE_SEARCH_STRING - Creates the search table for a given input string + * + * Function: + * + * This routine parses the input search string and creates the search + * table which will describe to the search routines how to match the + * buffer characters. Note that the search table must be constructed + * in such a way that it works equally well backward or forward. + */ +int +compile_search_string(search_tbl) +struct search_buff *search_tbl; +{ +register int position; +register char c; +register char *cp; +register struct buff_header *qbp; +register struct search_element *ep; +int radix; +int bracket_bits[256/BITS_PER_INT]; +char tmp_message[LINE_BUFFER_SIZE]; +char either_case_flag = YES; +char bracket_wildcard = NO; +char bracket_not_flag = NO; +char not_flag = NO; +char matchrepeat_flag = NO; + + PREAMBLE(); + +#define QBUF_CHAR() \ + (position < qbp->zee ? *cp++ = buff_contents(qbp,position++) : 0) + + search_tbl->error_message_given = NO; + +/* + * Get a pointer to the special Q-register which holds the search string. If + * it does not exist, have it be created automatically. + */ + qbp = buff_qfind('_',1); + if(qbp == NULL){ + error_message("Internal error - qfind returned null"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + if(qbp->zee >= SEARCH_STRING_MAX){ + error_message("Search Q-register too long"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + search_string.length = 0; + position = 0; + cp = search_string.input; + ep = search_string.data; + bzero(&ep->bitmask,sizeof(ep->bitmask)); + + while((c = QBUF_CHAR())){ +/* + * Check for CNTRL_E which is the standard lead-in for wildcard search + * strings. + */ + if(c == CNTRL_E){ + c = QBUF_CHAR(); + switch(c){ +/* + * [ specifies a list of possible matches, ANY of which satisfy the + * position. + */ + case '[': + bzero(bracket_bits,sizeof(bracket_bits)); + bracket_wildcard = YES; + if(not_flag){ + not_flag = NO; + bracket_not_flag = YES; + }/* End IF */ + continue; +/* + * A specifies any ALPHABETIC character as a match. Thus any upper or lower + * case character in the range a-z will match. + */ + case 'A': case 'a': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + break; +/* + * B specifies any separator character, when separator is defined as any + * non-alphanumeric character. + */ + case 'B': case 'b': + { + register int i; + ep->type = SEARCH_C_BYMASK; + for(i = 0; i < 256; i++){ + if(!isalnum(i)) srch_setbit((int *)&ep->bitmask,i); + }/* End FOR */ + break; + } +/* + * C specifies any symbol constituent. This is any alpha-numeric character, + * or dot (.), dollar sign ($), or underscore (_). + */ + case 'C': case 'c': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + srch_setbits((int *)&ep->bitmask,"0123456789.$_"); + break; +/* + * D specifies that any digit (0 to 9) is acceptable. + */ + case 'D': case 'd': + ep->type = SEARCH_C_BYMASK | SEARCH_M_MATCHREPEAT; + srch_setbits((int *)&ep->bitmask,"0123456789"); + break; +/* + * E or ^E is a Video TECO only definition which means that whether searches + * are case sensitive or not should be toggled. + */ + case 'E': case 'e': case CNTRL_E: + either_case_flag ^= 1; + continue; +/* + * G followed by a Q register name matches any character which is in the + * text portion of the Q register. + */ + case 'G': case 'g': + if(bracket_wildcard){ + error_message("^EG inside ^E[,,,] illegal"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + ep->type = SEARCH_C_QREGCHARS; + ep->value = QBUF_CHAR(); + break; +/* + * L matches any line terminator character. + */ + case 'L': case 'l': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask,"\n\r\f"); + break; +/* + * M is a flag that says any number of the following character (but at least + * one) are acceptable. Thus ^EMA would match A or AA or AAA, etc. + */ + case 'M': case 'm': + matchrepeat_flag = YES; + continue; +/* + * R matches any alphanumeric + */ + case 'R': case 'r': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + srch_setbits((int *)&ep->bitmask,"0123456789"); + break; +/* + * S matches any number of tabs and spaces, in any mixture (but there must + * be at least one). + */ + case 'S': case 's': + ep->type = SEARCH_C_BYMASK | SEARCH_M_MATCHREPEAT; + srch_setbits((int *)&ep->bitmask," \t"); + break; +/* + * U matches against the ASCII code which is contained in the Q register's + * numeric storage. + */ + case 'U': case 'u': + if(bracket_wildcard){ + error_message("^EU inside ^E[,,,] illegal"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + ep->type = SEARCH_C_QREGNUM; + ep->value = QBUF_CHAR(); + break; +/* + * V matches any lowercase letter. + */ + case 'V': case 'v': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + break; +/* + * W matches any uppercase letter + */ + case 'W': case 'w': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + break; +/* + * X is similar to ^X in that it matches any character + */ + case 'X': case 'x': + ep->type = SEARCH_C_BYMASK; + srch_setallbits((int *)&ep->bitmask); + break; +/* + * The < character opens the sequence for ^E<nnn> where nnn is a numeric + * (ASCII) code to search for. Classic TECO says that this code is octal, + * but Video TECO defaults to decimal, octal if the first digit is zero, + * or hex if the first two digits are 0x. + */ + case '<': + ep->value = 0; + radix = 10; + c = QBUF_CHAR(); + if(c == '0'){ + radix = 8; + c = QBUF_CHAR(); + if(c == 'x' || c == 'X'){ + radix = 16; + c = QBUF_CHAR(); + }/* End IF */ + }/* End IF */ + while(c && c != '>'){ + if(c >= '0' && c <= '7'){ + ep->value *= radix; + ep->value += c - '0'; + c = QBUF_CHAR(); + continue; + }/* End IF */ + if(c >= '8' && c <= '9' && radix > 8){ + ep->value *= radix; + ep->value += c - '0'; + c = QBUF_CHAR(); + continue; + }/* End IF */ + if(c >= 'a' && c <= 'f' && radix == 16){ + c = TOUPPER(c); + }/* End IF */ + if(c >= 'A' && c <= 'F' && radix == 16){ + ep->value *= radix; + ep->value += c - 'A' + 10; + c = QBUF_CHAR(); + continue; + }/* End IF */ + sprintf(tmp_message, + "?Illegal ^E code '%c' in search string",c); + search_tbl->error_message_given = YES; + error_message(tmp_message); + return(FAIL); + }/* End While */ + if(c == '>') c = QBUF_CHAR(); + else { + error_message("?Missing '>' in ^E<nnn> search string"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End Else */ + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,ep->value); + break; +/* + * This case is really just to catch the user typing ^E followed by the + * end of the search string. We just make it search for ^E in this case. + */ + case '\0': + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,CNTRL_E); + break; +/* + * Any unrecognized ^E code just searches for the exact character which + * follows the ^E. This is probably not very good, we should issue an + * error message instead. + */ + default: + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,c); + break; + }/* End Switch */ + }/* End IF */ +/* + * The ^X code embedded in a search string means match any character at + * this position. + */ + else if(c == CNTRL_X){ + ep->type = SEARCH_C_BYMASK; + srch_setallbits((int *)&ep->bitmask); + }/* End Else */ +/* + * The ^N code embedded in a search string means match any character EXCEPT + * the one that follows. + */ + else if(c == CNTRL_N){ + not_flag = YES; + continue; + }/* End Else */ +/* + * Else, here if it is just a normal character which should be searched for + */ + else { + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,c); + if(either_case_flag){ + if(islower((int)c)) srch_setbit((int *)&ep->bitmask,TOUPPER((int)c)); + if(isupper((int)c)) srch_setbit((int *)&ep->bitmask,TOLOWER((int)c)); + }/* End IF */ + }/* End Else */ + +/* + * If sense has been inverted with ^N, invert the bits + */ + if(not_flag){ + if(ep->type & SEARCH_C_BYMASK){ + srch_invert_sense((int *)&ep->bitmask); + }/* End IF */ + ep->type &= ~SEARCH_M_MATCHREPEAT; + ep->type |= SEARCH_M_NOT; + not_flag = NO; + }/* End IF */ +/* + * If already inside a ^E[a,b,c] type wildcard, check for comma or close + * bracket. + */ + if(bracket_wildcard){ + c = QBUF_CHAR(); + switch(c){ + case ',': + { + register int i; + for(i = 0; i < 256 / BITS_PER_INT; i++){ + bracket_bits[i] |= ep->bitmask.intarray[i]; + }/* End FOR */ + bzero(&ep->bitmask,sizeof(ep->bitmask)); + continue; + } + case ']': + { + register int i; + for(i = 0; i < 256 / BITS_PER_INT; i++){ + ep->bitmask.intarray[i] |= bracket_bits[i]; + }/* End FOR */ + if(bracket_not_flag){ + srch_invert_sense((int *)&ep->bitmask); + ep->type &= ~SEARCH_M_MATCHREPEAT; + bracket_not_flag = NO; + }/* End IF */ + bracket_wildcard = NO; + break; + } + default: + error_message("Illegal syntax in [,,,] construct"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End Switch */ + }/* End IF */ +/* + * Test for match repeating characters flag + */ + if(matchrepeat_flag){ + ep->type |= SEARCH_M_MATCHREPEAT; + matchrepeat_flag = NO; + }/* End IF */ +/* + * Point to the next entry in the search string table + */ + ep++; + bzero(&ep->bitmask,sizeof(ep->bitmask)); + search_string.length += 1; + + }/* End While */ + + *cp = '\0'; + + return(SUCCESS); + +}/* End Routine */ + + + +/* SRCH_SETALLBITS - Set ALL the bits on for a search table entry + * + * Function: + * + * This routine makes ANY character match the search string table + * entry. + */ +void +srch_setallbits(bit_table) +register int *bit_table; +{ +register int i; + + PREAMBLE(); + + for(i = 0; i < 256 / BITS_PER_INT; i++){ + bit_table[i] = ~0; + }/* End FOR */ + +}/* End Routine */ + +/* SRCH_INVERT_SENSE - Invert the sense of a search table entry + * + * Function: + * + * This routine is called when a 'NOT' modifier has been used, + * meaning that just the opposite matching should occur. We + * simply invert the setting of the bits in the search table entry. + */ +void +srch_invert_sense(bit_table) +register int *bit_table; +{ +register int i; + + PREAMBLE(); + + for(i = 0; i < 256 / BITS_PER_INT; i++){ + bit_table[i] ^= ~0; + }/* End FOR */ + +}/* End Routine */ + +/* SRCH_SETBITS - Set the corresponding search bits to 'on' + * + * Function: + * + * This routine is called with a zero terminated string of characters. + * It sets each corresponding bit in the search table to a 1. + */ +void +srch_setbits(bit_table,string) +register int *bit_table; +register char *string; +{ +register int c; + + PREAMBLE(); + + while((c = *string++)){ + bit_table[c/BITS_PER_INT] |= IntBits[c%BITS_PER_INT]; + }/* End While */ + +}/* End Routine */ + +/* SRCH_SETBIT - Set the corresponding search bit to 'on' + * + * Function: + * + * This routine is called with a char whose corresponding bit should + * be set in the search table. + */ +void +srch_setbit(bit_table,value) +register int *bit_table; +register unsigned char value; +{ + + PREAMBLE(); + + bit_table[value/BITS_PER_INT] |= IntBits[value%BITS_PER_INT]; + +}/* End Routine */ + + + +/* CMD_WRITE - Implements the EW command by writing out a buffer + * + * Function: + * + * This routine will write out the contents of the buffer after creating + * the appropriate backup files. If there is not already a file with the + * a .OLD extension, this is created. Otherwise, a .BAK file is created. + * If the name is so long that we can't append a of suffix, we have to + * hack it up a bit. This is not guaranteed to work, but... + */ +int +cmd_write(hbp,filename) +struct buff_header *hbp; +char *filename; +{ +char base_filename[TECO_FILENAME_COMPONENT_LENGTH + 1]; +char path_name[TECO_FILENAME_TOTAL_LENGTH + 1]; +char tmp_filename[TECO_FILENAME_TOTAL_LENGTH + 1]; +char tmp_message[LINE_BUFFER_SIZE]; +register char *cp1,*cp2; +register int i; +int path_len; +int file_len; +int combined_len; +int fi; +int status; + + PREAMBLE(); + +#ifdef UNIX +/* + * First step is to separate the path elements from the filename itself. + * To do this, we search for the directory path separator. + */ + cp1 = cp2 = filename; + while(*cp1){ + if(*cp1++ == '/') cp2 = cp1; + }/* End While */ + + combined_len = strlen(filename); + file_len = strlen(cp2); + path_len = combined_len - file_len; + +/* + * The filename really should not be longer than this. + */ + if(file_len > TECO_FILENAME_COMPONENT_LENGTH){ + sprintf(tmp_message,"?Filename is too long <%s>",cp2); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Make a copy of the filename portion for safe keeping. + */ + (void) strcpy(base_filename,cp2); +/* + * And do the same for the path portion. + */ + cp1 = path_name; + cp2 = filename; + for(i = 0; i < path_len; i++){ + *cp1++ = *cp2++; + }/* End FOR */ + *cp1 = '\0'; + +/* + * Now the first thing to do is see if the file exists, because this + * will impact us on whether we want to attempt .BAK files + */ + if(hbp->isbackedup == NO){ + + fi = open(filename,O_RDONLY); + if(fi >= 0){ + +#ifdef CREATE_OLD_FILES +#ifdef HAVE_LONG_FILE_NAMES + (void) strcpy(tmp_filename,base_filename); + (void) strcat(tmp_filename,".OLD"); + status = cmd_writebak(fi,filename,path_name,tmp_filename,O_EXCL); +#else + (void) strcpy(tmp_filename,path_name); + (void) strcat(tmp_filename,".TECOLD"); + status = cmd_writebak(fi,filename,tmp_filename,base_filename,O_EXCL); +#endif + if(status == SUCCESS) hbp->isbackedup = YES; + + if(status == FAIL && errno != EEXIST){ + sprintf(tmp_message,"?Error opening <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + close(fi); + return(FAIL); + }/* End IF */ +#endif + + if(hbp->isbackedup == NO){ +#ifdef HAVE_LONG_FILE_NAMES + (void) strcpy(tmp_filename,base_filename); + (void) strcat(tmp_filename,".BAK"); + status = cmd_writebak(fi,filename,path_name,tmp_filename,0); +#else + (void) strcpy(tmp_filename,path_name); + (void) strcat(tmp_filename,".TECBAK"); + status = cmd_writebak(fi,filename,tmp_filename,base_filename,0); +#endif + if(status == SUCCESS) hbp->isbackedup = YES; + if(status == FAIL){ + sprintf(tmp_message,"?Error opening <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + close(fi); + return(FAIL); + }/* End IF */ + }/* End IF */ + }/* End IF */ + + close(fi); + + }/* End IF */ + +/* END OF UNIX CONDITIONAL CODE */ + +#endif + +#ifdef VMS + fi = creat(filename,0,"mrs = 0"); +#else + fi = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666); +#endif + + if(fi < 0){ + sprintf(tmp_message,"?Error opening <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + status = buff_write(hbp,fi,0,hbp->zee); + + close(fi); + + if(status == FAIL){ + sprintf(tmp_message,"?Error writing <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + hbp->ismodified = NO; + if(hbp == curbuf){ + screen_label_line(hbp,"",LABEL_C_MODIFIED); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_WRITEBAK - Routine to open the BAK or OLD file + * + * Function: + * + * This routine opens a channel to the backup file and creates any + * subdirectories that may be necessary. + */ +int +cmd_writebak(fi,input_filename,pathname,output_filename,open_flags) +int fi; +char *pathname; +char *input_filename; +char *output_filename; +int open_flags; +{ +char tmp_filename[TECO_FILENAME_TOTAL_LENGTH + 1]; +char tmp_message[LINE_BUFFER_SIZE]; +char iobuf[IO_BUFFER_SIZE]; +int fo; +register int i; +register int status; + + PREAMBLE(); + +#ifndef HAVE_LONG_FILE_NAMES + if(mkdir(pathname,0777)){ + if(errno != EEXIST){ + sprintf(tmp_message,"?Error creating subdirectory <%s>: %s", + pathname,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + }/* End IF */ +#endif + + (void) strcpy(tmp_filename,pathname); + if(strlen(tmp_filename)){ + (void) strcat(tmp_filename,"/"); + }/* End IF */ + + (void) strcat(tmp_filename,output_filename); + chmod(tmp_filename,0666); + fo = open(tmp_filename,O_WRONLY|O_CREAT|O_TRUNC|open_flags,0666); + if(fo < 0){ + if(errno == EEXIST){ + chmod(tmp_filename,0444); + return(FAIL); + }/* End IF */ + sprintf(tmp_message,"?Error opening <%s>: %s", + tmp_filename,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + while(1){ + i = read(fi,iobuf,IO_BUFFER_SIZE); + if(i < 0){ + sprintf(tmp_message,"?Error reading <%s>: %s", + input_filename,error_text(errno)); + error_message(tmp_message); + close(fo); + return(FAIL); + }/* End IF */ + + status = write(fo,iobuf,(unsigned)i); + if(status < 0){ + sprintf(tmp_message,"?Error writing <%s>: %s", + tmp_filename,error_text(errno)); + error_message(tmp_message); + close(fo); + return(FAIL); + }/* End IF */ + + if(i < IO_BUFFER_SIZE) break; + + }/* End While */ + + close(fo); + chmod(tmp_filename,0444); + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_WORDMOVE - Here to move within the edit buffer by words + * + * Function: + * + * Here in response to one of the 'word' commands. We move the + * current edit position forward or backward by the specified + * number of words. + */ +int +cmd_wordmove(count) +register int count; +{ + register int c; + + PREAMBLE(); + + while(count > 0){ + while(1){ + if(curbuf->dot == curbuf->zee) break; + c = buff_contents(curbuf,curbuf->dot); + if(isspace(c)) break; + curbuf->dot++; + }/* End While */ + + while(1){ + if(curbuf->dot == curbuf->zee) break; + c = buff_contents(curbuf,curbuf->dot); + if(!isspace(c)) break; + curbuf->dot++; + }/* End While */ + + count -= 1; + + }/* End While */ + + while(count < 0){ + while(1){ + if(curbuf->dot == 0) break; + c = buff_contents(curbuf,curbuf->dot-1); + if(isspace(c)) break; + curbuf->dot--; + }/* End While */ + + while(1){ + if(curbuf->dot == 0) break; + c = buff_contents(curbuf,curbuf->dot-1); + if(!isspace(c)) break; + curbuf->dot--; + }/* End While */ + + count += 1; + + }/* End While */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_SUSPEND - We get here on a suspend signal from the tty + * + * Function: + * + * Here when the user has typed ^Z. We want to suspend back to the + * original shell. + */ +void +cmd_suspend() +{ +#if defined(UNIX) || defined(VMS) +extern char susp_flag; +#endif + + PREAMBLE(); + +#ifdef UNIX + if(waiting_for_input_flag == YES){ + pause_while_in_input_wait(); + return; + }/* End IF */ + +#ifdef JOB_CONTROL + if(susp_flag++ >= 4){ + signal(SIGTSTP,(void (*)())SIG_DFL); + }/* End IF */ +#endif + +#endif + +#ifdef VMS + if(suspend_is_okay_flag == YES){ + susp_flag++; + }/* End IF */ +#endif + +}/* End Routine */ + +/* CMD_PAUSE - Here when we are ready to suspend back to the shell + * + * Function: + * + * Here when the parser has noticed that the suspend flag is set, and + * is ready for us to suspend the process. + */ +void +cmd_pause() +{ +extern char susp_flag; + +#ifdef VMS +long owner_pid; +long status; + +struct itmlst { + short buffer_length; + short item_code; + char *buffer_address; + long *length_address; + long terminating_zero; +}list; +#endif + + PREAMBLE(); + + restore_tty(); + +#if defined(JOB_CONTROL) || defined(VMS) + +#ifdef CHECKPOINT + alarm(0); +#endif /* CHECKPOINT */ + +#ifdef UNIX + if(suspend_is_okay_flag == YES){ + kill(getpid(),SIGSTOP); + signal(SIGTSTP,(void (*)())cmd_suspend); + } +#endif /* UNIX */ + +#ifdef VMS + owner_pid = 0; + list.buffer_length = sizeof(owner_pid); + list.item_code = JPI$_OWNER; + list.buffer_address = (char *)&owner_pid; + list.length_address = 0; + list.terminating_zero = 0; + sys$getjpiw(0,0,0,&list,0,0,0); + + if(owner_pid){ + status = lib$attach(&owner_pid); + if(status != SS$_NORMAL){ + error_message("?LIB$ATTACH failed"); + susp_flag = 0; + return; + }/* End IF */ + }/* End IF */ + + else { + error_message("?Cannot suspend. No parent process for LIB$ATTACH"); + susp_flag = 0; + return; + }/* End IF */ +#endif + +#ifdef CHECKPOINT + alarm(checkpoint_interval); +#endif /* CHECKPOINT */ + +#endif /* JOB_CONTROL */ + + initialize_tty(); + screen_redraw(); + susp_flag = 0; + +}/* End Routine */ + + + +/* CMD_WINCH - We get here when the OS says the window changed size + * + * Function: + * + * Here when the OS says our window changed size. We just wanna + * call the screen package's resize entry point so it can build + * a new screen for us. + */ +void +cmd_winch() +{ + PREAMBLE(); + +#ifdef ATT_UNIX + signal(SIGWINCH,(void (*)())cmd_winch); +#endif + +#ifdef SEQUOIA + screen_message("WINCH!\g\g"); + screen_refresh(); + return; +#endif /* SEQUOIA */ + + resize_flag = YES; + + if(waiting_for_input_flag){ + screen_resize(); + screen_refresh(); + }/* End IF */ + +}/* End Routine */ + + + +/* CMD_INTERRUPT - We get here on an interrupt signal from the user + * + * Function: + * + * Here when the user has typed ^C. We want to cause any current commands + * to abort. This gives the user a way to break out of infinite iterations + * and macros. + */ +void +cmd_interrupt() +{ + PREAMBLE(); + +#ifdef ATT_UNIX + signal(SIGINT,(void (*)())cmd_interrupt); +#endif + + if(intr_flag++ >= 4){ + if(waiting_for_input_flag == NO){ + restore_tty(); + fprintf(stderr,"TECO aborted...\n"); + signal(SIGINT,(void (*)())SIG_DFL); + kill(getpid(),SIGINT); + }/* End IF */ + }/* End IF */ + + screen_message("interrupt!"); + +}/* End Routine */ + + + +#ifdef CHECKPOINT + +/* CMD_ALARM - Here to when the interval timer expires + * + * Function: + * + * This function is called periodically when the interval timer says + * it is time for us to checkpoint all our buffers. + */ +cmd_alarm() +{ + + PREAMBLE(); + +#if defined(ATT_UNIX) || defined(VMS) + signal(SIGALRM,(void (*)())cmd_alarm); +#endif + + if(waiting_for_input_flag == 0){ + checkpoint_flag = YES; + }/* End IF */ + + else { + if(checkpoint_enabled == YES){ + cmd_checkpoint(); + } + checkpoint_flag = NO; + }/* End Else */ + +#ifdef VMS + alarm(0); +#endif /* VMS */ + alarm(checkpoint_interval); + +}/* End Routine */ + +#endif /* CHECKPOINT */ + + + +#ifdef CHECKPOINT + +/* CMD_CHECKPOINT - Here to write all our buffers to a checkpoint file + * + * Function: + * + * Here we check out all the buffers and write out any which have + * ever been modified. + */ +void +cmd_checkpoint() +{ +register struct buff_header *bp; +int fd = 0; +char temp_buffer[TECO_FILENAME_TOTAL_LENGTH+20]; +char message_save_buff[SCREEN_NOMINAL_LINE_WIDTH]; +char filename_buffer[64]; +register char *cp; +int i,status; + + PREAMBLE(); + + if(checkpoint_modified == NO) return; + checkpoint_modified = NO; + + if(checkpoint_filename == NULL){ + temp_buffer[0] = '\0'; + if(cp = (char *)getenv("HOME")){ + (void) strcpy(temp_buffer,cp); +#ifndef VMS + (void) strcat(temp_buffer,"/"); +#endif /* VMS */ + }/* End IF */ + +#ifndef VMS + strcpy(filename_buffer,".tecXXXXXX"); +#else + strcpy(filename_buffer,"tecXXXXXX.CKP"); +#endif /* VMS */ + cp = mktemp(filename_buffer); + + strcat(temp_buffer,cp); + checkpoint_filename = tec_alloc(TYPE_C_CPERM,strlen(temp_buffer)+1); + if(checkpoint_filename == NULL) return; + strcpy(checkpoint_filename,temp_buffer); + }/* End IF */ + + status = 0; + for(bp = buffer_headers; bp != NULL; bp = bp->next_header){ + if(bp->ismodified){ + status = 1; + }/* End IF */ + }/* End FOR */ + + if(status == 0) return; + + screen_save_current_message(message_save_buff,sizeof(message_save_buff)); + screen_message("checkpointing..."); + screen_refresh(); + + fd = open(checkpoint_filename,O_WRONLY|O_CREAT|O_TRUNC,0666); + if(fd == 0 || fd == -1){ + sprintf(temp_buffer,"?Checkpoint file %s open failed %s", + checkpoint_filename,error_text(errno)); + error_message(temp_buffer); + screen_refresh(); + sleep(2); + return; + }/* End IF */ + + + for(bp = buffer_headers; bp != NULL; bp = bp->next_header){ + if(bp->ismodified == NO && bp->buffer_number <= 0) continue; + if(bp->buffer_number == 0)continue; + i = (78 - strlen(bp->name) - 4) / 2; + cp = temp_buffer; + *cp++ = '\n'; + while(i-- > 0)*cp++ = '*'; + *cp++ = '('; + strcpy(cp,bp->name); + while(*cp)cp++; + *cp++ = ')'; + if(bp->ismodified) *cp++ = '+'; + while(cp < &temp_buffer[78]) *cp++ = '*'; + *cp++ = '\n'; + *cp++ = '\0'; + i = strlen(temp_buffer); + + if(write(fd,temp_buffer,i) != i){ + sprintf(temp_buffer,"?checkpoint failed %s",error_text(errno)); + error_message(temp_buffer); + screen_refresh(); + sleep(2); + close(fd); + return; + }/* End IF */ + + + if(bp->ismodified){ + status = buff_write(bp,fd,0,bp->zee); + }/* End IF */ + + }/* End FOR */ + + close(fd); + + screen_message("checkpointing... done"); + screen_refresh(); + screen_message(message_save_buff); + +}/* End Routine */ + +#endif /* CHECKPOINT */ + + + +#ifdef CHECKPOINT + +/* REMOVE_CHECKPOINT_FILE - On exit we clean up the checkpoint file + * + * Function: + * + * This routine is called when a clean exit is assured. We remove + * the checkpoint file so they don't clutter up the disk. + */ +void +remove_checkpoint_file() +{ + + PREAMBLE(); + + if(checkpoint_filename == NULL) return; +#ifndef VMS + unlink(checkpoint_filename); +#endif + +}/* End Routine */ + +#endif /* CHECKPOINT */ + + + +#if defined(VMS) || defined(STARDENT) || defined(STARDENT_860) || defined(SEQUOIA) || defined(LOCUS_SYSV) || defined(SCO_386) +/* BZERO - Zero an array of bytes + * + * Function: + * + * This routine zeros an array of bytes. For some reason the VMS + * library doesn't seem to know about it. + */ +bzero(array,length) +register char *array; +register int length; +{ + PREAMBLE(); + + while(length-- > 0) *array++ = '\0'; + +}/* End Routine */ +#endif /* VMS || STARDENT et al */ + + + +#ifdef UNIX +/* CMD_OSCMD - Issue a command to the operating system + * + * Function: + * + * This routine is called in response to the EC command which allows the + * user to execute operating system commands from within the editor. + */ +int +cmd_oscmd(ct) +register struct cmd_token *ct; +{ +register char *cp; +int pid,w,status; +int pipe_desc[2]; +int last_intr_flag; +int line_cnt; +char tmpbuf[LINE_BUFFER_SIZE]; +char pipebuff[IO_BUFFER_SIZE]; +extern char susp_flag; +int pipe_buf_flag = 0; +int buf_pipe[2]; + + PREAMBLE(); + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + pipe_buf_flag = 1; + status = pipe(buf_pipe); + if (status != 0) { + error_message("?Error creating buffer pipe"); + return(FAIL); + } + }/* End IF */ + + cp = ct->ctx.carg; +/* + * Create a PIPE so that we can capture the output of the child process + */ + status = pipe(pipe_desc); + if(status != 0){ + sprintf(tmpbuf,"?Error creating PIPE: %s",error_text(errno)); + error_message(tmpbuf); + return(FAIL); + }/* End IF */ +/* + * Fork to get a process to run the shell + */ +#ifdef LINUX + if((pid = fork()) == 0){ + close(1); dup(pipe_desc[1]); + close(2); dup(pipe_desc[1]); + close(pipe_desc[0]); + close(pipe_desc[1]); + execl("/bin/bash","bash","-c",cp,0); + _exit(127); + }/* End IF */ +#else + if((pid = fork()) == 0){ + if (pipe_buf_flag) { + close(0); dup(buf_pipe[0]); + close(buf_pipe[1]); + } else { + close(pipe_desc[0]); + } + close(1); dup(pipe_desc[1]); + close(2); dup(pipe_desc[1]); + close(pipe_desc[0]); + close(pipe_desc[1]); + execl("/bin/csh","csh","-cf",cp,0); + _exit(127); + }/* End IF */ +#endif + if(pid == -1){ + close(pipe_desc[0]); + close(pipe_desc[1]); + sprintf(tmpbuf,"?Error performing FORK: %s",error_text(errno)); + return(FAIL); + }/* End IF */ + + if (pipe_buf_flag) { + struct buff_line *tmp_line = curbuf->first_line; + + close(buf_pipe[0]); + while (tmp_line) { + int n = tmp_line->byte_count; + char *lb = tmp_line->buffer; + + w = write(buf_pipe[1], lb, n); + while (w > 0) { + n -= w; + lb += w; + if (n == 0) + break; + w = write(pipe_desc[1], lb, n); + } + tmp_line = tmp_line->next_line; + } + close(buf_pipe[1]); + } + close(pipe_desc[1]); + +/* + * Loop reading stuff coming back from the pipe until we get an + * EOF which means the process has finished. Update the screen + * on newlines so the user can see what is going on. + */ + last_intr_flag = intr_flag; + line_cnt = 0; + + while((w = read(pipe_desc[0],pipebuff,sizeof(pipebuff))) > 0){ + + buff_insert(curbuf,curbuf->dot,pipebuff,w); + + if(intr_flag != last_intr_flag){ + kill(pid,SIGINT); + last_intr_flag = intr_flag; + }/* End IF */ + + if(susp_flag){ + cmd_pause(); + }/* End IF */ + + cp = pipebuff; + while(w--){ + if(intr_flag != last_intr_flag) break; + if(*cp++ == '\n' && line_cnt++ > (term_lines / 4)){ + line_cnt = 0; + if(tty_input_pending()) break; + screen_format_windows(); + screen_refresh(); + }/* End IF */ + }/* End While */ + + }/* End While */ + + close(pipe_desc[0]); + +/* + * The wait here is required so that the process we forked doesn't + * stay around as a zombie. + */ + status = 0; + while((w = wait(&status)) != pid && w != -1); + + return(SUCCESS); + +}/* End Routine */ + +/* END OF UNIX CONDITIONAL CODE */ + +#endif + + + +#ifdef VMS + +/* CMD_OSCMD - Issue a command to the operating system + * + * Function: + * + * This routine is called in response to the EC command which allows the + * user to execute operating system commands from within the editor. + */ +cmd_oscmd(ct) +register struct cmd_token *ct; +{ + PREAMBLE(); + + error_message("?VMS Does not currently support EC"); + return(FAIL); + +}/* End Routine */ + +#endif + + + +/* LOAD_QNAME_REGISTER - Loads buffer name into q-register * + * + * Function: + * + * This routine is called by a command operating on the text + * portion of a q-register if the specified q-register is *. + * In this case, we load the name of the current edit buffer + * into it so the user can easilly access it. + */ +void +load_qname_register() +{ +register struct buff_header *hbp; +register struct buff_header *qbp; + + PREAMBLE(); + + hbp = curbuf; + qbp = buff_qfind('*',1); + + if(qbp->zee > 0){ + buff_delete(qbp,0,qbp->zee); + }/* End IF */ + + buff_insert(qbp,qbp->dot,hbp->name,strlen(hbp->name)); + +}/* End Routine */ + +/* RENAME_EDIT_BUFFER - Change the name of the specified edit buffer + * + * Function: + * + * This routine is called by parser exec routines which have + * loaded q-register *. Since the text portion of this q-register + * is the buffer name, it has the effect of changing the name + * of the buffer. + */ +int +rename_edit_buffer(hbp,new_name,uct) +register struct buff_header *hbp; +register char *new_name; +register struct cmd_token *uct; +{ +register int i; +register struct undo_token *ut; +int length; + + PREAMBLE(); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_RENAME_BUFFER; + ut->carg1 = hbp->name; + + length = strlen(new_name); + hbp->name = tec_alloc(TYPE_C_CBUFF,length+1); + if(hbp->name == NULL) return(FAIL); + for(i = 0; i < length; i++){ + hbp->name[i] = new_name[i]; + }/* End FOR */ + hbp->name[length] = '\0'; + + buff_switch(hbp,0); + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_SETOPTIONS - Set runtime options via the EJ command + * + * Function: + * + * This routine is called when the user sets runtime variables using + * the EJ command. Although the command is a classic TECO command, the + * actual values are specific to Video TECO + */ +int +cmd_setoptions(arg1,arg2,uct) +int arg1; +int arg2; +struct undo_token *uct; +{ +struct undo_token fake_token; +extern int tab_width; + + PREAMBLE(); + + if(arg1 < SETOPTION_MIN_OPTION || arg1 > SETOPTION_MAX_OPTION){ + error_message("EJ option selector out of range"); + return(FAIL); + }/* End IF */ + + if(!uct) uct = &fake_token; + + switch(arg1){ + case SETOPTION_ALTERNATE_ESCAPE_CHAR: + uct->iarg1 = arg1; + uct->iarg2 = alternate_escape_character; + alternate_escape_character = arg2; + break; + case SETOPTION_SCREEN_WIDTH: + uct->iarg1 = arg1; + uct->iarg2 = forced_width; + forced_width = arg2; + screen_resize(); + break; + case SETOPTION_SCREEN_HEIGHT: + if(arg2 != 0 && arg2 < 3){ + error_message("TECO requires a larger window"); + return(FAIL); + }/* End IF */ + uct->iarg1 = arg1; + uct->iarg2 = forced_height; + forced_height = arg2; + screen_resize(); + break; + case SETOPTION_ALTERNATE_DELETE_CHAR: + uct->iarg1 = arg1; + uct->iarg2 = alternate_delete_character; + alternate_delete_character = arg2; + break; + case SETOPTION_FORCE_8_BIT_CHARS: + uct->iarg1 = arg1; + uct->iarg2 = eight_bit_output_flag; + eight_bit_output_flag = arg2 ? YES : NO; + screen_reformat_windows(); + break; + case SETOPTION_TAB_WIDTH: + uct->iarg1 = arg1; + uct->iarg2 = tab_width; + tab_width = arg2 >= 1 && arg2 <= 16 ? arg2 : NORMAL_TAB_WIDTH; + screen_reformat_windows(); + break; + case SETOPTION_HIDE_CR_CHARS: + uct->iarg1 = arg1; + uct->iarg2 = hide_cr_flag; + hide_cr_flag = arg2 ? YES : NO; + screen_reformat_windows(); + break; + + }/* End Switch */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_FTAGS - Perform UNIX tags function + * + * Function: + * + * This routine performs various unix tags functions. The following + * functions are supported: + * + * ARG1: Function: + * + * none Load tags file indicated in <string> + * 0 (same as <none>) + * 1 Find tag entry which corresponds with <string>. ARG2 + * is a flags word which says what to do with the tag + */ +int +cmd_tags(uct,arg_count,arg1,arg2,string) +struct cmd_token *uct; +int arg_count; +int arg1; +int arg2; +char *string; +{ +register struct undo_token *ut; +register struct tags *old_tags; +register struct tags *new_tags; +register struct tagent *tep = NULL; +int hashval,skip_cnt; + + PREAMBLE(); + + if(arg_count == 0) arg1 = 0; + if(arg_count < 2) arg2 = 0; + + if(arg1 < TAGS_MIN_OPTION || arg1 > TAGS_MAX_OPTION){ + error_message("FT option selector out of range"); + return(FAIL); + }/* End IF */ + + switch(arg1){ + case TAGS_LOAD_TAGS_FILE: + old_tags = current_tags; + new_tags = tag_load_file(string); + if(new_tags == NULL) return(FAIL); + current_tags = new_tags; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_LOAD_TAGS; + ut->carg1 = (char *)old_tags; + break; + case TAGS_SEARCH_TAGS_FILE: + if(current_tags == NULL) return(FAIL); + hashval = tag_calc_hash(string); + tep = current_tags->tagents[hashval]; + skip_cnt = arg2; + while(tep){ + if(strcmp(string,tep->te_symbol) == 0){ + if(skip_cnt-- <= 0){ + if(current_tags->current_entry != tep){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SELECT_TAGS; + ut->carg1 = (char *)current_tags->current_entry; + }/* End IF */ + current_tags->current_entry = tep; + return(SUCCESS); + }/* End IF */ + }/* End IF */ + tep = tep->te_next; + }/* End While */ + return(FAIL); + case TAGS_TEST_FOR_LOADED_TAGS: + if(current_tags){ + return(SUCCESS); + }/* End IF */ + return(FAIL); + case TAGS_INSERT_TARGET_FILE: + if(current_tags->current_entry){ + if(current_tags->current_entry->te_filename){ + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + current_tags->current_entry->te_filename, + strlen(current_tags->current_entry->te_filename) + ); + return(SUCCESS); + }/* End IF */ + }/* End IF */ + return(FAIL); + case TAGS_INSERT_SEARCH_STRING: + if(current_tags->current_entry){ + if(current_tags->current_entry->te_search_string){ + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + current_tags->current_entry->te_search_string, + strlen(current_tags->current_entry->te_search_string) + ); + return(SUCCESS); + }/* End IF */ + }/* End IF */ + return(FAIL); + case TAGS_INSERT_LINENO: + if(current_tags->current_entry){ + if(current_tags->current_entry->te_lineno){ + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + current_tags->current_entry->te_lineno, + strlen(current_tags->current_entry->te_lineno) + ); + return(SUCCESS); + }/* End IF */ + }/* End IF */ + return(FAIL); + case TAGS_INSERT_ALL: + if(current_tags){ + tag_dump_database(current_tags,uct); + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End Switch */ + + return(SUCCESS); + +}/* End Routine */ + +/* TAG_LOAD_FILE - Read in a tags file + * + * Function: + * + * This is a huge monolithic nasty routine which reads either VI or + * EMACS tags files, and builds an internal representation. + */ +struct tags * +tag_load_file(string) +register char *string; +{ +FILE *fd = NULL; +register struct tags *tp = NULL; +register struct tagent *tep = NULL; +register char *cp; +char *outcp = NULL; +char *filename_cp = NULL; +int state = 0; +int hashval; +int c; +char emacs_flag = NO; +char comma_seen = 0; +char tmp_buffer[LINE_BUFFER_SIZE]; +char tmp_search_string[LINE_BUFFER_SIZE]; +char tmp_filename[LINE_BUFFER_SIZE]; +char tmp_symbol[LINE_BUFFER_SIZE]; +char tmp_number[LINE_BUFFER_SIZE]; +char tmp_message[LINE_BUFFER_SIZE]; +int line = 0; + + fd = fopen(string,"r"); + if(fd == NULL){ + sprintf( + tmp_message, + "Could not open TAGS file <%s>: %s", + string, + error_text(errno) + ); + error_message(tmp_message); + return(NULL); + }/* End IF */ + + tp = (struct tags *)tec_alloc(TYPE_C_TAGS,sizeof(struct tags)); + if(tp == NULL) return(NULL); + bzero((char *)tp,sizeof(struct tags)); + +/* + * Loop reading tag entries + */ + while(1){ + cp = tmp_buffer; + bzero(cp,sizeof(tmp_buffer)); + if(fgets(cp,sizeof(tmp_buffer),fd) == NULL) break; + line++; +/* + * Ok, we have the start of an entry. Check for continuation. + */ + while(cp[0]){ + if(cp[0] == '\n'){ + cp[1] = '\0'; + break; + }/* End IF */ + if(cp[0] == '\\' && cp[1] == '\n'){ + if(fgets(cp,sizeof(tmp_buffer)-(cp-tmp_buffer),fd) == NULL){ + cp[0] = '\n'; cp[1] = '\0'; + }/* End IF */ + line++; + }/* End IF */ + cp++; + }/* End While */ + +/* + * First step is to get the name of the symbol so that we can obtain the + * hash value + */ + cp = tmp_buffer; + + if(emacs_flag == NO) state = 0; + + while(*cp){ + if(state < 0) break; + switch(state){ +/* + * Skip over symbol name until we hit whitespace. Then calculate the hash + * value for that symbol, allocate the tag entry structure, and copy the + * symbol name into it. + */ + case 0: + if(cp[0] == '\f' && cp[1] == '\n'){ + emacs_flag = YES; + state = 100; + continue; + }/* End IF */ + + if(isspace((int)*cp)){ + c = *cp; + *cp = '\0'; + hashval = tag_calc_hash(tmp_buffer); + tep = (struct tagent *)tec_alloc( + TYPE_C_TAGENT, + sizeof(struct tagent) + ); + if(tep == NULL) goto err; + bzero((char *)tep,sizeof(*tep)); + + tep->te_next = tp->tagents[hashval]; + tp->tagents[hashval] = tep; + + tep->te_symbol = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_buffer) + 1 + ); + if(tep->te_symbol == NULL) goto err; + strcpy(tep->te_symbol,tmp_buffer); + + *cp = c; + state++; + }/* End IF */ + cp++; + continue; +/* + * Skip any amount of whitespace which seperates the symbol from the + * filename. + */ + case 1: + if(!isspace((int)*cp)){ + filename_cp = cp; + state++; + continue; + }/* End IF */ + cp++; + continue; +/* + * Find the length of the filename portion and copy it to the tagent + */ + case 2: + if(isspace((int)*cp)){ + c = *cp; + *cp = '\0'; + + tep->te_filename = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(filename_cp) + 1 + ); + if(tep->te_filename == NULL) goto err; + strcpy(tep->te_filename,filename_cp); + + *cp = c; + state++; + }/* End IF */ + cp++; + continue; +/* + * Skip any amount of whitespace which seperates the filename from the + * search string + */ + case 3: + if(!isspace((int)*cp)){ + state++; + continue; + }/* End IF */ + cp++; + continue; + +/* + * The next character should be a slash to begin the search string + */ + case 4: + if(*cp == '/'){ + outcp = tmp_search_string; + *outcp = '\0'; + state++; + } + cp++; + continue; +/* + * Find the length of the search string portion and copy it to the tagent + */ + case 5: + if(*cp == '/'){ + *outcp = '\0'; + + tep->te_search_string = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_search_string) + 1 + ); + if(tep->te_search_string == NULL) goto err; + strcpy(tep->te_search_string,tmp_search_string); + + state = -1; + continue; + }/* End IF */ + if(*cp == '\\'){ + state = state + 1; + cp++; + continue; + }/* End IF */ + *outcp++ = *cp++; + continue; + +/* + * Here on a backquote character. Just skip it and pass through the quoted + * character without testing it for termination of the search string. + */ + case 6: + *outcp++ = *cp++; + state = state - 1; + continue; + +/* + * Here if we encounter a form-feed. This is a giveaway that we have an + * emacs style tags file, so we have to parse it differently. + */ + case 100: + if(cp[0] != '\f' || cp[1] != '\n'){ + cp++; + continue; + }/* End IF */ + cp += 2; + state = 101; + outcp = tmp_filename; + comma_seen = 0; + continue; +/* + * Here we get the name of the source file + */ + case 101: + if(*cp == '\n'){ + if(comma_seen == 0){ + state = 100; + continue; + }/* End IF */ + outcp--; + while(*outcp != ',') outcp--; + *outcp = '\0'; + cp++; + state = 102; + outcp = tmp_symbol; + *outcp++ = 127; + continue; + }/* End IF */ + + if(*cp == ',') comma_seen = 1; + *outcp++ = *cp++; + continue; +/* + * Now we get symbol names + */ + case 102: + if(*cp == '\f'){ + state = 100; + continue; + }/* End IF */ + + if(*cp != 127){ + *outcp++ = *cp++; + continue; + }/* End IF */ +/* + * Here on a 127 code which terminates the symbol name. First eat back to + * the first symbol character. + */ + while((c = outcp[-1])){ + if(c == 127) break; + if( + isalnum(c) || + isdigit(c) || + c == '_' || + c == '$' || + c == '.' + ){ + *outcp = '\0'; + break; + }/* End IF */ + outcp--; + }/* End While */ +/* + * Now go back until we find the first non-symbol character. + */ + while((c = outcp[-1])){ + if(c == 127) break; + if( + isalnum(c) || + isdigit(c) || + c == '_' || + c == '$' || + c == '.' + ){ + outcp--; + continue; + }/* End IF */ + break; + }/* End While */ + + hashval = tag_calc_hash(outcp); + tep = (struct tagent *)tec_alloc( + TYPE_C_TAGENT, + sizeof(struct tagent) + ); + if(tep == NULL) goto err; + bzero((char *)tep,sizeof(*tep)); + + tep->te_next = tp->tagents[hashval]; + tp->tagents[hashval] = tep; + + tep->te_symbol = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(outcp) + 1 + ); + if(tep->te_symbol == NULL) goto err; + strcpy(tep->te_symbol,outcp); + tep->te_filename = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_filename) + 1 + ); + if(tep->te_filename == NULL) goto err; + strcpy(tep->te_filename,tmp_filename); + + state = 103; + continue; + +/* + * Eat until we reach a number, which is the line number + */ + case 103: + if(isdigit((int)*cp)){ + state = 104; + outcp = tmp_number; + continue; + }/* End IF */ + + if(*cp == '\f'){ + state = 100; + continue; + }/* End IF */ + + cp++; + continue; +/* + * Read in the line number + */ + case 104: + if(isdigit((int)*cp)){ + *outcp++ = *cp++; + continue; + }/* End IF */ + + *outcp++ = '\0'; + tep->te_lineno = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_number) + 1 + ); + if(tep->te_lineno == NULL) goto err; + strcpy(tep->te_lineno,tmp_number); + + state = 105; + continue; +/* + * Eat until end of line + */ + case 105: + if(*cp == '\n'){ + cp++; + state = 102; + outcp = tmp_symbol; + continue; + }/* End IF */ + if(*cp == '\f'){ + state = 100; + continue; + }/* End IF */ + cp++; + continue; + }/* End Switch */ + }/* End While */ + + }/* End While */ + + fclose(fd); + return(tp); + +err: + + tag_free_struct(tp); + if(fd) fclose(fd); + return(NULL); + +}/* End Routine */ + +void +tag_free_struct(tp) +register struct tags *tp; +{ +register struct tagent **tepp; +register struct tagent *tep; +register int i; + + if(tp == NULL) return; + + for(i = 0, tepp = &tp->tagents[0]; i < ELEMENTS(tp->tagents); i++,tepp++){ + while((tep = *tepp)){ + *tepp = tep->te_next; + + if(tep->te_symbol){ + tec_release(TYPE_C_TAGSTR,tep->te_symbol); + }/* End IF */ + + if(tep->te_filename){ + tec_release(TYPE_C_TAGSTR,tep->te_filename); + }/* End IF */ + + if(tep->te_search_string){ + tec_release(TYPE_C_TAGSTR,tep->te_search_string); + }/* End IF */ + + tec_release(TYPE_C_TAGENT,(char *)tep); + + }/* End While */ + + }/* End FOR */ + + tec_release(TYPE_C_TAGS,(char *)tp); + +}/* End Routine */ + +int +tag_calc_hash(string) +register char *string; +{ +register int hash = 0; +register int c; +register int shift = 0; + + while((c = *string++)){ + shift = shift == 8 ? 0 : shift + 1; + hash += c << shift; + }/* End While */ + + return(hash % TAG_HASH_ENTRIES); + +}/* End Routine */ + +void +tag_dump_database(tp,uct) +register struct tags *tp; +struct cmd_token *uct; +{ +register struct tagent **tepp; +register struct tagent *tep; +register int i; +char tmp_output[LINE_BUFFER_SIZE]; + + if(tp == NULL) return; + + for(i = 0, tepp = &tp->tagents[0]; i < ELEMENTS(tp->tagents); i++,tepp++){ + tep = *tepp; + while(tep){ + + sprintf( + tmp_output, + "sym <%s> hash %d file <%s> line <%s> search <%s>\n", + tep->te_symbol ? tep->te_symbol : "", + tep->te_symbol ? tag_calc_hash(tep->te_symbol) : 0, + tep->te_filename ? tep->te_filename : "", + tep->te_lineno ? tep->te_lineno : "", + tep->te_search_string ? tep->te_search_string : "" + ); + + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + tmp_output, + strlen(tmp_output) + ); + + tep = tep->te_next; + + }/* End While */ + }/* End FOR */ + + return; + +}/* End Routine */ diff --git a/tecdebug.c b/tecdebug.c new file mode 100644 index 0000000..1d06808 --- /dev/null +++ b/tecdebug.c @@ -0,0 +1,401 @@ +char *tecdebug_c_version = "tecdebug.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecdebug.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecdebug.c + * Debugging routines for Teco + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern struct screen_line *saved_screen; + extern char saved_screen_valid; + extern int term_lines; + + extern struct buff_header *buffer_headers; + extern struct buff_header *curbuf; + extern struct buff_header *qregister_push_down_list; + + extern struct format_line *format_line_free_list; + extern struct format_line *format_line_alloc_list; + + extern char format_lines_invalid; + +void +do_preamble_checks() +{ + tecdebug_check_screen_magic(); + tecdebug_check_buffer_magic(); + tecdebug_check_line_magic(); + tecdebug_check_format_magic(); + tecdebug_check_companion_pointers(); + tecdebug_check_window_pointers(); +} + +void +do_return_checks() +{ + do_preamble_checks(); +} + +/* TECDEBUG_CHECK_SCREEN_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks that each saved screen array element has + * the correct magic number. + */ +void +tecdebug_check_screen_magic() +{ +register int i; +register struct screen_line *sbp; +/* + * Basic checks first - check that each saved screen structure has the proper + * magic number in it. + */ + if(saved_screen_valid){ + for(sbp = saved_screen,i = 0; i < term_lines && sbp != NULL;i++,sbp++){ + if(sbp->scr_magic != MAGIC_SCREEN){ + restore_tty(); + fprintf( + stderr, + "?saved_screen[%d] bad magic#, 0x%08x should be 0x%08x\n", + i, + sbp->scr_magic, + (int)MAGIC_SCREEN + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + }/* End IF */ +}/* End Routine */ + + + +/* TECDEBUG_CHECK_BUFFER_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks that each buffer header to check for + * the correct magic number. + */ +void +tecdebug_check_buffer_magic() +{ +register struct buff_header *bp; +register char saw_curbuf; +register int count; + + bp = buffer_headers; + count = 0; + saw_curbuf = 0; + while(bp){ + if(bp == curbuf) saw_curbuf = 1; + if(bp->buf_magic != MAGIC_BUFFER){ + restore_tty(); + fprintf( + stderr, + "?buff_headers[%d] bad magic#, 0x%08x should be 0x%08x\n", + count, + bp->buf_magic, + (int)MAGIC_BUFFER + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + bp = bp->next_header; + }/* End While */ + + if(curbuf != NULL && saw_curbuf == 0){ + restore_tty(); + fprintf( + stderr, + "?curbuf 0x%x not on buff_headers list\n", + (unsigned int)curbuf + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + + bp = qregister_push_down_list; + count = 0; + while(bp){ + if(bp->buf_magic != MAGIC_BUFFER){ + restore_tty(); + fprintf( + stderr, + "?qpushdown[%d] bad magic#, 0x%08x should be 0x%08x\n", + count, + bp->buf_magic, + (int)MAGIC_BUFFER + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + bp = bp->next_header; + }/* End While */ +}/* End Routine */ + + + +/* TECDEBUG_CHECK_LINE_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks that each line buffer data structure for + * the correct magic number. + */ +void +tecdebug_check_line_magic() +{ +register struct buff_header *bp; +register struct buff_line *lbp; +register int count; + + bp = buffer_headers; + while(bp){ + lbp = bp->first_line; + count = 0; + while(lbp){ + if(lbp->lin_magic != MAGIC_LINE){ + restore_tty(); + fprintf( + stderr, + "?buf[%s] line %d bad magic#, 0x%08x should be 0x%08x\n", + bp->name, + count, + lbp->lin_magic, + (int)MAGIC_LINE + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + lbp = lbp->next_line; + }/* End While */ + bp = bp->next_header; + }/* End While */ + + bp = qregister_push_down_list; + while(bp){ + lbp = bp->first_line; + count = 0; + while(lbp){ + if(lbp->lin_magic != MAGIC_LINE){ + restore_tty(); + fprintf( + stderr, + "?qreg pdl line %d bad magic#, 0x%08x should be 0x%08x\n", + count, + lbp->lin_magic, + (int)MAGIC_LINE + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + lbp = lbp->next_line; + }/* End While */ + bp = bp->next_header; + }/* End While */ +}/* End Routine */ + + + +/* TECDEBUG_CHECK_FORMAT_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks each screen format buffer data structure for + * the correct magic number. + */ +void +tecdebug_check_format_magic() +{ +register struct format_line *fp; +register int count; + + fp = format_line_alloc_list; + while(fp){ + if(fp->fmt_magic != MAGIC_FORMAT){ + restore_tty(); + fprintf( + stderr, + "?format line bad magic#, 0x%08x should be 0x%08x\n", + fp->fmt_magic, + (int)MAGIC_FORMAT + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + fp = fp->fmt_next_alloc; + }/* End While */ + + fp = format_line_free_list; + count = 0; + while(fp){ + if(fp->fmt_magic != MAGIC_FORMAT_LOOKASIDE){ + restore_tty(); + fprintf( + stderr, + "?format lookaside[%d] bad magic#, 0x%08x should be 0x%08x\n", + count, + fp->fmt_magic, + (int)MAGIC_FORMAT_LOOKASIDE + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + fp = fp->fmt_next_line; + count += 1; + }/* End While */ + +}/* End Routine */ + + + +/* TECDEBUG_CHECK_COMPANION_POINTERS - Check for pointer consistency + * + * Function: + * + * This routine checks that each saved_screen entry points to + * a single format line which also points back. It's an error + * if a structure points to another structure which doesn't + * point back. + */ +void +tecdebug_check_companion_pointers() +{ +register int i; +register struct screen_line *sbp; +register struct format_line *fbp; + +/* + * First check from the saved screen side that the pointers seem to be + * consistent - i.e. that every structure points to one and only one + * structure which points back. + */ + if(!saved_screen_valid) return; + for(sbp = saved_screen,i = 0; i < term_lines && sbp != NULL;i++,sbp++){ + if(sbp->companion == NULL) continue; + if(sbp->companion->fmt_saved_line != sbp){ + restore_tty(); + fprintf( + stderr, + "?saved_screen[%d] companion 0x%08x ->fmt_saved_line 0x%08x\n", + i, + (unsigned int)sbp->companion, + (unsigned int)sbp->companion->fmt_saved_line + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + +/* + * Now check from the allocated format structure side of things, to make sure + * that each one of them which points to a screen structure is pointed back + * at by that screen structure. + */ + fbp = format_line_alloc_list; + while(fbp){ + if(fbp->fmt_saved_line && fbp->fmt_saved_line->companion != fbp){ + restore_tty(); + fprintf( + stderr, + "?fbp 0x%08x fbp->fmt_saved 0x%08x ->companion 0x%08x\n", + (unsigned int)fbp, + (unsigned int)fbp->fmt_saved_line, + (unsigned int)fbp->fmt_saved_line->companion + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + fbp = fbp->fmt_next_alloc; + }/* End While */ + +}/* End Routine */ + + + +/* TECDEBUG_CHECK_WINDOW_POINTERS - Check for window pointer consistency + * + * Function: + * + * This routine checks that every format line is correctly chained with respect + * to which window it is connected to. An error occurs if the line_buffer does + * not chain down to it. Also, for every "next_window" pointer, all format + * structures chained off of it must belong to the same window. + */ +void +tecdebug_check_window_pointers() +{ +register struct format_line *fbp,*fl_win_head,*fl_temp; +register struct window *wptr; +register struct buff_line *blp; +char saw_our_format_line; + + if(format_lines_invalid) return; + +/* + * Check each format line on the allocated list + */ + for(fbp = format_line_alloc_list; fbp != NULL; fbp = fbp->fmt_next_alloc){ + saw_our_format_line = 0; + blp = fbp->fmt_buffer_line; + fl_win_head = blp->format_line; + for(; fl_win_head != NULL; fl_win_head = fl_win_head->fmt_next_window){ + wptr = fl_win_head->fmt_window_ptr; + for(fl_temp = fl_win_head; fl_temp != NULL; fl_temp = fl_temp->fmt_next_line){ + if(fl_temp == fbp) saw_our_format_line = 1; + if(fl_temp->fmt_window_ptr != wptr){ + restore_tty(); + fprintf( + stderr, + "?wrong window in fmt chain format_line 0x%08x format_line->wptr 0x%08x wptr 0x%08x\n", + (unsigned int)fl_temp, + (unsigned int)fl_temp->fmt_window_ptr, + (unsigned int)wptr + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + if(fl_temp->fmt_buffer_line != blp){ + restore_tty(); + fprintf( + stderr, + "?wrong buffer_line in fmt chain fmt 0x%08x fmt->fmt_buffer_line 0x%08x lbp 0x%08x\n", + (unsigned int)fl_temp, + (unsigned int)fl_temp->fmt_buffer_line, + (unsigned int)blp + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + }/* End FOR */ + if(saw_our_format_line == 0){ + restore_tty(); + fprintf( + stderr, + "?never encountered our format line 0x%08x in format list\n", + (unsigned int)fbp + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + +}/* End Routine */ diff --git a/tecdisp.c b/tecdisp.c new file mode 100644 index 0000000..a1d2bb6 --- /dev/null +++ b/tecdisp.c @@ -0,0 +1,2867 @@ +char *tecdisp_c_version = "tecdisp.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecdisp.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecdisp.c + * Terminal Screen Display Subroutines + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#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 + +/* + * 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; + +/* SCREEN_INIT - Initialize the TECO screen package + * + * Function: + * + * 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++){ + lp->scr_magic = 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; + message_line.fmt_magic = MAGIC_FORMAT_LOOKASIDE; + lp->companion = &message_line; + + lp += 1; + echo_line.fmt_permanent = 1; + echo_line.fmt_saved_line = lp; + echo_line.fmt_magic = MAGIC_FORMAT_LOOKASIDE; + lp->companion = &echo_line; + + screen_label_line(curbuf," TECO",LABEL_C_TECONAME); + + return(SUCCESS); + +}/* End Routine */ + + + +/* SCREEN_RESIZE - Cause the screen to be refreshed at a new size + * + * Function: + * + * 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 0x%x != ->fmt_saved_line 0x%x\n", + (unsigned int)lp->companion, + (unsigned int)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 */ + + + +/* SCREEN_FINISH - Here when we are exiting the editor + * + * Function: + * + * 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 */ + + + +/* SCREEN_REFRESH - Redraw the screen based on new database + * + * Function: + * + * 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 */ + + + +/* SCREEN_OPTIMIZE_LINES - Optimize output with insert/delete line operations + * + * Function: + * + * 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){ + void screen_message(); + 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 */ + + + +/* SCREEN_ACCOUNT_FOR_DELETE_LINE - Update screen database + * + * Function: + * + * 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(y,count) +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 */ + +/* SCREEN_ACCOUNT_FOR_INSERT_LINE - Update screen database + * + * Function: + * + * 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(y,count) +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 */ + + + +/* SCREEN_OPTIMIZE_BY_PATTERN_MATCHING - More insert/delete line optimizations + * + * Function: + * + * 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 */ + + + +/* SCREEN_ECHO - Echo the character + * + * Function: + * + * 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 '<CR>' when it is an echo + * operation. + */ +void +screen_echo(data) +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; i < strlen("<CR>"); i++){ + screen_echo("<CR>"[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; 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 */ + +/* SCREEN_RESET_ECHO - Reset the echo line + * + * Function: + * + * 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(ct) +register 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 */ + + + +/* SCREEN_MESSAGE - Place a message in the message line + * + * Function: + * + * 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(string) +register 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 = "<CR>"; + continue; + case RUBOUT: + mep = "<RUBOUT>"; + 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 */ + +/* ERROR_MESSAGE - Place an error message in the message line + * + * Function: + * + * 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(string) +char *string; +{ + PREAMBLE(); + + screen_message(string); + ring_audible_bell = 1; + +}/* End Routine */ + +/* SCREEN_RESET_MESSAGE - Reset the message line + * + * Function: + * + * 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 */ + +/* SCREEN_SAVE_CURRENT_MESSAGE - Copy out the current message + * + * Function: + * + * This routine copies out the current message up to a certain length. + */ +void +screen_save_current_message(message_save_buff,message_save_max_length) +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 */ + + + +/* SCREEN_LABEL_LINE - Routine to change the contents of the label line + * + * Function: + * + * This routine is called to change the label line + */ +int +screen_label_line(buffer,string,field) +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 */ + + + +/* SCREEN_LABEL_WINDOW - Routine to set the window number field + * + * Function: + * + * 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 */ + + + +/* SCREEN_FORMAT_WINDOWS - Generate display contents for all windows + * + * Function: + * + * 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 */ + +/* SCREEN_REFORMAT_WINDOWS - Reformat windows (because tab size changed) + * + * Function: + * + * 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 0x%x != ->fmt_saved_line 0x%x\n", + (unsigned int)lp->companion, + (unsigned int)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 */ + +/* SCREEN_DISPLAY_WINDOW - Generate display for a window + * + * Function: + * + * 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(wptr) +register 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 */ + + + +/* SCREEN_REBUILD_FROM_SCRATCH - Things have changed a lot, just rebuild it + * + * Function: + * + * This routine is called to build up the format line descriptor + * structures which represent the current edit buffer. + */ +int +screen_rebuild_from_scratch(wptr) +register 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 */ + + + +/* SCREEN_SCROLL - Move the current screen up or down specified # lines + * + * Function: + * + * 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(count) +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 */ + + + +/* SCREEN_GC_FORMAT_LINES - Garbage collect unused format line structures + * + * Function: + * + * 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 */ + + + +/* SCREEN_FREE_WINDOW_FORMAT_LINES - Deallocate a window's format lines + * + * Function: + * + * 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(wptr,lbp) +struct window *wptr; +register 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 */ + + + +/* SCREEN_FREE_FORMAT_LINES - Deallocate a list of format lines + * + * Function: + * + * 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(sbp) +register 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 0x%x != sbp->...companion 0x%x\n", + (unsigned int)sbp->fmt_saved_line, + (unsigned int)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. + */ + sbp->fmt_magic = 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(sbp,who) +register 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 0x%x != sbp->...companion 0x%x\n", + who, + (unsigned int)sbp->fmt_saved_line, + (unsigned int)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 */ + +} + + +/* SCREEN_DEALLOCATE_FORMAT_LOOKASIDE_LIST + * + * Function: + * + * 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; + sbp->fmt_magic = 0; + tec_release(TYPE_C_SCREENBUF,(char *)sbp->fmt_buffer); + tec_release(TYPE_C_SCREEN,(char *)sbp); + }/* End While */ + +}/* End Routine */ + + + +/* SCREEN_FIND_WINDOW_FORMAT_LINE - Finds format lines for a particular window + * + * Function: + * + * 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(wptr,lbp) +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 */ + + + +/* SCREEN_FORMAT_BUFF_LINE - Create the format_line structures for a buff line + * + * Function: + * + * 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(wptr,lbp) +struct window *wptr; +struct buff_line *lbp; +{ +register struct format_line *sbp; +register char *cp; +register short *sp; +register unsigned int c; +register int i; +int byte_count; +int current_column; +char *multi_byte_echo; +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; + + while(byte_count > 0){ + + 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){ + c = *multi_byte_echo++; + if(*multi_byte_echo == '\0') multi_byte_echo = NULL; + }/* End IF */ + + else { + c = *cp++; + byte_count -= 1; + }/* End Else */ + + c &= 0xFF; + + 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': + *sp++ = c; + current_column = 0; + break; + default: + if(c < ' '){ + if( hide_cr_flag ){ + expand_buffer[0] = ' '; + expand_buffer[1] = '\0'; + } else { + expand_buffer[0] = '^'; + expand_buffer[1] = c + 'A' - 1; + expand_buffer[2] = '\0'; + } + 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 >= 128){ + c -= 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 */ + + + +/* SCREEN_FIND_DOT - Find position of dot in a format_line + * + * Function: + * + * 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(wptr) +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 */ + + + +/* ALLOCATE_FORMAT_BUFFER - Routine to return a format_line structure + * + * Function: + * + * 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(wptr,lbp) +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 == 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); + + 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 */ + + sbp->fmt_magic = 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 */ + + + +/* SCREEN_REDRAW - Cause a redraw of the screen + * + * Function: + * + * 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 */ + + + +/* CREATE_WINDOW - Create a window data structure + * + * Function: + * + * 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(x_size,y_size) +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); + + 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++; + wptr->win_next_window = NULL; + wptr->win_buffer = NULL; +/* + * 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_base = 0; + 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_next_line = + wptr->win_label_line.fmt_prev_line = + wptr->win_label_line.fmt_next_alloc = + wptr->win_label_line.fmt_prev_alloc = NULL; + + wptr->win_label_line.fmt_permanent = 1; + wptr->win_label_line.fmt_sequence = 0; + wptr->win_label_line.fmt_owning_buffer = NULL; + wptr->win_label_line.fmt_visible_line_position = -1; + + wptr->win_label_line.fmt_window_ptr = wptr; /* FIX */ + wptr->win_label_line.fmt_buffer_line = NULL; + 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; + + for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ + wptr->win_label_field_contents[i] = NULL; + }/* End FOR */ + + return(wptr); + +}/* End Routine */ + + + +/* SCREEN_SPLIT_WINDOW - Make two where there is only one + * + * Function: + * + * 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(old_wptr,lines,buffer_number) +register 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 */ + + + +/* SCREEN_DELETE_WINDOW - Here to delete a window + * + * Function: + * + * 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(old_wptr) +register 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 */ + + + +/* WINDOW_SWITCH - Switch to a new window + * + * Function: + * + * This function is called to switch the current window to a different + * window. + */ +int +window_switch(window_number) +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 */ diff --git a/tecexec.c b/tecexec.c new file mode 100644 index 0000000..6d94fd5 --- /dev/null +++ b/tecexec.c @@ -0,0 +1,2801 @@ +char *tecexec_c_version = "tecexec.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecexec.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecexec.c + * The SWITCH/CASE statements which implement execution stage of the parser + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern struct cmd_token *jump_to_token; + extern struct cmd_token *last_token_executed; + extern struct cmd_token *last_cmd_list; + extern struct search_buff search_string; + extern char user_message[PARSER_STRING_MAX]; + + extern char exit_flag; + extern struct window *window_list; + extern struct buff_header *curbuf,*buffer_headers; + extern struct buff_header *qregister_push_down_list; + extern int last_search_pos1,last_search_pos2; + extern int last_search_status; + extern int remembered_dot; + extern char intr_flag; + extern char immediate_execute_flag; + extern struct window *curwin; + + int find_conditional_else(struct cmd_token *); + int find_conditional_end(struct cmd_token *); + int compare_label(struct cmd_token *,struct cmd_token *); + void extract_label(struct cmd_token *,char *); + + + +/* EXECUTE_A_STATE - Do the runtime execution part of a command + * + * Function: + * + * This routine is called to execute the runtime part of a command token. + * This could be during immediate execution mode, directly after a token + * gets parsed, or it could be during final stages when we are either + * running commands which cannot be undone (like ex), or simply complex + * things like an iteration. The execute_state which we dispatch on was + * set by the parse state during syntax analysis. + */ +int +execute_a_state(ct,uct) +register struct cmd_token *ct; +struct cmd_token *uct; +{ +register struct undo_token *ut; +char tmp_buffer[LINE_BUFFER_SIZE],tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + + last_token_executed = ct; + + switch(ct->execute_state){ +/* + * Here to return the numeric value of a q-register. This has to be done + * at run time because the value of the q-register might be modified during + * the course of the command. Note that we return an error if the q-register + * has never been loaded. Otherwise, we return the value in tmpval so that + * it can be the input to further arithmetic expressions. + */ + case EXEC_C_QVALUE: + {/* Local Block */ + register struct buff_header *hbp; + + if(ct->q_register == '*'){ + ct->ctx.tmpval = curbuf->buffer_number; + return(SUCCESS); + }/* End IF */ + + hbp = buff_qfind(ct->q_register,0); + if(hbp == NULL){ + sprintf(tmp_message,"?Q-register %c empty",ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + ct->ctx.tmpval = hbp->ivalue; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to return the numeric value of a q-register and also increment it's + * value. Other than the incrementing, this is exactly like the QVALUE state. + */ + case EXEC_C_PERCENT_VALUE: + {/* Local Block */ + register struct buff_header *hbp; + + hbp = buff_qfind(ct->q_register,0); + if(hbp == NULL){ + sprintf(tmp_message,"?Q-register %c empty",ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Set up an undo block so that we can put the previous value back into + * the q-register if this command gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = ct->q_register; + ut->iarg2 = hbp->ivalue; + + hbp->ivalue += ct->ctx.tmpval; + ct->ctx.tmpval = hbp->ivalue; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to read a file into a q-register. We also set up undo tokens to + * restore the previous contents of the q-register if he undoes this. + */ + case EXEC_C_EQQREGISTER: + {/* Local Block */ + register struct buff_header *qbp; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = curbuf->buffer_number; + + qbp = buff_qfind(ct->ctx.tmpval,1); + + if(ct->ctx.tmpval == '*'){ + if(ct->ctx.carg){ + rename_edit_buffer(curbuf,ct->ctx.carg,uct); + }/* End IF */ + else { + load_qname_register(); + buff_switch(qbp,1); + }/* End Else */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + }/* End IF */ + + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + buff_switch(qbp,1); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + }/* End IF */ +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. Also, if the colon modifier was + * specified, then we append to the buffer instead of replacing it. + */ + if((ct->ctx.flags & CTOK_M_COLON_SEEN) == 0 && qbp->zee){ + buff_delete_with_undo(uct,qbp,0,qbp->zee); + }/* End IF */ + +/* + * Set up an undo argument to delete any characters which get inserted as + * part of the buff_read we do to load the q-register. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = qbp->dot; + ut->iarg2 = qbp->zee; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + sprintf(tmp_message,"EQ No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + while(np){ + buff_read(qbp,np->we_name); + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + ut->iarg2 = qbp->zee - ut->iarg2; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to load a q-register with a numeric value. Note that not only do we + * have to put the new number into it, we also have to set up the undo token + * so that if this command is backed out of we can restore the previous value + * to the q-register. + */ + case EXEC_C_UQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; +/* + * Find the q-register. If it does not exist yet, create it. + */ + hbp = buff_qfind(ct->q_register,1); + if(hbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Insure that we have an argument to store + */ + if(ct->ctx.iarg1_flag == NO){ + error_message("?Argument required for U command"); + return(FAIL); + }/* End IF */ +/* + * Set up an undo block so that we can put the previous value back into + * the q-register if this command gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = ct->q_register; + ut->iarg2 = hbp->ivalue; + + hbp->ivalue = ct->ctx.iarg1; + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to load the text contents of the specified q-register into the + * edit buffer at the current location. + */ + case EXEC_C_GQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_header *qbp; + + hbp = curbuf; +/* + * If this is the buffer name q-register, we have to do some special + * stuff. + */ + if(ct->q_register == '*') load_qname_register(); +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, declare an error. + */ + qbp = buff_qfind(ct->q_register,0); + if(qbp == NULL){ + sprintf(tmp_message,"?Q-register <%c> does not exist", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Test if the q-register is empty, and declare an error if it is + */ + if(qbp->zee == 0){ + sprintf(tmp_message,"?Q-register <%c> is empty", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Make sure the Q register and the edit buffer are not the same buffer + */ + if(qbp == hbp){ + strcpy(tmp_message, + "?Cannot use G to copy from a Q-register to itself"); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Now copy the text out of the q-register and insert it into the edit buffer + */ + buff_insert_from_buffer_with_undo(uct,hbp,hbp->dot,qbp,0,qbp->zee); + + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to execute the macro in the specified Q register + */ + case EXEC_C_MQREGISTER: + {/* Local Block */ + register struct buff_header *qbp; +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, declare an error. + */ + qbp = buff_qfind(ct->q_register,0); + if(qbp == NULL){ + sprintf(tmp_message,"?Q-register <%c> does not exist", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * It's an error if the q-register is empty + */ + if(qbp->zee == 0){ + sprintf(tmp_message,"?Q-register <%c> is empty", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Set up the undo block, and pass its address to the tecmacro routine so + * that the tecmacro can chain the command list off of it. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_MACRO; + ut->carg1 = NULL; + + return(tecmacro(qbp,ct,(struct cmd_token **)&ut->carg1)); + + }/* End Local Block */ + +/* + * Here to load text into a q-register in response to the X command. + * This clears any existing text from the q-register, and then copies + * the specified text into it. + */ + case EXEC_C_XQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_header *qbp; + register struct buff_line *lbp; + register int i,j; + int pos1,pos2; + + hbp = curbuf; +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(ct->q_register,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Make sure the Q register and the edit buffer are not the same buffer + */ + if(qbp == hbp){ + strcpy(tmp_message, + "?Cannot use X to copy from a Q-register to itself"); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * If two arguments were specified, then he has specified an a,b range to + * be copied. + */ + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg2_flag == YES){ + pos1 = ct->ctx.iarg1; + pos2 = ct->ctx.iarg2; + if(parse_illegal_buffer_position(pos1,pos2,"X")){ + return(FAIL); + }/* End IF */ + if(pos2 < pos1){ + pos2 = ct->ctx.iarg1; + pos1 = ct->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * Else, if it was a single command, it is a relative copy which affects a + * portion of the buffer in a similar way as if it was an L command. Our job + * now is to turn it into a a,b range. + */ + else { + lbp = buff_find_line(hbp,hbp->dot); + if(lbp == NULL) return(FAIL); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = 1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with X"); + return(FAIL); + }/* End IF */ + pos1 = hbp->dot; + pos2 = hbp->dot + j; + }/* End IF */ +/* + * Else, it is backwards + */ + else { + + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with X"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + pos1 = hbp->dot + j; + pos2 = hbp->dot; + }/* End Else */ + }/* End Else */ +/* + * Now that we have calculated the buffer positions to be used by the command, + * we can actually do the work. + */ + j = pos2 - pos1; +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. Also, if the colon modifier was + * specified, then we append to the buffer instead of replacing it. + */ + if((ct->ctx.flags & CTOK_M_COLON_SEEN) == 0 && qbp->zee){ + buff_delete_with_undo(uct,qbp,0,qbp->zee); + }/* End IF */ +/* + * Now copy the new text from the edit buffer into the q-register + */ + buff_insert_from_buffer_with_undo(uct,qbp,qbp->zee,hbp,pos1,j); +/* + * If the q-register specified is '*', then the actual result is to rename + * the current buffer. + */ + if(ct->q_register == '*'){ + rename_edit_buffer(hbp,qbp->name,uct); + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to load text into a q-register in response to the * command. + * This clears any existing text from the q-register, and then copies + * the previous command string into it. + */ + case EXEC_C_SAVECOMMAND: + {/* Local Block */ + register struct buff_header *qbp; + register struct cmd_token *oct; + register int i; + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(ct->q_register,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. + */ + if(qbp->zee) buff_delete_with_undo(uct,qbp,0,qbp->zee); +/* + * Set up an undo token to delete the contents of the q-register that we + * are about to load. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = 0; + ut->iarg2 = 0; + +/* + * Now we go through the last command list and anytime we see an input + * byte, we copy it into the q-register. + */ + oct = last_cmd_list; + i = 0; + while(oct){ + if(oct->flags & TOK_M_EAT_TOKEN){ + buff_insert_char(qbp,i++,oct->input_byte); + ut->iarg2 += 1; + }/* End IF */ + oct = oct->next_token; + }/* End While */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to push the specified q-register onto the q-register stack + */ + case EXEC_C_PUSH_QREGISTER: + if(ct->q_register == '*') load_qname_register(); + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + if(push_qregister(ct->q_register) != SUCCESS) return(FAIL); + ut->opcode = UNDO_C_POP; + return(SUCCESS); +/* + * Here to pop a Q register off of the stack and replace a Q register + * with it. Since this destroys the Q register we pop onto, we first + * must save the entire Q register. + */ + case EXEC_C_POP_QREGISTER: + {/* Local Block */ + register struct buff_header *pbp; + register struct buff_header *qbp; +/* + * Get a pointer to the pushed buffer structure. If there are none on + * the stack, then he has tried to pop too many, and this is an error. + */ + pbp = qregister_push_down_list; + + if(pbp == NULL){ + error_message("?Q-register push down list is empty"); + return(FAIL); + }/* End IF */ +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(ct->q_register,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Arrange to restore the numeric contents of the Q register if this + * should be undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = ct->q_register; + ut->iarg2 = qbp->ivalue; + + qbp->ivalue = pbp->ivalue; +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. + */ + if(qbp->zee){ + if(buff_delete_with_undo(uct,qbp,0,qbp->zee) == FAIL){ + return(FAIL); + }/* End IF */ + }/* End IF */ +/* + * Arrange for it to be deleted if this gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = 0; + ut->iarg2 = pbp->zee; +/* + * Transfer the contents of the pushed register into the actual Q register + */ + buff_bulk_insert(qbp,0,pbp->zee,pbp->first_line); + pbp->first_line = NULL; + qbp->pos_cache.lbp = NULL; + qbp->pos_cache.base = 0; + +/* + * Arrange that the push be re-done if the pop is undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_PUSH; + ut->iarg1 = ct->q_register; +/* + * Place it on the main buffer list so that buff_destroy can find it + */ + qregister_push_down_list = pbp->next_header; + pbp->next_header = buffer_headers; + buffer_headers = pbp; + buff_destroy(pbp); +/* + * If the q-register specified is '*', then the actual result is to rename + * the current buffer. + */ + if(ct->q_register == '*'){ + rename_edit_buffer(curbuf,qbp->name,uct); + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to return the current position of 'dot' + */ + case EXEC_C_DOTVALUE: + ct->ctx.tmpval = curbuf->dot; + return(SUCCESS); +/* + * Here to return the number of bytes in the edit buffer + */ + case EXEC_C_ZEEVALUE: + ct->ctx.tmpval = curbuf->zee; + return(SUCCESS); +/* + * Here to return the value 0,Z which H is shorthand for + */ + case EXEC_C_HVALUE: + ct->ctx.iarg1 = 0; + ct->ctx.iarg2 = curbuf->zee; + return(SUCCESS); +/* + * Print out the numeric value of an expression + */ + case EXEC_C_EQUALS: + if(ct->ctx.iarg1_flag == NO){ + error_message("?Argument required for = command"); + return(FAIL); + }/* End IF */ + + sprintf(tmp_buffer,"value = %d",ct->ctx.iarg1); + screen_message(tmp_buffer); + return(SUCCESS); + + case EXEC_C_TWO_EQUALS: + sprintf(tmp_buffer,"value = 0%o",ct->ctx.iarg1); + screen_message(tmp_buffer); + return(SUCCESS); + + case EXEC_C_THREE_EQUALS: + sprintf(tmp_buffer,"value = 0x%X",ct->ctx.iarg1); + screen_message(tmp_buffer); + return(SUCCESS); +/* + * Here to insert the value of the expression into the edit buffer as a + * decimal representation. + */ + case EXEC_C_BACKSLASH: + {/* Local Block */ + register int i,j; + char *cp; + int length = 0; + + if(ct->ctx.iarg2 < 2 || ct->ctx.iarg2 > 36) ct->ctx.iarg2 = 10; + j = ct->ctx.iarg1; + cp = tmp_buffer + sizeof(tmp_buffer); + do { + i = j % ct->ctx.iarg2; + j = (j - i) / ct->ctx.iarg2; + if(i > 9) i += ('A' - 10); + else i += '0'; + *(--cp) = i; + length += 1; + } while(j); + + buff_insert_with_undo(uct,curbuf,curbuf->dot,cp,length); + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to interpret the ascii digits following 'dot' in the edit buffer + * as a decimal number, and return this number as the value of this expression, + * leaving 'dot' positioned after the digits. + */ + case EXEC_C_BACKSLASHARG: + {/* Local Block */ + register int i; + register int radix; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + radix = ct->ctx.tmpval; + ct->ctx.tmpval = 0; + + for(;;){ + i = buff_contents(curbuf,curbuf->dot); + + if(i >= '0' && i <= '9') i -= '0'; + else if(i >= 'a' && i <= 'z') i -= ('a' - 10); + else if(i >= 'A' && i <= 'Z') i -= ('A' - 10); + else break; + + if(i < 0 || i >= radix) break; + + ct->ctx.tmpval = ct->ctx.tmpval * radix + i; + if(curbuf->dot == curbuf->zee) break; + curbuf->dot++; + }/* End FOR */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * The poor bastard wants to leave... + */ + case EXEC_C_EXITCOMMAND: + {/* Local Block */ + register struct buff_header *hbp; + + if(ct->ctx.iarg1_flag == NO || ct->ctx.iarg1 != -1){ + for(hbp = buffer_headers; hbp != NULL; hbp = hbp->next_header){ + if(hbp->buffer_number <= 0) continue; + if(hbp->ismodified != 0 && hbp->isreadonly == 0){ + error_message("?Modified buffers exist"); + return(FAIL); + }/* End IF */ + }/* End FOR */ + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_EXIT_FLAG; + + exit_flag = YES; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to jump to an absolute position in the buffer + */ + case EXEC_C_JUMP: + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg1 < 0){ + error_message("?Negative Argument to J"); + return(FAIL); + }/* End IF */ + + if(ct->ctx.iarg1_flag == NO) ct->ctx.iarg1 = 0; + + if(parse_illegal_buffer_position(ct->ctx.iarg1,0,"J")){ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + if(ct->ctx.iarg1_flag == NO) curbuf->dot = 0; + else curbuf->dot = ct->ctx.iarg1; + return(SUCCESS); +/* + * Here to move dot by a relative number of buffer positions + */ + case EXEC_C_CHAR: + {/* Local Block */ + register int i; + + i = curbuf->dot + 1; + + if(ct->ctx.iarg1_flag == YES){ + i = curbuf->dot + ct->ctx.iarg1; + }/* End IF */ + + if(parse_illegal_buffer_position(i,0,"C")) return(FAIL); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + curbuf->dot = i; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to move dot by a relative number of buffer positions, just like + * the "C" command, except that the direction is by default backwards. + */ + case EXEC_C_RCHAR: + {/* Local Block */ + register int i; + + i = curbuf->dot - 1; + if(ct->ctx.iarg1_flag == YES){ + i = curbuf->dot - ct->ctx.iarg1; + }/* End IF */ + + if(parse_illegal_buffer_position(i,0,"R")) return(FAIL); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + curbuf->dot = i; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to delete the specified number of characters following 'dot'. + * A negative value indicates we should delete characters BEFORE 'dot'. + */ + case EXEC_C_DELETE: + {/* Local Block */ + register int i; + + i = 1; + if(ct->ctx.iarg1_flag == YES) i = ct->ctx.iarg1; + + if(parse_illegal_buffer_position(curbuf->dot+i,0,"D")){ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + if(i < 0){ + i = 0 - i; + curbuf->dot -= i; + }/* End IF */ + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(i) buff_delete_with_undo(uct,curbuf,curbuf->dot,i); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to move 'dot' by the specified number of words. Note that words + * can have a pretty nebulous definition, depending on what you are doing. + * This should probably have a q-register which contains all of the delimeters. + */ + case EXEC_C_WORD: + {/* Local Block */ + register int count; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + count = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) count = 1; + + cmd_wordmove(count); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to delete the specified number of words. Note that words + * can have a pretty nebulous definition, depending on what you are doing. + * This should probably have a q-register which contains all of the delimeters. + */ + case EXEC_C_DELWORD: + {/* Local Block */ + register int count; + int original_position; + int pos; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = original_position = curbuf->dot; + + count = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) count = 1; + + cmd_wordmove(count); + + pos = original_position; + count = curbuf->dot - original_position; + if(count < 0){ + count = 0 - count; + pos = curbuf->dot; + }/* End IF */ + + if(count) buff_delete_with_undo(uct,curbuf,pos,count); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to delete backward the specified number of words. Note that + * words can have a pretty nebulous definition, depending on what you are + * doing. This should probably have a q-register which contains all of the + * delimeters. + */ + case EXEC_C_RDELWORD: + {/* Local Block */ + register int count; + int original_position; + int pos; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = original_position = curbuf->dot; + + count = 0 - ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) count = -1; + + cmd_wordmove(count); + + pos = original_position; + count = curbuf->dot - original_position; + if(count < 0){ + count = 0 - count; + pos = curbuf->dot; + }/* End IF */ + + if(count) buff_delete_with_undo(uct,curbuf,pos,count); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to move the 'dot' by the specified number of lines. + */ + case EXEC_C_LINE: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_line *lbp; + register int i,j; + + hbp = curbuf; + lbp = buff_find_line(hbp,hbp->dot); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = 1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with L"); + return(FAIL); + }/* End IF */ + }/* End IF */ + + else { + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with L"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + }/* End Else */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = hbp->dot; + + hbp->dot += j; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to move the 'dot' backward by the specified number of lines. + */ + case EXEC_C_RLINE: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_line *lbp; + register int i,j; + + hbp = curbuf; + lbp = buff_find_line(hbp,hbp->dot); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = 0 - ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = -1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with B"); + return(FAIL); + }/* End IF */ + }/* End IF */ + + else { + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with B"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + }/* End Else */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = hbp->dot; + + hbp->dot += j; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to kill the specified number of lines. + */ + case EXEC_C_KILL: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_line *lbp; + register int i,j; + int pos1,pos2; + + hbp = curbuf; +/* + * If two arguments were specified, then he has specified an a,b range to + * be killed. + */ + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg2_flag == YES){ + pos1 = ct->ctx.iarg1; + pos2 = ct->ctx.iarg2; + if(parse_illegal_buffer_position(pos1,pos2,"K")){ + return(FAIL); + }/* End IF */ + if(pos2 < pos1){ + pos2 = ct->ctx.iarg1; + pos1 = ct->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * Else, if it was a single command, it is a relative kill which affects a + * portion of the buffer in a similar way as if it was an L command. Our job + * now is to turn it into a a,b range. + */ + else { + lbp = buff_find_line(hbp,hbp->dot); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = 1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with K"); + return(FAIL); + }/* End IF */ + pos1 = hbp->dot; + pos2 = hbp->dot + j; + }/* End IF */ + + else { + + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with K"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + pos1 = hbp->dot + j; + pos2 = hbp->dot; + }/* End Else */ + }/* End Else */ + + j = pos2 - pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = hbp->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,hbp,pos1,j); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to insert a character as part of an insert command + */ + case EXEC_C_INSERT: + buff_insert_char_with_undo( + uct, + curbuf, + curbuf->dot, + ct->ctx.iarg1_flag ? ct->ctx.iarg1 : ct->input_byte + ); + return(SUCCESS); +/* + * This state is used when a subroutine state is trying to return a + * value to the calling state. It gets specially noticed by the parser, + * so it really doesn't do much except act as a flag. + */ + case EXEC_C_STOREVAL: + return(SUCCESS); +/* + * Store the current expression value as the first argument to a command + */ + case EXEC_C_STORE1: + ct->ctx.iarg1 = ct->ctx.tmpval; + return(SUCCESS); +/* + * Store the current expression value as the second argument to a command + */ + case EXEC_C_STORE2: + ct->ctx.iarg2 = ct->ctx.tmpval; + return(SUCCESS); +/* + * Here when we have a leading minus sign in an expression such as -3+4 + */ + case EXEC_C_UMINUS: + ct->ctx.tmpval = 0 - ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to add the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_PLUS: + ct->ctx.tmpval = ct->ctx.iarg2 + ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to subtract the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_MINUS: + ct->ctx.tmpval = ct->ctx.iarg2 - ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to multiply the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_TIMES: + ct->ctx.tmpval = ct->ctx.iarg1 * ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to divide the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_DIVIDE: + if(ct->ctx.tmpval == 0){ + error_message("?Attempt to Divide by Zero"); + return(FAIL); + }/* End IF */ + ct->ctx.tmpval = ct->ctx.iarg1 / ct->ctx.tmpval; + return(SUCCESS); +/* + * Here in response to the "A" command which returns as a value the ASCII + * value of the character at 'dot' + offset. + */ + case EXEC_C_ACOMMAND: + {/* Local Block */ + register int i; + + i = curbuf->dot + ct->ctx.tmpval; + + if(parse_illegal_buffer_position(i,i+1,"A")){ + return(FAIL); + }/* End IF */ + + ct->ctx.tmpval = buff_contents(curbuf,i); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to cause a repaint of the screen incase it got messed up by + * something + */ + case EXEC_C_REDRAW_SCREEN: + screen_redraw(); + return(SUCCESS); +/* + * Here to search for a given string. This gets used not only in the + * "S" command, but as a substate in any command which requires searching + * for a string (such as FD, FK, FS, FR, etc.) + */ + case EXEC_C_SEARCH: + {/* Local Block */ + int arg1,arg2; +#if 0 + struct buff_header *qbp; +#endif + + if(set_search_string_with_undo(ct->ctx.carg,uct) == FAIL){ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + arg1 = 1; + arg2 = -1; + if(ct->ctx.iarg1_flag) arg1 = ct->ctx.iarg1; + if(ct->ctx.iarg2_flag) arg2 = ct->ctx.iarg2; + +/* + * If there are two arguments, then it is a constrained search and we must + * insure that the two positions specified are valid buffer positions. + */ + if(ct->ctx.iarg1_flag && ct->ctx.iarg2_flag){ + if(arg1 < 0 || arg1 > curbuf->zee){ + sprintf(tmp_message, + "?Illegal buffer position %d in search command",arg1); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + if(arg2 < 0 || arg2 > curbuf->zee){ + sprintf(tmp_message, + "?Illegal buffer position %d in search command",arg2); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + }/* End IF */ + +/* + * That done, we call the search command to perform the search + */ + if(cmd_search(arg1,arg2,&search_string) == FAIL){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + + if(!ct->ctx.inest && search_string.error_message_given == NO){ + sprintf(tmp_message, + "?Cannot find '%.40s'",search_string.input); + error_message(tmp_message); + }/* End IF */ + + return(SUCCESS); + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to implement the FR command. The FR command is similar to the FS + * command (Find-Substitute), except that if the second string argument is + * null, rather than simply deleting the first string argument, it is replaced + * by the default replace string. + */ + case EXEC_C_FRREPLACE: + {/* Local Block */ + register struct buff_header *qbp; + register int j; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + j = last_search_pos2 - last_search_pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + qbp = buff_qfind('-',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + + buff_insert_from_buffer_with_undo( + uct, + curbuf, /* destination is current edit buf */ + last_search_pos1, /* at the search position */ + qbp, /* source is replacement q-register*/ + 0, /* from position 0 to the end */ + qbp->zee + ); + + if(ct->ctx.iarg1_flag == YES){ + if( ct->ctx.iarg1 < 0){ + curbuf->dot = last_search_pos1; + }/* End IF */ + else if(ct->ctx.iarg2_flag == YES){ + if(ct->ctx.iarg2 < ct->ctx.iarg1){ + curbuf->dot = last_search_pos1; + }/* End IF */ + }/* End Else */ + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to implement the FS (Find-Substitute) command which searches for + * the first string argument and replaces it with the second. + */ + case EXEC_C_FSREPLACE1: + {/* Local Block */ + register int j; + register struct buff_header *qbp; + + ct->ctx.tmpval = 0; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + +#ifdef INTERACTIVE_FS + j = last_search_pos2 - last_search_pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); +#endif /* INTERACTIVE_FS */ + + return(SUCCESS); + + case EXEC_C_FSREPLACE2: + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + qbp = buff_qfind('-',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + +#ifdef INTERACTIVE_FS + + buff_insert_char_with_undo(uct,curbuf,curbuf->dot,ct->input_byte); + +#endif /* INTERACTIVE_FS */ + +/* + * What this does is hope that the replacement string is the same as the + * previous one. Only if we get a miscompare do we delete the contents of + * the replacement q-register, and insert the new string. + */ + if(qbp->zee > ct->ctx.tmpval){ + if(ct->input_byte == buff_contents(qbp,ct->ctx.tmpval)){ + ct->ctx.tmpval += 1; + return(SUCCESS); + } + + buff_delete_with_undo(uct,qbp,ct->ctx.tmpval, + qbp->zee-ct->ctx.tmpval); + + } + + buff_insert_char_with_undo(uct,qbp,ct->ctx.tmpval,ct->input_byte); + + ct->ctx.tmpval += 1; + return(SUCCESS); + + case EXEC_C_FSREPLACE3: + + qbp = buff_qfind('-',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + + if(qbp->zee > ct->ctx.tmpval){ + buff_delete_with_undo(uct,qbp,ct->ctx.tmpval, + qbp->zee-ct->ctx.tmpval); + } + +#ifndef INTERACTIVE_FS + j = last_search_pos2 - last_search_pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + buff_insert_from_buffer_with_undo(uct, + curbuf, + curbuf->dot, + qbp, + 0, + qbp->zee + ); + +#endif /* !INTERACTIVE_FS */ + + if(ct->ctx.iarg1_flag == YES){ + if( ct->ctx.iarg1 < 0){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + curbuf->dot = last_search_pos1; + }/* End IF */ + else if(ct->ctx.iarg2_flag == YES){ + if(ct->ctx.iarg2 < ct->ctx.iarg1){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + curbuf->dot = last_search_pos1; + }/* End IF */ + }/* End Else */ + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = last_search_status; + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to execute the FT (tags) command. Since this is a fairly complicated + * command, we call a subroutine to perform the actions. + */ + case EXEC_C_FTAGS: + {/* Local Block */ + register int arg_count = 0; + register int status; + + if(ct->ctx.iarg1_flag == YES) arg_count = 1; + if(ct->ctx.iarg2_flag == YES) arg_count = 2; + + status = cmd_tags( + uct, + arg_count, + ct->ctx.iarg1, + ct->ctx.iarg2, + ct->ctx.carg + ); + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = status; + return(SUCCESS); + }/* End IF */ + + return(status); + + }/* End Local Block */ + +/* + * Here to execute the FD command which searches for a string and deletes + * it. It is like FS but saves you from typing the second escape. + */ + case EXEC_C_FDCOMMAND: + {/* Local Block */ + register int j; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + j = last_search_pos2 - last_search_pos1; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + curbuf->dot = last_search_pos1; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to implement the FK command. The FK command remembers the current + * position in the buffer and searches for the specified string. It then + * deletes the characters from the old current position up to but not + * including the search string. + */ + case EXEC_C_FKCOMMAND: + {/* Local Block */ + register int j; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + if(remembered_dot <= last_search_pos1){ + last_search_pos2 = last_search_pos1; + last_search_pos1 = remembered_dot; + }/* End IF */ + + else { + last_search_pos1 = last_search_pos2; + last_search_pos2 = remembered_dot; + curbuf->dot = last_search_pos1; + }/* End IF */ + + j = last_search_pos2 - last_search_pos1; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + curbuf->dot = last_search_pos1; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ +/* + * This state is used by the FK command to remember the current dot + * position before it is changed by the search command. + */ + case EXEC_C_REMEMBER_DOT: + remembered_dot = curbuf->dot; + return(SUCCESS); +/* + * Here to implement the N search which searches across edit buffers for + * the given string. + */ + case EXEC_C_NSEARCH: + {/* Local Block */ + int count; + struct buff_header *original_buffer; + int original_dot; + int saved_dot,saved_top,saved_bottom; + int first_buffer; + int last_buffer = 0; + char back_in_original_buffer; +/* + * We set the search string, just like all the other search commands + */ + if(set_search_string_with_undo(ct->ctx.carg,uct) == FAIL){ + return(FAIL); + }/* End IF */ +/* + * Remember the buffer that we started in, and be ready to change back to + * it if we undo. + */ + original_buffer = curbuf; + original_dot = curbuf->dot; + + count = 1; + if(ct->ctx.iarg1_flag) count = ct->ctx.iarg1; + +/* + * If he says 1,5n<string>$ then we search for the string in buffers 1 + * through 5. We check that both buffers exist before we begin the search. + */ + if(ct->ctx.iarg2_flag){ + count = 1; + first_buffer = ct->ctx.iarg1; + last_buffer = ct->ctx.iarg2; + + if(first_buffer != curbuf->buffer_number){ + if(buff_openbuffer(NULL,last_buffer,0) != SUCCESS){ + return(FAIL); + }/* End IF */ + if(buff_openbuffer(NULL,first_buffer,0) != SUCCESS){ + return(FAIL); + }/* End IF */ + curbuf->dot = 0; + }/* End IF */ + }/* End IF */ + +/* + * In the starting buffer, we want to search from 'dot' to the end. Then + * we would search all the other buffers, and then the original one again, + * but from the top to 'dot'. The saved_dot stuff is necessary because + * the cmd_search routine has the side effect of changing 'dot' in the + * buffer being searched. However, we only want to affect 'dot' in the + * final buffer that the final occurance of the search string is found in. + */ + saved_dot = curbuf->dot; + saved_top = curbuf->dot; + saved_bottom = curbuf->zee; + back_in_original_buffer = 0; + + while(count > 0){ + + if(cmd_search(saved_top,saved_bottom,&search_string) != FAIL){ + saved_top = curbuf->dot; + count -= 1; + continue; + }/* End IF */ +/* + * Here if the search failed. We want to switch over to the next buffer and + * try again, until we exhaust all the buffers. If we reach the final buffer + * without finding the string, we return an error to the user, leaving him + * back in his original buffer. + */ + if(ct->ctx.iarg2_flag && curbuf->buffer_number == last_buffer){ + if(buff_openbuffer(NULL,original_buffer->buffer_number,0) != SUCCESS){ + error_message("Boom! lost original buffer"); + return(FAIL); + }/* End IF */ + back_in_original_buffer = 1; + }/* End IF */ + + if(back_in_original_buffer){ + curbuf->dot = saved_dot; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + sprintf(tmp_message, + "?Cannot find '%.40s'",search_string.input); + if(!ct->ctx.inest && !intr_flag && + search_string.error_message_given == NO){ + error_message(tmp_message); + }/* End IF */ + return(SUCCESS); + }/* End IF */ +/* + * We havn't tried all the buffers yet, switch to the next one. + */ + curbuf->dot = saved_dot; + + while(1){ + curbuf = curbuf->next_header; + if(curbuf == NULL) curbuf = buffer_headers; + if(curbuf->buffer_number == -1) break; + if(curbuf->buffer_number > 0) break; + if(ct->ctx.iarg2_flag) continue; + if(curbuf == original_buffer) break; + }/* End While */ + + saved_dot = curbuf->dot; + saved_top = 0; + saved_bottom = curbuf->zee; + + if(ct->ctx.iarg2_flag == NO){ + if(curbuf->buffer_number == original_buffer->buffer_number){ + back_in_original_buffer = 1; + saved_bottom = original_dot; + }/* End IF */ + }/* End IF */ + + }/* End While */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = original_buffer->buffer_number; + + buff_switch(curbuf,1); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = saved_dot; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to begin an iteration < ... > + */ + case EXEC_C_ITERATION_BEGIN: + {/* Local Block */ + register struct cmd_token *oct; + if(ct->ctx.iarg1_flag == YES){ + if(ct->ctx.iarg1 <= 0){ + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode != TOK_C_ITERATION_END) continue; + if(oct->ctx.inest != (ct->ctx.inest-1)) continue; + jump_to_token = oct; + return(SUCCESS); + }/* End While */ + ct->ctx.go_flag = NO; + return(SUCCESS); + }/* End IF */ + ct->ctx.iarg1 -= 1; + }/* End IF */ + return(SUCCESS); + }/* End Local Block */ +/* + * Here to provide the execution code for the end of an iteration. + * This must search backwards in the parse tokens to find the begining + * of the iteration, and set up to jump to that token + */ + case EXEC_C_ITERATION_END: + {/* Local Block */ + register struct cmd_token *oct; +/* + * The parser left us a pointer to the begining of the iteration so we can + * find it without having to search. + */ + oct = ct->ctx.caller_token; + + if(oct->ctx.iarg1_flag == YES){ + if(oct->ctx.iarg1 <= 0){ + return(SUCCESS); + }/* End IF */ + }/* End IF */ +/* + * The following line grabs the old iteration value so that when the + * begining of iteration code grabs the "previous" iarg1, this will + * be it. Think of it this way: the code has to do something different + * the first time into the iteration (it has to load the results of the + * STORE1, then the subsequent times it has to decrement what's already + * there. That's why this seems a little kludgy. + */ + ct->ctx.iarg1 = oct->ctx.iarg1; + jump_to_token = oct; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to implement the semicolon command which is used to jump out + * of an iteration if the argument is >= 0. + */ + case EXEC_C_SEMICOLON: + {/* Local Block */ + register struct cmd_token *oct; + int value; + + value = last_search_status; + if(ct->ctx.iarg1_flag == YES) value = ct->ctx.iarg1; + + if(value < 0) return(SUCCESS); + + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode != TOK_C_ITERATION_END) continue; + if(oct->ctx.inest != (ct->ctx.inest-1)) continue; + if(oct->next_token){ + jump_to_token = oct->next_token; + return(SUCCESS); + }/* End IF */ + }/* End While */ + + ct->ctx.go_flag = NO; + return(BLOCKED); + + }/* End Local Block */ +/* + * Here to implement the various conditional operators + */ + case EXEC_C_COND_GT: + if(ct->ctx.iarg1 <= 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_LT: + if(ct->ctx.iarg1 >= 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_EQ: + if(ct->ctx.iarg1 != 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_NE: + if(ct->ctx.iarg1 == 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_DIGIT: + if(!isdigit(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_ALPHA: + if(!isalpha(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_LOWER: + if(!islower(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_UPPER: + if(!isupper(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_SYMBOL: + if(!isalnum(ct->ctx.iarg1) && ct->ctx.iarg1 != '_'){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_SKIP_ELSE: + return(find_conditional_end(ct)); +/* + * Here to implement the GOTO command OLABEL$ + */ + case EXEC_C_GOTO: + {/* Local Block */ + struct cmd_token *goto_ptr; + struct cmd_token *test_ptr; +/* + * First step is to find the begining of this goto string + */ + goto_ptr = ct; + while((goto_ptr = goto_ptr->prev_token)){ + if(goto_ptr->opcode != TOK_C_GOTO_BEGIN) continue; + break; + }/* End While */ +/* + * Now try to find the label. First search backwards for it + */ + test_ptr = goto_ptr; + while((test_ptr = test_ptr->prev_token)){ + if(test_ptr->opcode != TOK_C_LABEL_BEGIN) continue; + if(compare_label(goto_ptr,test_ptr) == YES){ + ct->ctx.go_flag = NO; + jump_to_token = test_ptr; + return(SUCCESS); + }/* End IF */ + }/* End While */ +/* + * The label is not in front of us, look for it afterwards + */ + test_ptr = goto_ptr; + while((test_ptr = test_ptr->next_token)){ + if(test_ptr->opcode == TOK_C_FINALTOKEN){ + char string1[PARSER_STRING_MAX]; + + extract_label(goto_ptr,string1); + (void) sprintf(tmp_message,"?Can't find label <%s>", + string1); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + if(test_ptr->opcode != TOK_C_LABEL_BEGIN) continue; + + if(compare_label(goto_ptr,test_ptr) == YES){ + jump_to_token = test_ptr; + return(SUCCESS); + }/* End IF */ + }/* End While */ + + ct->ctx.go_flag = NO; + return(BLOCKED); + + }/* End Local Block */ +/* + * Here to write out the buffer. If the filename is specified, write it + * to that name, otherwise write it to the default name of the buffer. + */ + case EXEC_C_WRITEFILE: + {/* Local Block */ + int status; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + char *filename; + + if(curbuf->isreadonly){ + error_message("?File is Readonly"); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + if(ct->ctx.carg) filename = ct->ctx.carg; + else filename = curbuf->name; + + np = expand_filename(filename); + if(np == NULL){ + sprintf(tmp_message,"EW No such file <%s>",filename); + error_message(tmp_message); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + if(np->we_next){ + sprintf(tmp_message,"EW: Non-unique filename <%s>",filename); + error_message(tmp_message); + while(np){ + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + status = cmd_write(curbuf,np->we_name); + tec_release(TYPE_C_WILD,(char *)np); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = status; + return(SUCCESS); + }/* End IF */ + return(status); + + }/* End Local Block */ +/* + * Read in the named file to the current position in the edit buffer + */ + case EXEC_C_READFILE: + {/* Local Block */ + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + error_message("?ER Requires a filename"); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)curbuf; + ut->iarg1 = curbuf->dot; + ut->iarg2 = curbuf->zee; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + sprintf(tmp_message,"ER No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + while(np){ + buff_read(curbuf,np->we_name); + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + + ut->iarg2 = curbuf->zee - ut->iarg2; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + return(SUCCESS); + }/* End IF */ + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to change the current edit buffer. If a numeric argument is + * supplied, change to that buffer, otherwise a string should have been + * specified, and this is the name of the file/buffer to be loaded. + */ + case EXEC_C_EDITBUF: + {/* Local Block */ + struct buff_header *hbp,*old_buffer,*first_buffer; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + +/* + * Here if there is no string argument. It better be a numeric buffer + * number argument. If so, switch to that buffer, and set the undo to + * switch back to the old one if this gets undone. + */ + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + if(ct->ctx.iarg1_flag == NO){ + error_message("?EB Requires a filename or argument"); + return(FAIL); + }/* End IF */ + + old_buffer = curbuf; + if(buff_openbuffer(0,ct->ctx.iarg1,0) == FAIL){ + return(FAIL); + }/* End IF */ + + if(curbuf->buffer_number != ct->ctx.iarg1){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + } + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + } + return(SUCCESS); + + }/* End IF */ + + old_buffer = curbuf; + first_buffer = NULL; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + } + sprintf(tmp_message,"EB No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + while(np){ +/* + * The following hack detects whether a new buffer was created so that + * we know whether or not to set up an undo to un-create it. + */ + hbp = buff_find(np->we_name); + if(buff_openbuffer(np->we_name,0,0) == SUCCESS){ + if(hbp == NULL){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CLOSEBUFF; + ut->carg1 = (char *)curbuf; + }/* End IF */ + if(first_buffer == NULL) first_buffer = curbuf; + }/* End IF */ + + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + buff_openbuffer(0,first_buffer->buffer_number,0); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + return(SUCCESS); + } + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to implement the EF command which removes an edit buffer. + */ + case EXEC_C_CLOSEBUF: + {/* Local Block */ + register struct buff_header *hbp,*bp; + register int i; + + hbp = curbuf; + if(curbuf->buffer_number == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + error_message("?EF Not allowed on special buffers"); + return(FAIL); + }/* End IF */ + + if(hbp->ismodified && !hbp->isreadonly){ + if(ct->ctx.iarg1_flag == NO || + ct->ctx.iarg1 != -1){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + sprintf(tmp_message,"?EF Buffer %s is modified", + curbuf->name); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = hbp->buffer_number; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_REOPENBUFF; + ut->iarg1 = hbp->buffer_number; + ut->carg1 = (char *)hbp; + +/* + * Unlink this buffer header from the header list. Check to see if it is + * at the head of the list. If not at the head of the list, we have to + * search the list till we find it's father. Then we make it's father's + * child be it's child. + */ + i = 0; + bp = buffer_headers; + if(bp == hbp) buffer_headers = bp->next_header; + else { + while(bp){ + if(bp->next_header == hbp) break; + bp = bp->next_header; + }/* End While */ + bp->next_header = hbp->next_header; + curbuf = NULL; + if(bp->buffer_number > 0 && hbp->buffer_number > 0){ + i = bp->buffer_number; + }/* End IF */ + else if(bp->buffer_number < 0 && hbp->buffer_number < 0){ + i = bp->buffer_number; + }/* End Else */ + else if(hbp->buffer_number > 0 && hbp->next_header){ + i = hbp->next_header->buffer_number; + }/* End Else */ + else i = -1; + }/* End Else */ +/* + * Pick a different buffer to be the current buffer + */ + if(i){ + if(buff_openbuffer(NULL,i,0) == FAIL) i = 0; + }/* End IF */ + if(!i){ + if(buff_openbuffer(NULL,-1,0) == FAIL){ + buff_openbuffer(NULL,0,0); + }/* End IF */ + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to implement the EV command which is identical to the EB command + * except that if the buffer does not exist, when it is created it is created + * as a read only buffer. + */ + case EXEC_C_VIEWBUF: + {/* Local Block */ + struct buff_header *hbp,*old_buffer,*first_buffer; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + +/* + * Here if there is no string argument. It better be a numeric buffer + * number argument. If so, switch to that buffer, and set the undo to + * switch back to the old one if this gets undone. + */ + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + if(ct->ctx.iarg1_flag == NO){ + error_message("?EV Requires a filename or argument"); + return(FAIL); + }/* End IF */ + + old_buffer = curbuf; + buff_openbuffer(0,ct->ctx.iarg1,1); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + } + return(SUCCESS); + + }/* End IF */ + + old_buffer = curbuf; + first_buffer = NULL; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + } + sprintf(tmp_message,"EV No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + while(np){ +/* + * The following hack detects whether a new buffer was created so that + * we know whether or not to set up an undo to un-create it. + */ + hbp = buff_find(np->we_name); + if(buff_openbuffer(np->we_name,0,1) == SUCCESS){ + if(hbp == NULL){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CLOSEBUFF; + ut->carg1 = (char *)curbuf; + }/* End IF */ + if(first_buffer == NULL) first_buffer = curbuf; + }/* End IF */ + + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + buff_openbuffer(0,first_buffer->buffer_number,1); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + return(SUCCESS); + } + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to scroll the screen the specified number of lines + */ + case EXEC_C_SCROLL: + if(ct->ctx.iarg1_flag == NO) ct->ctx.iarg1 = 1; + screen_scroll(ct->ctx.iarg1); + return(SUCCESS); +/* + * Here to cause the screen to update + */ + case EXEC_C_UPDATE_SCREEN: + screen_format_windows(); + screen_refresh(); + return(SUCCESS); +/* + * Here to either split or combine windows + */ + case EXEC_C_WINDOW_CONTROL: + {/* Local Block */ + struct window *old_wptr; + struct window *new_wptr; + + if(ct->ctx.iarg1_flag == NO) screen_delete_window(curwin); + + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg2_flag == YES){ + old_wptr = curwin; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + + new_wptr = + screen_split_window( + curwin, + ct->ctx.iarg1, + ct->ctx.iarg2 + ); + + if(new_wptr == NULL) return(FAIL); + + ut->opcode = UNDO_C_WINDOW_SPLIT; + ut->iarg1 = old_wptr->win_window_number; + ut->carg1 = (char *)new_wptr; + }/* End Else */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to select the next window as active + */ + case EXEC_C_NEXT_WINDOW: + {/* Local Block */ + int status; + int new_window; + + new_window = ct->ctx.iarg1_flag == YES ? ct->ctx.iarg1 : 0; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_WINDOW_SWITCH; + ut->iarg1 = curwin->win_window_number; + + status = window_switch(new_window); + + return(status); + + }/* End Local Block */ +/* + * Here to reset a ^A message to NULL + */ + case EXEC_C_RESET_MESSAGE: + user_message[0] = '\0'; + return(SUCCESS); +/* + * Here to add a byte to the length of the current user message string + */ + case EXEC_C_MESSAGE: + {/* Local Block */ + register int i; + + if(strlen(user_message) > (sizeof(user_message) - 1)){ + error_message("?^A message length exceeded"); + return(FAIL); + }/* End IF */ + + i = strlen(user_message); + user_message[i] = ct->input_byte; + user_message[i+1] = '\0'; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SHORTEN_MESSAGE; + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to print the current user message to the status line of the screen + */ + case EXEC_C_OUTPUT_MESSAGE: + screen_message(user_message); + screen_refresh(); + return(SUCCESS); +/* + * Here to execute the specified operating system command, and insert the + * generated output into the edit buffer. + */ + case EXEC_C_ECCOMMAND: + {/* Local Block */ + int status; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)curbuf; + ut->iarg1 = curbuf->dot; + ut->iarg2 = 0; + + status = cmd_oscmd(ct); + ut->iarg2 = curbuf->dot - ut->iarg1; + return(status); + + }/* End Local Block */ + + case EXEC_C_SET_IMMEDIATE_MODE: + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_IMMEDIATE_MODE; + ut->iarg1 = immediate_execute_flag; + + immediate_execute_flag = YES; + if(ct->ctx.iarg1 == 0){ + ct->ctx.go_flag = NO; + immediate_execute_flag = NO; + }/* End IF */ + return(SUCCESS); + + case EXEC_C_OPENBRACE: + {/* Local Block */ + register struct buff_header *qbp; + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind('$',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Set up the undo token to remember the current edit buffer, and then switch + * to the Q-register. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = curbuf->buffer_number; + + buff_switch(qbp,1); +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. + */ + if(qbp->zee) buff_delete_with_undo(uct,qbp,0,qbp->zee); +/* + * Arrange for it to be deleted if this gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); +/* + * Copy the parser command line into the Q-register + */ + parser_dump_command_line(qbp); + + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = 0; + ut->iarg2 = qbp->zee; + + return(SUCCESS); + }/* End Local Block */ + + case EXEC_C_CLOSEBRACE: + {/* Local Block */ + register struct buff_header *qbp; + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, that is an error + */ + qbp = buff_qfind('$',0); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Replace the command line with the contents of the Q-register + */ + parser_replace_command_line(qbp); + + return(INVALIDATE); + + }/* End Local Block */ + +/* + * Here to skip a label (this occurs when we run into a label) + */ + case EXEC_C_SKIPLABEL: + while(ct){ + if(ct->opcode == TOK_C_LABEL_END){ + jump_to_token = ct; + return(SUCCESS); + }/* End IF */ + ct = ct->next_token; + }/* End While */ + return(SUCCESS); + +/* + * Here if he wants to set some options up + */ + case EXEC_C_SETOPTIONS: + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SETOPTIONS; + + return(cmd_setoptions(ct->ctx.iarg1,ct->ctx.iarg2,ut)); + + default: + sprintf(tmp_message,"?TECEXEC: unknown state %d dispatched", + ct->execute_state); + error_message(tmp_message); + return(FAIL); + + }/* End Switch */ + +}/* End Routine */ + + + +/* PUSH_QREGISTER - Push a Q register onto the stack + * + * Function: + * + * This routine is called to make a copy of a Q register and place it + * on the Q register stack. + */ +int +push_qregister(letter) +char letter; +{ +register struct buff_header *hbp; +register struct buff_header *qbp; + + PREAMBLE(); + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(letter,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + + hbp = buff_duplicate(qbp); + + hbp->next_header = qregister_push_down_list; + qregister_push_down_list = hbp; + + return(SUCCESS); + +}/* End Routine */ + + +/* EXTRACT_LABEL - Build a string with the contents of the label in it + * + * Function: + * + * This routine is called to build a string with the label name + * in it. The current use is for error messages. + */ +void +extract_label(label_ptr,string1) +register struct cmd_token *label_ptr; +char *string1; +{ +register char *cp1; + + cp1 = string1; + *cp1 = '\0'; + while(label_ptr){ + if(cp1 >= &string1[PARSER_STRING_MAX-1]) break; + if(label_ptr->opcode == TOK_C_INPUTCHAR){ + if(label_ptr->input_byte == '!') break; + *cp1++ = label_ptr->input_byte; + *cp1 = '\0'; + }/* End IF */ + if(label_ptr->opcode == TOK_C_LABEL_END) break; + label_ptr = label_ptr->next_token; + }/* End While */ + +}/* End Routine */ + +/* COMPARE_LABEL - Test if this is our label + * + * Function: + * + * This routine is called by the GOTO function to compare labels + * to see if they match. + */ +int +compare_label(goto_ptr,label_ptr) +register struct cmd_token *goto_ptr; +register struct cmd_token *label_ptr; +{ +char string1[PARSER_STRING_MAX],string2[PARSER_STRING_MAX]; +char *cp1; + + PREAMBLE(); + + cp1 = string1; + + *cp1 = '\0'; + while(goto_ptr){ + if(cp1 >= &string1[PARSER_STRING_MAX-1]) break; + if(goto_ptr->opcode == TOK_C_INPUTCHAR){ + if(goto_ptr->input_byte == ESCAPE) break; + *cp1++ = goto_ptr->input_byte; + *cp1 = '\0'; + }/* End IF */ + if(goto_ptr->opcode == TOK_C_GOTO_END) break; + goto_ptr = goto_ptr->next_token; + }/* End While */ + + cp1 = string2; + + *cp1 = '\0'; + while(label_ptr){ + if(cp1 >= &string2[PARSER_STRING_MAX-1]) break; + if(label_ptr->opcode == TOK_C_INPUTCHAR){ + if(label_ptr->input_byte == '!') break; + *cp1++ = label_ptr->input_byte; + *cp1 = '\0'; + }/* End IF */ + if(label_ptr->opcode == TOK_C_LABEL_END) break; + label_ptr = label_ptr->next_token; + }/* End While */ + + if(strcmp(string1,string2) == 0) return(YES); + + return(NO); + +}/* End Routine */ + + + +/* FIND_CONDITIONAL_ELSE - Routine to find the else of a conditional + * + * Function: + * + * This routine is called by the conditional expressions when the + * specified condition has not been met and we want to execute the + * else clause of the conditional. The routine searches until it + * finds either the else, or the end of the conditional. + */ +int +find_conditional_else(ct) +register struct cmd_token *ct; +{ + register struct cmd_token *oct; + + PREAMBLE(); + + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode == TOK_C_CONDITIONAL_ELSE){ + if(oct->ctx.cnest == ct->ctx.cnest){ + jump_to_token = oct; + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + if(oct->opcode == TOK_C_CONDITIONAL_END){ + if(oct->ctx.cnest == (ct->ctx.cnest-1)){ + jump_to_token = oct; + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + }/* End While */ + ct->ctx.go_flag = NO; + return(BLOCKED); + +}/* End Routine */ + +/* FIND_CONDITIONAL_END - Routine to find the close of a conditional + * + * Function: + * + * This routine is called to skip over the else clause and find the + * end of the conditional. + */ +int +find_conditional_end(ct) +register struct cmd_token *ct; +{ + register struct cmd_token *oct; + + PREAMBLE(); + + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode != TOK_C_CONDITIONAL_END) continue; + if(oct->ctx.cnest != (ct->ctx.cnest-1)) continue; + jump_to_token = oct; + return(SUCCESS); + }/* End While */ + ct->ctx.go_flag = NO; + return(BLOCKED); + +}/* End Routine */ + + + +/* EXEC_DOQ0 - Execute Q register zero as a macro + * + * Function: + * + * This routine is called to execute Q register zero as a macro on + * startup. This allows the user to initialize things from his teco.ini + * before teco reaches command level. + */ +int +exec_doq0() +{ + register struct buff_header *qbp; + register struct undo_token *ut; + struct cmd_token *ct; + + PREAMBLE(); + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, declare an error. + */ + qbp = buff_qfind('0',0); + if(qbp == NULL){ + error_message("?Internal Error! Q register 0 disappeared during init"); + return(FAIL); + }/* End IF */ +/* + * If the Q register is empty, just return with no error. + */ + if(qbp->zee == 0){ + return(SUCCESS); + }/* End IF */ +/* + * Set up a fake command token and undo token structure for the parser to + * chain undo blocks off of. We don't ever undo this stuff, but the routine + * expects it to be there. + */ + ct = allocate_cmd_token((struct cmd_token *)NULL); + if(ct == NULL) return(FAIL); + + ut = allocate_undo_token(ct); + if(ut == NULL){ + free_cmd_token(ct); + return(FAIL); + }/* End IF */ + + ut->opcode = UNDO_C_MACRO; + ut->carg1 = NULL; + + tecmacro(qbp,ct,(struct cmd_token **)&ut->carg1); + parser_cleanup_ctlist(ct); + return(SUCCESS); + +}/* End Routine */ diff --git a/tecmem.c b/tecmem.c new file mode 100644 index 0000000..739265b --- /dev/null +++ b/tecmem.c @@ -0,0 +1,456 @@ +char *tecmem_c_version = "tecmem.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecmem.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecmem.c + * Subroutines to manage memory allocation and deallocation + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" + +#if HAVE_SBRK +#ifndef HAVE_UNISTD_H + char *sbrk(); +#endif +#endif + + char *starting_break; + +#ifndef HAVE_UNISTD_H + char *malloc(); + void free(); + void exit(); +#endif + + extern struct buff_header *curbuf; + extern FILE *teco_debug_log; + +struct memblock { + unsigned char type; + int size; +}; + +struct memlist { + unsigned char type; + struct memlist *next_block; +}; + +char *malloc_leftover; +int malloc_leftover_size; +struct memlist *lookaside_lists[LOOKASIDE_COUNT]; +int lookaside_stat_allocated[LOOKASIDE_COUNT]; +int lookaside_stat_available[LOOKASIDE_COUNT]; +int malloc_stat_call_count,malloc_stat_inuse; + +int memstat_by_type[TYPE_C_MAXTYPE-TYPE_C_MINTYPE+1]; + +void tecmem_verify(unsigned char,char *,char *); + +/* TEC_ALLOC - Routine to allocate some memory + * + * Function: + * + * This routine is called to request memory + */ +char * +tec_alloc(type,size) +int type; +int size; +{ +int actual_size; +register int i,j; +register char *cp; +register struct memblock *mp; +register struct memlist *lp; +extern char outof_memory; + + PREAMBLE(); + +/* + * First we check that the caller is requesting a known memory block + * type. This is just a consitency check. + */ + if(type < TYPE_C_MINTYPE || type > TYPE_C_MAXTYPE){ + printf("\nTECO: UNKNOWN MEMORY BLOCK TYPE %d in TEC_ALLOC\n",type); +#ifdef UNIX + kill(getpid(),SIGQUIT); +#endif + exit(ENOMEM); + }/* End IF */ + + +#ifdef INSERT_RANDOM_MALLOC_ERRORS + { + int chance; + chance = rand() % 100; + if(chance == 0){ + return(NULL); + }/* End IF */ + } +#endif /* INSERT_RANDOM_MALLOC_ERRORS */ + +/* + * If the size required is larger than the largest lookaside block we support, + * we just call malloc with it. + */ + actual_size = size + sizeof(struct memblock); + if(actual_size > LARGEST_LOOKASIDE_BLOCK){ + mp = (struct memblock *)malloc((unsigned)actual_size); + + if(mp == NULL){ + outof_memory = YES; + return(NULL); + }/* End IF */ + + malloc_stat_call_count += 1; + malloc_stat_inuse += actual_size; + mp->type = type; + mp->size = actual_size; + cp = (char *)mp + sizeof(struct memblock); + return(cp); + }/* End IF */ + +/* + * Round up to the next larger lookaside block size + */ + if(actual_size & (MINIMUM_ALLOCATION_BLOCK - 1)){ + actual_size += MINIMUM_ALLOCATION_BLOCK; + actual_size &= ~(MINIMUM_ALLOCATION_BLOCK - 1); + }/* End IF */ + +/* + * Check whether we have a lookaside entry of that size. If not, we allocate + * some number of entries of that size from our large memory hunk. + */ + i = (actual_size / MINIMUM_ALLOCATION_BLOCK) - 1; + + if(lookaside_lists[i] == NULL){ + tec_gc_lists(); + }/* End IF */ + + if(lookaside_lists[i] == NULL){ + if(malloc_leftover != NULL){ + if(malloc_leftover_size < actual_size){ + i = (malloc_leftover_size / MINIMUM_ALLOCATION_BLOCK) - 1; + lp = (struct memlist *)malloc_leftover; + lp->next_block = lookaside_lists[i]; + lookaside_lists[i] = lp; + lookaside_stat_available[i] += 1; + i = (actual_size / MINIMUM_ALLOCATION_BLOCK) - 1; + malloc_leftover = NULL; + malloc_leftover_size= 0; + }/* End IF */ + else { + lp = (struct memlist *)malloc_leftover; + malloc_leftover += actual_size; + malloc_leftover_size -= actual_size; + if(malloc_leftover_size == 0) malloc_leftover = NULL; + lp->next_block = NULL; + lookaside_lists[i] = lp; + lookaside_stat_available[i] += 1; + }/* End Else */ + }/* End IF */ + }/* End IF */ + + if(lookaside_lists[i] == NULL){ + j = BIG_MALLOC_HUNK_SIZE / actual_size; + cp = malloc((unsigned)(BIG_MALLOC_HUNK_SIZE)); + + if(cp == NULL){ + printf("\nTECO: malloc failed!\n"); + outof_memory = YES; + return(NULL); + }/* End IF */ + +/* + * Place the one block on the lookaside list, and the rest as a malloc + * leftover. + */ + lp = (struct memlist *)cp; + lookaside_stat_available[i] += 1; + lp->next_block = NULL; + lookaside_lists[i] = lp; + malloc_leftover = cp + actual_size; + malloc_leftover_size = BIG_MALLOC_HUNK_SIZE - actual_size; + + }/* End IF */ + + lp = lookaside_lists[i]; + lookaside_lists[i] = lp->next_block; + lookaside_stat_allocated[i] += 1; + lookaside_stat_available[i] -= 1; + memstat_by_type[type-TYPE_C_MINTYPE] += 1; + + mp = (struct memblock *)lp; + mp->type = type; + mp->size = actual_size; + cp = (char *)mp + sizeof(struct memblock); + bzero(cp,size); + return(cp); + +}/* End Routine */ + +/* TEC_RELEASE - Envelope routine for free() + * + * Function: + * + * This routine is called to release memory which was previously allocated + * by calling the tec_alloc routine. + */ +void +tec_release(type,addr) +unsigned char type; +register char *addr; +{ +register struct memblock *mp; +register struct memlist *lp; +register int i; + + PREAMBLE(); + + mp = (struct memblock *)( addr - sizeof(struct memblock) ); + if(mp->type != type){ + printf("\nTEC_RELEASE: TYPE Mismatch: Supplied %d Stored %d addr 0x%x\n", + type,mp->type,(unsigned int)addr); +#ifdef UNIX + kill(getpid(),SIGQUIT); +#endif + exit(ENOMEM); + }/* End IF */ + + if(mp->size > LARGEST_LOOKASIDE_BLOCK){ + malloc_stat_inuse -= mp->size; + free(mp); + return; + }/* End IF */ + + mp->type += 1000; + i = mp->size / MINIMUM_ALLOCATION_BLOCK - 1; + lp = (struct memlist *)mp; + lp->next_block = lookaside_lists[i]; + lookaside_lists[i] = lp; + + lookaside_stat_available[i] += 1; + lookaside_stat_allocated[i] -= 1; + memstat_by_type[type-TYPE_C_MINTYPE] -= 1; + + return; + +}/* End Routine */ + + + +/* TECMEM_VERIFY - Verify that structure type hasn't been corrupted + * + * Function: + * + * This debug routine checks to see whether the memory block has been + * overwritten by checking the type code. + */ +void +tecmem_verify(type,addr,message) +unsigned char type; +register char *addr; +char *message; +{ +register struct memblock *mp; +#if 0 +register struct memlist *lp; +#endif +#if 0 +register int i; +#endif + + PREAMBLE(); + + mp = (struct memblock *)( addr - sizeof(struct memblock) ); + if(mp->type != type){ + printf( + "\nTYPE Mismatch: Supplied %d Stored %d addr %x '%s'\n", + type,mp->type,(unsigned int)addr,message); + exit(1); + }/* End IF */ + + return; + +}/* End Routine */ + + + +/* TEC_GC_LISTS - Garbage collect any local lookaside lists + * + * Function: + * + * This routine is called before we call malloc for more memory in + * an attempt to find the memory on some local lookaside lists. + */ +void +tec_gc_lists() +{ + + PREAMBLE(); + + screen_deallocate_format_lookaside_list(); + buff_deallocate_line_buffer_lookaside_list(); + +}/* End Routine */ + + +void +initialize_memory_stats() +{ + starting_break = 0; + +#if HAVE_SBRK + if(starting_break == NULL){ + starting_break = sbrk(0); + }/* End IF */ +#endif + +} + + +/* TECMEM_STATS - Insert memory statistics into the current buffer + * + * Function: + * + * This routine is called by the buffer map routine to allow us to + * insert some memory statistics into the map. + */ +void +tecmem_stats() +{ +char tmp_buffer[LINE_BUFFER_SIZE]; +register int i; +register int size; +int total_memory_in_use; +char *current_break; +int bss_in_use; + + PREAMBLE(); + + total_memory_in_use = malloc_stat_inuse; + + sprintf( + tmp_buffer, + "\n\n%d non-lookaside allocations, %d bytes outstanding\n\n", + malloc_stat_call_count, + malloc_stat_inuse + ); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + for(i = 0; i < LOOKASIDE_COUNT; i++){ + if(lookaside_stat_allocated[i] == 0 && + lookaside_stat_available[i] == 0) continue; + size = ( i + 1 ) * MINIMUM_ALLOCATION_BLOCK; + total_memory_in_use += lookaside_stat_allocated[i] * size; + + sprintf( + tmp_buffer, + "LA%2d, size %4d, bytes in use %6d, bytes available %6d\n", + i, + size, + lookaside_stat_allocated[i] * size, + lookaside_stat_available[i] * size + ); + + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + }/* End FOR */ + + sprintf( + tmp_buffer, + "Malloc Leftover %d bytes\n", + malloc_leftover_size + ); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + buff_insert(curbuf,curbuf->dot,"\n",1); + + for(i = 0; i < TYPE_C_MAXTYPE-TYPE_C_MINTYPE; i++){ + static char *type_name_list[] = { + "CBUFF", + "CPERM", + "CMD", + "UNDO", + "SCR", + "SCRBUF", + "SCREEN", + "SCREENBUF", + "LINE", + "LINEBUF", + "BHDR", + "WINDOW", + "LABELFIELD", + "WILD", + "TAGS", + "TAGENT", + "TAGSTR", + "MAXTYPE" + }; + + if(i >= ELEMENTS(type_name_list)){ + sprintf( + tmp_buffer, + "Unknown memory type index %d(%d)\n", + i, + i+TYPE_C_MINTYPE + ); + }/* End IF */ + + else { + sprintf( + tmp_buffer, + "Type %10s, blocks in use %d\n", + type_name_list[i], + memstat_by_type[i] + ); + }/* End Else */ + + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + }/* End FOR */ + +#if HAVE_SBRK + current_break = sbrk(0); + if(current_break != sbrk(0)){ + tec_panic("sbrk(0) seems to be allocating space!\n"); + }/* End IF */ + bss_in_use = current_break - starting_break; +#else + bss_in_use = 0; +#endif /* UNIX */ + + if( bss_in_use ){ + sprintf( + tmp_buffer, + "\nTotal memory in use %d, Total allocated bss %d\n", + total_memory_in_use, + bss_in_use + ); + } else { + sprintf(tmp_buffer,"\nTotal memory in use %d\n",total_memory_in_use); + } + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + +}/* End Routine */ @@ -0,0 +1,1888 @@ +char *teco_c_version = "teco.c: $Revision: 1.1 $"; +char *copyright = "Copyright (c) 1985-2003 Paul Cantrell & Joyce Nishinaga"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/teco.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* teco.c + * Main TECO entry point with most of the initialization code + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +/* + * Global Storage is defined here for lack of a better place. + * First off are all the flags. + */ + char readonly_flag = NO; + char piped_input_flag = NO; + char piped_output_flag = NO; + char tty_modes_saved = NO; + char exit_flag = NO; + char intr_flag = 0; + char susp_flag = 0; + char input_pending_flag = NO; + int tab_width = NORMAL_TAB_WIDTH; + char alternate_escape_character = ESCAPE; + char main_delete_character = RUBOUT; + char alternate_delete_character = RUBOUT; + char eight_bit_output_flag = NO; + char hide_cr_flag = NO; + char waiting_for_input_flag = NO; + char resize_flag = NO; + char teco_startup = YES; + char screen_startup = YES; + char checkpoint_flag = NO; + char checkpoint_enabled = YES; + char checkpoint_modified = NO; + char outof_memory = NO; + char suspend_is_okay_flag = YES; + +/* + * Global Variables + */ + int tty_input_chan; + int tty_output_chan; + int term_speed; + FILE *teco_debug_log; + + int term_columns; + int term_lines; + int forced_width,forced_height; + + int checkpoint_interval = DEFAULT_CHECKPOINT; + + char *output_tty_name; +/* + * Table of Bit Positions + */ + unsigned int IntBits[BITS_PER_INT]; + +/* + * Table of Spaces for tab expansion + */ + char tab_expand[MAX_TAB_WIDTH+1]; +/* + * Global structures + */ +#if HAVE_TERMIOS_H + static struct termios tty_input_modes; + static struct termios tty_output_modes; +#else +#ifdef ATT_UNIX + static struct termio tty_input_modes; + static struct termio tty_output_modes; +#endif +#ifdef BSD_UNIX + static struct sgttyb tty_input_modes; + static struct sgttyb tty_output_modes; +#endif +#endif + +/* + * External Variables + */ + +/* + * Prototypes + */ + void init_signals(void); + void teco_ini(void); + void process_directory(char *,char*,int); + int match_name(char *,char *); + int map_baud(int); + + + +/* TECO - Text Edit and COrrector + * + * Function: + * + * This is the entry point for TECO. It initializes the terminal, reads + * in the specified files, and generally gets things going. + */ +int +main(argc,argv) +int argc; +char **argv; +{ +register int i; + + initialize_memory_stats(); + + for(i = 0; i < BITS_PER_INT; i++){ + IntBits[i] = 1 << i; + }/* End FOR */ + for(i = 0; i < MAX_TAB_WIDTH; i++){ + tab_expand[i] = ' '; + }/* End FOR */ + tab_expand[MAX_TAB_WIDTH] = '\0'; + + if(buff_init() == FAIL){ + tec_error(ENOMEM,"Unable to initialize buffers"); + }/* End IF */ + + handle_command_line(0,argc,argv); + + init_signals(); + initialize_tty(); + + if(strcmp(argv[0],TECO_READONLY_NAME) == 0){ + readonly_flag = YES; + }/* End IF */ + + handle_command_line(1,argc,argv); + + teco_ini(); + + teco_startup = NO; + + while(exit_flag == NO){ + tecparse(); + intr_flag = 0; + }/* End While */ + + if(piped_output_flag){ + extern struct buff_header *curbuf; + buff_openbuffnum(-1,0); + buff_write(curbuf,fileno(stdout),0,curbuf->zee); + }/* End IF */ + + restore_tty(); +/* + * Output a final newline to get the terminal onto a fresh + * line. + */ + term_putc('\n'); + term_flush(); + +#ifdef CHECKPOINT + remove_checkpoint_file(); +#endif /* CHECKPOINT */ + +#ifdef UNIX + return(0); +#endif + +#ifdef VMS + return(STS$M_SUCCESS); +#endif + +}/* End Routine */ + + + +#ifdef UNIX + +/* INITIALIZE_TTY - Set the tty up the way we want it + * + * Function: + * + * This routine is called at startup time to set up the terminal the way + * that we require, i.e., it sets the tty modes. It also gives the screen + * package a chance to initialize itself. + */ +void +initialize_tty() +{ +#if HAVE_TERMIOS_H + struct termios tmp_tty_modes; +#else +#ifdef ATT_UNIX +#define GET_IOCTL TCGETA +#define SET_IOCTL TCSETA + struct termio tmp_tty_modes; +#endif +#ifdef BSD_UNIX +#define GET_IOCTL TIOCGETP +#define SET_IOCTL TIOCSETN +#ifdef INTEGRATED_SOLUTIONS + int mask; +#endif /* INTEGRATED_SOLUTIONS */ + + struct sgttyb tmp_tty_modes; +#endif /* BSD_UNIX */ +#endif /* HAVE_TERMIOS_H */ + +#ifdef SUN_STYLE_WINDOW_SIZING + struct winsize os_window; +#endif + +/* + * We start by assuming that standard input is a tty device. If it turns out + * that it is not, however, then we want to use /dev/tty as the keyboard. + */ + if(!isatty(tty_input_chan = fileno(stdin))){ + tty_input_chan = open("/dev/tty",O_RDONLY); + piped_input_flag = YES; + if(tty_input_chan < 0){ + perror("cannot open /dev/tty"); + punt(errno); + }/* End IF */ + }/* End IF */ +/* + * Standard output MUST by a tty at this time. + */ + if(output_tty_name){ + tty_output_chan = open(output_tty_name,O_RDWR); + }/* End IF */ + + else { + tty_output_chan = fileno(stdout); + }/* End Else */ + + if(!isatty(tty_output_chan)){ + tty_output_chan = open("/dev/tty",O_RDWR); + piped_output_flag = YES; + if(tty_output_chan < 0){ + perror("cannot open /dev/tty"); + punt(errno); + }/* End IF */ + }/* End IF */ +/* + * Get the current modes of the tty so it can be restored later. + */ +#if HAVE_TERMIOS_H + if(tcgetattr(tty_input_chan,&tty_input_modes) < 0){ + perror("could not get input tty modes"); + punt(errno); + }/* End IF */ + + if(tcgetattr(tty_output_chan,&tty_output_modes) < 0){ + perror("could not get output tty modes"); + punt(errno); + }/* End IF */ + +#else + if(ioctl(tty_input_chan,GET_IOCTL,&tty_input_modes) < 0){ + perror("could not get input tty modes"); + punt(errno); + }/* End IF */ + + if(ioctl(tty_output_chan,GET_IOCTL,&tty_output_modes) < 0){ + perror("could not get output tty modes"); + punt(errno); + }/* End IF */ + +#endif + +#if 0 /* Turned off because xterm is getting clobbered by this */ +#if defined(ATT_UNIX) || defined(_POSIX_SOURCE) + if((tty_output_modes.c_cflag & CSIZE) == CS8){ + eight_bit_output_flag = YES; + }/* End IF */ +#endif +#endif + +#if defined(HAVE_DECL_CFGETOSPEED) && HAVE_DECL_CFGETOSPEED + term_speed = map_baud( cfgetospeed(&tty_output_modes) ); +#else +# if defined(HAVE_STRUCT_TERMIOS_C_CFLAG) && defined(CBAUD) + term_speed = map_baud(tty_output_modes.c_cflag & CBAUD); +# else +# if HAVE_STRUCT_TERMIOS_C_OSPEED + term_speed = map_baud(tty_output_modes.c_ospeed); +#else +# if HAVE_STRUCT_TERMIOS_SG_OSPEED + term_speed = map_baud(tty_output_modes.sg_ospeed); +# endif +# endif +# endif +#endif + +#if HAVE_TERMIOS_H + main_delete_character = tty_input_modes.c_cc[VERASE]; +#else +#if defined(BSD_UNIX) + main_delete_character = tty_input_modes.sg_erase; +#endif +#endif + + tty_modes_saved = YES; +/* + * Check out the termcap description for this tty now. It makes + * no sense to go changing the terminal modes all around until + * we decide whether we can run on this terminal or not. + */ + init_term_description(); +/* + * 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){ + + if((unsigned)os_window.ws_row >= SCREEN_MAX_LINES){ + fprintf(stderr,"os_window.ws_row %d os_window.ws_col %d\n", + (int)os_window.ws_row,(int)os_window.ws_col); + fprintf(stderr,"term_lines %d term_columns %d\n", + term_lines,term_columns); + fprintf(stderr,"forced_height %d forced_width %d\n", + forced_height,forced_width); + os_window.ws_row = term_lines; + os_window.ws_col = term_columns; + }/* End IF */ + + if(forced_height > 0) os_window.ws_row = forced_height; + if(forced_width > 0) os_window.ws_col = forced_width; + if(teco_startup == YES){ + term_lines = os_window.ws_row; + term_columns = os_window.ws_col; + }/* End IF */ + if(term_lines == 0) term_lines = 24; + if(term_columns == 0) term_columns = 80; + if(term_lines != os_window.ws_row || term_columns != os_window.ws_col){ + screen_resize(); + }/* End IF */ + }/* End IF */ +#else + 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; +#endif + + if(term_lines >= SCREEN_MAX_LINES){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "terminal line count of %d exceeds maximum of %d", + term_lines, + SCREEN_MAX_LINES + ); + tec_error(E2BIG,tmp_message); + }/* End IF */ + +/* + * Now set the modes that we want + */ + tmp_tty_modes = tty_input_modes; + +#if HAVE_TERMIOS_H + tmp_tty_modes.c_lflag &= ~(ICANON | ECHO); + tmp_tty_modes.c_cc[VMIN] = 1; + tmp_tty_modes.c_cc[VTIME] = 1; +#else +#ifdef SYS_III_UNIX + tmp_tty_modes.c_lflag &= ~(ICANON | ECHO | ISTATUS); + tmp_tty_modes.c_cc[VMIN] = 1; + tmp_tty_modes.c_cc[VTIME] = 1; +#endif + +#ifdef BSD_UNIX + tmp_tty_modes.sg_flags |= CBREAK; + tmp_tty_modes.sg_flags &= ~(ECHO); +#endif +#endif /* SYSV or POSIX */ + +#if HAVE_TERMIOS_H + if(tcsetattr(tty_input_chan,TCSANOW,&tmp_tty_modes) < 0){ + perror("could not set input tty modes"); + punt(errno); + }/* End IF */ +#else + if(ioctl(tty_input_chan,SET_IOCTL,&tmp_tty_modes) < 0){ + perror("could not set input tty modes"); + punt(errno); + }/* End IF */ +#endif + +#ifdef INTEGRATED_SOLUTIONS + mask = LLITOUT; + ioctl(tty_output_chan,TIOCLBIS,&mask); +#endif + + screen_init(); + +}/* End Routine */ + +#endif /* UNIX */ + +/* END OF UNIX CONDITIONAL CODE */ + + + +#if !HAVE_TCGETATTR + +/* TCGETATTR - Mimic the POSIX terminal get/set functions + * + * Function: + * + * For a system being compiled with POSIX headers, but which doesn't + * actually have all the POSIX functions in libc, we have to fake up + * this call. + */ +int +tcgetattr(fd,termios_p) +int fd; +struct termios *termios_p; +{ +struct termio tmp_modes; +register int i; +int status; + + bzero(&tmp_modes,sizeof(tmp_modes)); + if(status = ioctl(fd,TCGETA,&tmp_modes) < 0){ + return(-1); + }/* End IF */ + + termios_p->c_iflag = tmp_modes.c_iflag; + termios_p->c_oflag = tmp_modes.c_oflag; + termios_p->c_cflag = tmp_modes.c_cflag; + termios_p->c_lflag = tmp_modes.c_lflag; + termios_p->c_line = tmp_modes.c_line; + for(i = 0; i < (NCCS < NCC ? NCCS : NCC); i++){ + termios_p->c_cc[i] = tmp_modes.c_cc[i]; + }/* End FOR */ + + return(status); + +}/* End Routine */ + +#endif /* !HAVE_TCGETATTR */ + +#if !HAVE_TCSETATTR + +/* TCSETATTR - Mimic the POSIX terminal get/set functions + * + * Function: + * + * For a system being compiled with POSIX headers, but which doesn't + * actually have all the POSIX functions in libc, we have to fake up + * this call. + */ +tcsetattr(fd,flags,termios_p) +int fd; +int flags; +const struct termios *termios_p; +{ +struct termio tmp_modes; +register int i; +int status; + + bzero(&tmp_modes,sizeof(tmp_modes)); + tmp_modes.c_iflag = termios_p->c_iflag; + tmp_modes.c_oflag = termios_p->c_oflag; + tmp_modes.c_cflag = termios_p->c_cflag; + tmp_modes.c_lflag = termios_p->c_lflag; + tmp_modes.c_line = termios_p->c_line; + for(i = 0; i < (NCCS < NCC ? NCCS : NCC); i++){ + tmp_modes.c_cc[i] = termios_p->c_cc[i]; + }/* End FOR */ + if(status = ioctl(fd,TCSETA,&tmp_modes) < 0){ + return(-1); + }/* End IF */ + + return(status); + +}/* End Routine */ + +#endif /* !HAVE_TCSETATTR */ + +#ifdef VMS + + +/* INITIALIZE_TTY - Set the tty up the way we want it + * + * Function: + * + * This routine is called at startup time to set up the terminal the way + * that we require, i.e., it sets the tty modes. It also gives the screen + * package a chance to initialize itself. + */ +void +initialize_tty() +{ +register int status; +$DESCRIPTOR(input_name,"SYS$INPUT"); +$DESCRIPTOR(output_name,"SYS$OUTPUT"); + +struct sense_mode_buffer { + unsigned char class; + unsigned char type; + unsigned short buffer_size; + unsigned char characteristics[3]; + unsigned char page_length; +} sense_buffer; + +struct sense_iosb { + unsigned short status; + unsigned char transmit_speed; + unsigned char receive_speed; + unsigned char cr_fill_count; + unsigned char lf_fill_count; + unsigned char parity_flags; + unsigned char unused; +} sense_iosb; + +/* + * Assign channels to the terminal + */ + status = sys$assign(&input_name,&tty_input_chan,0,0); + if(!(status & STS$M_SUCCESS)){ + tec_error(status,"VTECO: ASSIGN of SYS$INPUT Failed"); + }/* End IF */ + +/* + * Standard output MUST by a tty at this time. + */ + status = sys$assign(&output_name,&tty_output_chan,0,0); + if(!(status & STS$M_SUCCESS)){ + tec_error(status,"VTECO: ASSIGN of SYS$OUTPUT Failed"); + }/* End IF */ + +/* + * Get the baud rate of the terminal + */ + status = sys$qiow(0,tty_output_chan,IO$_SENSEMODE, + &sense_iosb,0,0,&sense_buffer,sizeof(sense_buffer),0,0,0,0); + if(!(status & STS$M_SUCCESS)){ + tec_error(status,"VTECO: Error on sense mode %d (%x)",status,status); + }/* End IF */ + + term_lines = sense_buffer.page_length; + term_columns = sense_buffer.buffer_size; + + term_speed = map_baud(sense_iosb.transmit_speed); + tty_modes_saved = YES; + +/* + * Check out the termcap description for this tty now. It makes + * no sense to go changing the terminal modes all around until + * we decide whether we can run on this terminal or not. + */ + init_term_description(); +/* + * Now set the modes that we want + */ + screen_init(); + +}/* End Routine */ + +/* END OF VMS CONDITIONAL CODE */ + +#endif + + + +/* RESTORE_TTY - Set the tty back to the way that it was + * + * Function: + * + * This routine is called at exit time to set the terminal modes back + * how they were when we started. + */ +void +restore_tty() +{ + +/* + * Give the screen package a chance to cleanup + */ + screen_finish(); + +/* + * Can't restore the terminal modes if they haven't been saved yet + */ + if(tty_modes_saved != YES) return; + +/* + * Restore the modes that we saved away previously + */ +#if HAVE_TERMIOS_H + if(tcsetattr(tty_input_chan,TCSADRAIN,&tty_input_modes) < 0){ + perror("could not set input tty modes"); + punt(errno); + }/* End IF */ + if(tcsetattr(tty_output_chan,TCSADRAIN,&tty_output_modes) < 0){ + perror("could not set output tty modes"); + punt(errno); + }/* End IF */ +#else +#ifdef UNIX + if(ioctl(tty_input_chan,SET_IOCTL,&tty_input_modes) < 0){ + perror("could not restore input tty modes"); + }/* End IF */ + + if(ioctl(tty_output_chan,SET_IOCTL,&tty_output_modes) < 0){ + perror("could not restore output tty modes"); + }/* End IF */ +#endif +#endif + +}/* End Routine */ + + + +/* TTY_INPUT_PENDING - Check if there is input waiting on the cmd channel + * + * Function: + * + * This routine returns a true false indication of whether or not there + * is input waiting on the command channel. This generally gets used to + * set the input_pending_flag variable. Now it is set to only work if + * the baud rate is quite low, since some of the current side effects are + * undesirable. + */ +int +tty_input_pending() +{ +#ifdef HAS_FIONREAD +long temp; +#endif + +#ifdef VMS +struct sense_typeahead_buffer { + unsigned short count; + unsigned char first_char; + unsigned char reserved1; + unsigned long reserved2; +} sense_typeahead_buffer; +#endif + +#ifdef SEQUOIA +struct sequoia_weirdo_get_param_buffer { + unsigned short fc_open_mode; + unsigned long fc_num_bytes_remaining; + char fc_no_delay; + char fc_modify_no_delay; + char fc_append; + char fc_modify_append; + char fc_close_across_exec; + char fc_modify_close_across_exec; + char fc_no_further_opens; + char fc_modify_no_further_opens; +}term_params; + +typedef enum +{ + FC_SET_PARAMETERS, + FC_GET_PARAMETERS, + FC_SET_BUFFER_REPLACEMENT_PARAMETERS, + FC_GET_BUFFER_REPLACEMENT_PARAMETERS, +} FILE_CONTROL_REQUEST; +#endif + +#ifdef SEQUOIA +int err; + err = seqksr_file_control(&term_params,tty_input_chan,FC_GET_PARAMETERS); + if(err == 0 && term_params.fc_num_bytes_remaining != 0) return(YES); +#endif + +#ifdef HAS_POLL + int err; + struct pollfd pollStructs[1]; +#endif + +#ifdef HAS_SELECT + int err; + fd_set theFds; + struct timeval zeroTime; +#endif + + PREAMBLE(); + +#ifdef HAS_SELECT + FD_ZERO( &theFds ); + FD_SET( tty_input_chan, &theFds ); + zeroTime.tv_sec = 0; + zeroTime.tv_usec = 0; + err = select( tty_input_chan+1, &theFds, 0, 0, &zeroTime ); + if(err > 0) + { + return(YES); + } +#endif + +#ifdef HAS_POLL + pollStructs[0].fd = tty_input_chan; + pollStructs[0].events = POLLIN; + pollStructs[0].revents = 0; + err = poll( pollStructs, ELEMENTS(pollStructs), 0 ); + if( err >= 0 && pollStructs[0].revents & POLLIN) return(YES); +#endif + +#ifdef HAS_FIONREAD + temp = 0; + if(ioctl(tty_input_chan,FIONREAD,&temp) < 0) return(NO); + if(temp > 0) return(YES); +#endif + +#ifdef VMS + temp = sys$qiow(0,tty_input_chan,IO$_SENSEMODE|IO$M_TYPEAHDCNT, + 0,0,0,&sense_typeahead_buffer,sizeof(sense_typeahead_buffer),0,0,0,0); + if(!(temp & STS$M_SUCCESS)){ + return(NO); + }/* End IF */ + + if(sense_typeahead_buffer.count > 0) return(YES); + +#endif + + return(NO); + +}/* End Routine */ + + + +/* INIT_SIGNALS - Initialize the signal interface for our use + * + * Function: + * + * This routine sets up the way that we want signals to behave + */ +void +init_signals() +{ + +#if 0 +int cmd_suspend(); +#endif +int cmd_interrupt(); +int cmd_alarm(); +int cmd_winch(); + + PREAMBLE(); + +#ifdef CHECKPOINT + signal(SIGALRM,(void (*)())cmd_alarm); + alarm(checkpoint_interval); +#endif /* CHECKPOINT */ + + signal(SIGINT,(void (*)())cmd_interrupt); + +#ifdef JOB_CONTROL + if(suspend_is_okay_flag == YES){ + signal(SIGTSTP,(void (*)())cmd_suspend); + } +#endif + +#ifdef SUN_STYLE_WINDOW_SIZING + signal(SIGWINCH,(void (*)())cmd_winch); +#endif + +}/* End Routine */ + + + +/* TECO_INI - Routine to read the TECO_INI file + * + * Function: + * + * This routine reads the user's TECO_INI file and sets up the default + * Q-Register contents he has specified. It also executes Q-Register 0. + */ +void +teco_ini() +{ +register char *cp; +register int c,dc; +register int i; +char filename[LINE_BUFFER_SIZE]; +char tmp_message[LINE_BUFFER_SIZE]; +FILE *fp; +struct buff_header *hbp; +char zero_flag = NO; +char comment_flag = NO; + + PREAMBLE(); + +#ifdef UNIX + + if((cp = getenv("HOME"))){ + (void) strcpy(filename,cp); + }/* End IF */ + + else return; + + (void) strcat(filename,"/.teco_ini"); + +#endif + +#ifdef VMS + + strcpy(filename,"SYS$LOGIN:TECO.INI"); + +#endif + + fp = fopen(filename,"r"); + + if(fp == NULL) return; + +/* + * Outer loop reads the name of the Q-Register to be loaded and the delimiter, + * inner loop reads contents of a Q-Register in. + */ + while(1){ + while(1){ + if((c = getc(fp)) == EOF){ + fclose(fp); + if(zero_flag) exec_doq0(); + return; + }/* End IF */ + + if(comment_flag){ + if(c == '!') comment_flag = NO; + continue; + }/* End IF */ + + if(c == '!'){ + comment_flag = YES; + continue; + }/* End IF */ + + if(isspace(c)) continue; + break; + + }/* End While */ + + if(c == '0') zero_flag = YES; + + hbp = buff_qfind(c,1); + if(hbp == NULL){ + fclose(fp); + return; + }/* End IF */ + + if((dc = getc(fp)) == EOF){ + sprintf(tmp_message,"?EOF while loading Q-REGISTER <%c>",c); + error_message(tmp_message); + fclose(fp); + return; + }/* End IF */ + + while(1){ + if((i = getc(fp)) == EOF){ + sprintf(tmp_message,"?EOF while loading Q-REGISTER <%c>",c); + error_message(tmp_message); + fclose(fp); + return; + }/* End IF */ + + if(i == dc) break; + + buff_insert_char(hbp,hbp->zee,i); + + }/* End While */ + + hbp->ismodified = NO; + + }/* End While */ + +}/* End Routine */ + + + +void +check_for_forced_screen_size(argc,argv) +int argc; +char **argv; +{ +register int i; +char *cp, c; + + PREAMBLE(); + + for(i = 1; i < argc; i++){ + if(argv[i][0] == '-'){ + cp = &argv[i][1]; + while((c = *cp++)){ + switch(c){ + case 'X': case 'x': + case 'W': case 'w': + if(isdigit((int)*cp)){ + forced_width = 0; + while(isdigit((int)*cp)){ + forced_width = forced_width * 10 + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_width = atoi(argv[i+1]); + i++; + } + else forced_width = 80; + break; + case 'Y': case 'y': + case 'H': case 'h': + if(isdigit((int)*cp)){ + forced_height = 0; + while(isdigit((int)*cp)){ + forced_height = forced_height * 10 + + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_height = atoi(argv[i+1]); + i++; + } + else forced_height = 24; + break; + case 'O': case 'o': + output_tty_name = argv[i+1]; + i++; + break; + }/* End Switch */ + }/* End While */ + continue; + }/* End IF */ + + }/* End FOR */ + +}/* End Routine */ + + + +/* HANDLE_COMMAND_LINE - OS Dependent Code to handle command line arguments + * + * Function: + * + * This code handles the command line arguments to VTECO. This requires + * different processing for different operating systems. + */ +#ifdef UNIX +int +handle_command_line(which_time,argc,argv) +int which_time; +int argc; +char **argv; +{ +register int i; +extern struct buff_header *curbuf; +int first_file_argument; +char *cp, c; +int arg_skip; + + PREAMBLE(); + + first_file_argument = 0; + for(i = 1; i < argc; i += arg_skip){ + arg_skip = 1; + if(argv[i][0] == '-'){ + cp = &argv[i][1]; + while((c = *cp++)){ + switch(c){ + case 'C': case 'c': + checkpoint_enabled = NO; + break; + case 'X': case 'x': + case 'W': case 'w': + if(isdigit((int)*cp)){ + forced_width = 0; + while(isdigit((int)*cp)){ + forced_width = forced_width * 10 + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_width = atoi(argv[i+1]); + arg_skip++; + } + else forced_width = 80; + break; + case 'Y': case 'y': + case 'H': case 'h': + if(isdigit((int)*cp)){ + forced_height = 0; + while(isdigit((int)*cp)){ + forced_height = forced_height * 10 + + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_height = atoi(argv[i+1]); + i++; + } + else forced_height = 24; + break; + case 'O': case 'o': + output_tty_name = argv[i+1]; + arg_skip++; + break; + default: + fprintf(stderr,"teco: unknown switch '%c'\n",c); + cp = ""; + break; + }/* End Switch */ + }/* End While */ + continue; + }/* End IF */ + + if(which_time == 0) return( SUCCESS ); + + if(!first_file_argument) first_file_argument = i; + if(!outof_memory){ + buff_openbuffer(argv[i],0,readonly_flag); + }/* End IF */ + + }/* End FOR */ + + if(first_file_argument){ + buff_openbuffer(argv[first_file_argument],0,readonly_flag); + }/* End IF */ + + if(piped_input_flag){ + buff_openbuffnum(-1,0); + buff_readfd(curbuf,"Standard Input",fileno(stdin)); + curbuf->dot = 0; + }/* End IF */ + + return( SUCCESS ); + +}/* End Routine */ +#endif + +#ifdef VMS + +int +handle_command_line(which_time,argc,argv) +int which_time; +int argc; +char **argv; +{ +register int i,j; +int count; +register char *cp,*dp; +struct wildcard_expansion *name_list,*np; +struct wildcard_expansion *expand_filename(); +int number_of_buffers_opened = 0; +char switch_buffer[TECO_FILENAME_TOTAL_LENGTH]; +char *command_line; +struct dsc$descriptor command_desc; +struct dsc$descriptor symbol_value; +$DESCRIPTOR (symbol_name,"teco_spawn_execute"); +long table = LIB$K_CLI_LOCAL_SYM; +long flags = 0; +long status; +long completion_status; +long sub_pid; + + PREAMBLE(); + + for(i = 1; i < argc; i++){ + cp = argv[i]; + if(*cp == '/'){ + cp++; + dp = switch_buffer; + while(isalnum(*cp)) *dp++ = *cp++; + *dp = '\0'; + if(strcmp(switch_buffer,"subjb") == 0){ + continue; + }/* End IF */ + + else if(strcmp(switch_buffer,"nosuspend") == 0){ + suspend_is_okay_flag = NO; + }/* End IF */ + + else if(strcmp(switch_buffer,"nocheckpoint") == 0){ + checkpoint_enabled = NO; + }/* End IF */ + + else if(strcmp(switch_buffer,"spawn") == 0){ + if(which_time == 1){ + cp -= 5; + dp = "subjb"; + while(*dp) *cp++ = *dp++; + strcpy(switch_buffer,"$ "); + strcat(switch_buffer,argv[0]); + symbol_value.dsc$w_length = strlen(switch_buffer); + symbol_value.dsc$b_dtype = 0; + symbol_value.dsc$b_class = DSC$K_CLASS_S; + symbol_value.dsc$a_pointer = switch_buffer; + status = lib$set_symbol(&symbol_name,&symbol_value,&table); + if(status != SS$_NORMAL){ + perror("could not create symbol 'teco_spawn_execute'"); + punt(status); + }/* End IF */ + count = strlen(symbol_name.dsc$a_pointer); + for(j = 1; j < argc; j++){ + count += strlen(argv[j]) + 1; + }/* End FOR */ + command_line = tec_alloc(TYPE_C_CBUFF,count+8); + if(command_line == NULL) return(FAIL); + dp = command_line; + cp = symbol_name.dsc$a_pointer; + while(*dp++ = *cp++); dp--; + for(j = 1; j < argc; j++){ + *dp++ = ' '; + cp = argv[j]; + while(*dp++ = *cp++); + dp--; + }/* End FOR */ + command_desc.dsc$w_length = strlen(command_line); + command_desc.dsc$b_dtype = 0; + command_desc.dsc$b_class = DSC$K_CLASS_S; + command_desc.dsc$a_pointer = command_line; + + printf("VTECO: Spawning subjob for edit\n"); + + status = lib$spawn(&command_desc,0,0,&flags,0, + &sub_pid,&completion_status,0,0,0,0,0); + if(status != SS$_NORMAL){ + perror("VTECO: LIB$SPAWN failed"); + punt(status); + }/* End IF */ + + exit(SS$_NORMAL); + + }/* End IF */ + }/* End IF */ + + else { + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "?Unknown VTECO switch '%s'", + switch_buffer + ); + tec_error(EINVAL,tmp_message); + }/* End Else */ + + }/* End IF */ + + if(which_time == 0) return(SUCCESS); + + if(argv[i][0] != '/'){ + np = expand_filename(argv[i]); + while(np){ + number_of_buffers_opened++; + buff_openbuffer(np->we_name,0,readonly_flag); + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,name_list); + }/* End While */ + }/* End IF */ + + }/* End FOR */ + + if(number_of_buffers_opened > 0) buff_openbuffer(0,1,readonly_flag); + + return(SUCCESS); + +}/* End Routine */ + + + +/* EXPAND_FILENAME - Expand VMS wildcards to a list of files + * + * Function: + * + * This routine returns a linked list of expanded filenames. + */ +struct wildcard_expansion * +expand_filename(wildcard_string) +char *wildcard_string; +{ +struct wildcard_expansion *name_list,*np; +struct dsc$descriptor argument; +struct dsc$descriptor result; +struct { + unsigned short curlen; + char body[TECO_FILENAME_TOTAL_LENGTH]; +}result_buffer; +int context; +int fnd_flags; +int status; +char flags = 0; +char *cp,*sp; + +#define NODE_SEEN (1<<0) +#define DEV_SEEN (1<<1) +#define DIR_SEEN (1<<2) +#define VER_SEEN (1<<3) + + PREAMBLE(); + +/* + * Determine which fields are present in the supplied string so that we + * may trim the resulting filespecs some. + */ + for(cp = wildcard_string; *cp != '\0'; cp++){ + switch(*cp){ + case ':': + if(cp[1] == ':'){ + flags |= NODE_SEEN; + cp++; + }/* End IF */ + else flags |= DEV_SEEN | DIR_SEEN; + break; + case '[': + flags |= DIR_SEEN; + break; + case ';': + if(cp[1] != '\0') flags |= VER_SEEN; + break; + }/* End Switch */ + }/* End FOR */ + + argument.dsc$w_length = strlen(wildcard_string); + argument.dsc$b_dtype = 0; + argument.dsc$b_class = DSC$K_CLASS_S; + argument.dsc$a_pointer = wildcard_string; + + result.dsc$w_length = sizeof(result_buffer.body); + result.dsc$b_dtype = DSC$K_DTYPE_VT; + result.dsc$b_class = DSC$K_CLASS_VS; + result.dsc$a_pointer = &result_buffer; + + context = 0; + fnd_flags = (1<<1); + + name_list = NULL; + + while(1){ + status = lib$find_file(&argument,&result,&context,0,0,0,&fnd_flags); + if((status & 0xFFFF) != SS$_NORMAL) break; + np = tec_alloc(TYPE_C_WILD, sizeof(struct wildcard_expansion)); + if(np == NULL) return(NULL); + +/* + * Trim the resulting filespec as much as possible + */ + sp = result_buffer.body; + sp[result_buffer.curlen] = '\0'; + + if((flags & DEV_SEEN) == 0){ + cp = sp; + while(*cp && *cp != ':') cp++; + if(*cp == ':') strcpy(sp,cp + 1); + }/* End IF */ + + if((flags & DIR_SEEN) == 0){ + cp = sp; + while(*cp && *cp != '[') cp++; + if(*cp == '['){ + sp = cp + 1; + while(*sp && *sp != ']') sp++; + if(*sp == ']'){ + strcpy(cp,sp+1); + }/* End IF */ + sp = result_buffer.body; + }/* End IF */ + }/* End IF */ + + if((flags & VER_SEEN) == 0){ + cp = sp; + while(*cp && *cp != ';') cp++; + *cp = '\0'; + }/* End IF */ + + if(strlen(sp) > (sizeof(np->we_name)-1)){ + error_message("Filename too long"); + continue; + }/* End IF */ + + strcpy(np->we_name,sp); + np->we_next = name_list; + name_list = np; + }/* End While */ + + lib$find_file_end(&context); + +/* + * If no expansion names at all, perhaps he specified a non-wild file which + * he wants to create. If so, we will just use the wild specification as is. + */ + if(name_list == NULL){ + np = tec_alloc(TYPE_C_WILD, sizeof(struct wildcard_expansion)); + if(np == NULL) return(NULL); + if(strlen(wildcard_string) > (sizeof(np->we_name)-1)){ + error_message("Filename too long"); + return(NULL); + }/* End IF */ + + strcpy(np->we_name,wildcard_string); + np->we_next = name_list; + name_list = np; + + }/* End IF */ + + return(name_list); + +}/* End Routine */ + +/* END OF VMS CONDITIONAL CODE */ + +#endif + + + +/* + * EXPAND_FILENAME - Expand a wildcard specification to a list of files + * + * Function: + * + * This routine returns a linked list of expanded filenames. + * + */ +#ifdef UNIX +struct wildcard_expansion *name_list; +struct wildcard_expansion *name_list_end; + +struct wildcard_expansion * +expand_filename(wildcard_string) +char *wildcard_string; +{ +char temp_name[TECO_FILENAME_TOTAL_LENGTH]; +register char *cp,*sp; +struct passwd *pw; +struct passwd *getpwnam(); + + PREAMBLE(); + + name_list = NULL; + name_list_end = NULL; + +/* + * Else, start branching down into the directory list he specified + */ + if(wildcard_string[0] == '/'){ + process_directory(&wildcard_string[0],"/",2); + } + else if (wildcard_string[0] == '~'){ + cp = temp_name; + sp = &wildcard_string[1]; + while(*sp && *sp != '/') *cp++ = *sp++; + *cp++ = '\0'; + if(temp_name[0] == '\0'){ + if((cp = getenv("HOME"))) strcpy(temp_name,cp); + } + else { + pw = getpwnam(temp_name); + if(pw) strcpy(temp_name,pw->pw_dir); + } + strcat(temp_name,sp); + process_directory(temp_name,"/",2); + } + else { + strcpy(temp_name,"./"); + strcat(temp_name,wildcard_string); + process_directory(temp_name,".",1); + } + + return(name_list); + +}/* End Routine */ + +void +process_directory(wildstr,path,flags) +char *wildstr; +char *path; +int flags; +{ +char directory_path[TECO_FILENAME_TOTAL_LENGTH]; +struct wildcard_expansion *np; +DIR *dirp; + +struct dirent *dp; + +struct stat statbuf; +int c; +char *cp; + + PREAMBLE(); + +/* + * Find the next slash, and if there isn't one, then we have reached + * the end of the file specification, and this should be a file rather + * than a directory. + */ + wildstr = (char *)strchr(wildstr,'/'); + + directory_path[0] = '\0'; + + if(wildstr == NULL){ + if(flags < 0){ + if(stat(path,&statbuf) < 0) return; +#ifndef USE_S_MACS + if((statbuf.st_mode & S_IFMT) != S_IFREG) return; +#else + if(!S_ISREG(statbuf.st_mode)) return; +#endif + }/* End IF */ + if(strlen(path) > (sizeof(np->we_name)-1)){ + error_message("Filename too long"); + return; + }/* End IF */ + np = (struct wildcard_expansion *) + tec_alloc(TYPE_C_WILD, sizeof(struct wildcard_expansion)); + if(np == NULL) return; + np->we_next = NULL; + strcpy(np->we_name,path); + if(name_list == NULL) name_list = np; + if(name_list_end)name_list_end->we_next = np; + name_list_end = np; + return; + }/* End IF */ + +#ifndef USE_S_MACS + if(stat(path,&statbuf) < 0 || ((statbuf.st_mode & S_IFMT) != S_IFDIR)){ + return; + }/* End IF */ +#else + if (stat(path,&statbuf) < 0 || !S_ISDIR(statbuf.st_mode)) { + return; + } +#endif + +/* + * This loop tests whether there are any wildcard characters in the + * next directory. If not, don't go through the overhead of reading + * directories, just simply hop down to that subdir. + */ + cp = wildstr+1; + while((c = *cp++)){ + switch(c){ + case '*': + case '?': + case '[': + case '{': + while(*cp != '\0' && *cp != '/') cp++; + goto read_directory; + case '\0': + strcpy(directory_path,path); + strcat(directory_path,"/"); + strcat(directory_path,wildstr+1); + process_directory(NULL,directory_path,-1); + return; + case '/': + cp--; + *cp = '\0'; + switch(flags){ + case 2: + strcpy(directory_path,"/"); + strcat(directory_path,wildstr+1); + flags = 0; + break; + case 1: + strcpy(directory_path,wildstr+1); + flags = 0; + break; + case 0: + case -1: + strcpy(directory_path,path); + strcat(directory_path,"/"); + strcat(directory_path,wildstr+1); + break; + }/* End Switch */ + + *cp = '/'; + process_directory(wildstr+1,directory_path,flags); + return; + }/* End Switch */ + }/* End While */ + + switch(flags){ + case 2: + strcat(directory_path,wildstr); + flags = 0; + break; + case 1: + strcpy(directory_path,wildstr+1); + flags = 0; + break; + case 0: + case -1: + strcpy(directory_path,path); + strcat(directory_path,wildstr); + break; + }/* End Switch */ + process_directory("",directory_path,flags); + return; + +read_directory: + + dirp = opendir(path); + + while((dp = readdir(dirp)) != NULL) { +#ifndef _POSIX_SOURCE + if(dp->d_ino == 0) continue; +#endif /* _POSIX_SOURCE */ + if(match_name(dp->d_name,wildstr+1)){ + switch(flags){ + case 2: + strcpy(directory_path,"/"); + strcat(directory_path,dp->d_name); + break; + case 1: + strcpy(directory_path,dp->d_name); + break; + case -1: + case 0: + strcpy(directory_path,path); + strcat(directory_path,"/"); + strcat(directory_path,dp->d_name); + }/* End Switch */ + process_directory(cp,directory_path,-1); + }/* End IF */ + }/* End While */ + + closedir(dirp); + +}/* End Routine */ + +/* + * MATCH_NAME - Check whether a name satisfies a wildcard specification + * + * Function: + * + * This routine attempts to do csh style wildcard matching so that + * internal teco routines may support this behavior. + */ +int +match_name(name,pattern) +char *name; +char *pattern; +{ +char temp_buff[TECO_FILENAME_TOTAL_LENGTH]; +register int c; +register char *cp,*sp; +int match; +int previous_char; +int pattern_char; + + PREAMBLE(); + + while(1){ + switch(pattern_char = *pattern++){ + case '/': + case '\0': + return(*name == '\0' ? 1 : 0); + case '?': + return(*name == '\0' || *name == '/' ? 0 : 1); +/* + * Open-Bracket allows any of a list of characters to match, such as [abc], + * or a range such as [a-c], or a combination such as [abg-mz]. + */ + case '[': + match = 0; + c = *name; + previous_char = 'A'; + while((pattern_char = *pattern++)){ + if(pattern_char == c) match = 1; + if(pattern_char == ']'){ + if(match) break; + return(0); + }/* End IF */ + if(pattern_char == '-'){ + pattern_char = *pattern++; + if(pattern_char == ']'){ + pattern--; + pattern_char = 'z'; + }/* End IF */ + if(c >= previous_char && c <= pattern_char){ + match = 1; + }/* End IF */ + }/* End IF */ + previous_char = pattern_char; + }/* End While */ + break; +/* + * Brace allows a list of strings, any one of which may match, and any + * one of which may contain further wildcard specifications. + */ + case '{': + cp = temp_buff; + while((pattern_char = *pattern++)){ + if(pattern_char == ',' || pattern_char == '}'){ + sp = pattern; + if(pattern_char == ','){ + while((c = *sp++)){ + if(c == '}') break; + }/* End While */ + }/* End IF */ + while((*cp++ = *sp++)); + if(match_name(name,temp_buff)) return(1); + if(pattern_char == '}') return(0); + cp = temp_buff; + }/* End IF */ + else *cp++ = pattern_char; + }/* End While */ + break; +/* + * Asterisk matches any string + */ + case '*': + if(*pattern == '\0') return(1); + if(*pattern == '/') return(1); + for(c = 0; name[c] != '\0'; c++){ + if(match_name(&name[c],pattern)) return(1); + }/* End FOR */ + return(0); + default: + if(pattern_char != *name) return(0); + break; + }/* End Switch */ + name++; + }/* End While */ +}/* End Routine */ + +#endif + + + +/* PUNT - Exit with a specified error code + * + * Function: + * + * PUNT is called with an errno code with which we want to exit + * + */ +void +punt(exit_code) +int exit_code; +{ + PREAMBLE(); + + exit(exit_code); + +}/* End Routine */ + + +/* TEC_PANIC - Print an error string + * + * Function: + * + * This routine is called previous to punt to print an error string + */ +void +tec_panic(string) +char *string; +{ + PREAMBLE(); + + fputs(string,stdout); + fputs("\n",stdout); + +#ifndef UNIX + exit(1); +#else + kill(getpid(),SIGQUIT); +#endif + +}/* End Routine */ + +/* TEC_ERROR - Exit with specified error code, printing an error string + * + * Function: + * + * This routine is called to exit with the specified error code + * after printing the supplied error string. + */ +void +tec_error(code,string) +int code; +char *string; +{ + PREAMBLE(); + + fprintf(stderr,"%s\n",string); + +#ifdef VMS + exit(1); +#endif + + exit(code); + +}/* End Routine */ + +#if DEBUG_UNUSED +/* OPEN_DEBUG_LOG_FILE - Open a file to write debug output to + * + * Function: + * + * This routine is called so that a log file is open for writing of + * debugging messages. + */ +void +open_debug_log_file() +{ + PREAMBLE(); + + teco_debug_log = fopen("teco.dbg","w"); + if(teco_debug_log == NULL){ + teco_debug_log = stderr; + }/* End IF */ + +}/* End Routine */ +#endif + + + +/* MAP_BAUD - Map system dependent baud fields to a standard integer + * + * Function: + * + * This routine takes an operating system specific baud rate + * representation, and maps it into a simple integer value. + */ +int +map_baud(input_baud_rate) +int input_baud_rate; +{ +register int i; +int return_baud; + +#ifdef UNIX + +#ifndef B19200 +#define B19200 B9600 +#endif +#ifndef B38400 +#define B38400 B9600 +#endif + +static unsigned int encoded_bauds[] = { + B0,B50,B75,B110,B134,B150,B200,B300,B600,B1200, + B1800,B2400,B4800,B9600,B19200,B38400, + EXTA,EXTB }; +static int equivalent_baudrates[] = { + 0,50,75,110,134,150,200,300,600,1200, + 1800,2400,4800,9600,19200,38400, + 9600,9600 }; + +/* END OF UNIX CONDITIONAL CODE */ + +#endif + +#ifdef VMS +static unsigned char encoded_bauds[] = { + TT$C_BAUD_50,TT$C_BAUD_75,TT$C_BAUD_110,TT$C_BAUD_134, + TT$C_BAUD_150,TT$C_BAUD_300,TT$C_BAUD_600,TT$C_BAUD_1200, + TT$C_BAUD_1800,TT$C_BAUD_2000,TT$C_BAUD_2400,TT$C_BAUD_3600, + TT$C_BAUD_4800,TT$C_BAUD_7200,TT$C_BAUD_9600,TT$C_BAUD_19200 }; +static int equivalent_baudrates[] = { + 50,75,110,134, + 150,300,600,1200, + 1800,2000,2400,3600, + 4800,7200,9600,19200 }; + +/* END OF VMS CONDITIONAL CODE */ + +#endif + + PREAMBLE(); + +/* + * Default is a common baud rate incase we miss somehow + */ + return_baud = 9600; + +/* + * Determine how many entries there are in the baud table + */ + i = sizeof(encoded_bauds) / sizeof(encoded_bauds[0]); + +/* + * Now scan the table for our input baud rate + */ + while(--i >= 0){ + if(input_baud_rate != encoded_bauds[i]) continue; + return_baud = equivalent_baudrates[i]; + break; + }/* End While */ + + return(return_baud); + +}/* End Routine */ + + + +/* ERROR_TEXT - Return 'errno' style error string + * + * Function: + * + * This routine is called to return the error string associated with an + * errno error code. + */ +char * +error_text(err_num) +register int err_num; +{ + +#ifdef VMS + + int sys_nerr; + +static char *tec_errlist[] = { + "success", /* ESUCCESS */ + "not owner", /* EPERM */ + "no such file or directory", /* ENOENT */ + "no such process", /* ESRCH */ + "interrupted system call", /* EINTR */ + "i/o error", /* EIO */ + "no such device or address", /* ENXIO */ + "arg list too long", /* E2BIG */ + "exec format error", /* ENOEXEC */ + "bad file number", /* EBADF */ + "no children", /* ECHILD */ + "no more processes", /* EAGAIN */ + "not enough core", /* ENOMEM */ + "permission denied", /* EACCES */ + "bad address", /* EFAULT */ + "block device required", /* ENOTBLK */ + "mount device busy", /* EBUSY */ + "file exists", /* EEXIST */ + "cross-device link", /* EXDEV */ + "no such device", /* ENODEV */ + "not a directory", /* ENOTDIR */ + "is a directory", /* EISDIR */ + "invalid argument", /* EINVAL */ + "file table overflow", /* ENFILE */ + "too many open files", /* EMFILE */ + "not a typewriter", /* ENOTTY */ + "text file busy", /* ETXTBSY */ + "file too large", /* EFBIG */ + "no space left on device", /* ENOSPC */ + "illegal seek", /* ESPIPE */ + "read-only file system", /* EROFS */ + "too many links", /* EMLINK */ + "broken pipe", /* EPIPE */ + "math argument", /* EDOM */ + "result too large" /* ERANGE */ +}; + + PREAMBLE(); + + sys_nerr = sizeof(tec_errlist) / sizeof(tec_errlist[0]) - 1; + +/* END OF VMS CONDITIONAL CODE */ + +#endif + +#if 0 + if(err_num < 0 || err_num > sys_nerr){ + return("unknown error"); + }/* End IF */ +#endif + +#ifdef UNIX +#if 0 + return((char *)sys_errlist[err_num]); +#endif + return( strerror( err_num )); +#endif + +#ifdef VMS + return(tec_errlist[err_num]); +#endif + +}/* End Routine */ @@ -0,0 +1,657 @@ +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/teco.h,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* teco.h + * Global TECO Definitions + * %W% (PC) %G% + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#define YES 1 +#define NO 0 +#define FAIL 0 +#define SUCCESS 1 +#define NULL 0 + +#define CREATE_OLD_FILES + +#define BLOCKED 2 +#define INVALIDATE 3 + +#define VMAJOR 6 +#define VMINOR 7 +#define AUTO_DATE "$Date: 2007/12/10 21:59:20 $" + +#define TECO_FILENAME_TOTAL_LENGTH 1024 +#define TECO_FILENAME_COMPONENT_LENGTH 256 +#define TECO_READONLY_NAME "visit" +#define TECO_INTERNAL_BUFFER_NAME "TECO-" +#define TERMCAP_BUFFER_SIZE 2048 +#define SCREEN_OUTPUT_BUFFER_SIZE 4096 +#define SCREEN_RESERVED_LINES 2 +#define SCREEN_MAX_LABEL_FIELDS 4 +#define SCREEN_NOMINAL_LINE_WIDTH 132 +#define SCREEN_MAX_LINES 1024 +#define SCREEN_MINIMUM_WINDOW_HEIGHT 4 +#define IO_BUFFER_SIZE 512 +#define LINE_BUFFER_SIZE 1024 +#define DEFAULT_CHECKPOINT (5*60) +#define TAG_HASH_ENTRIES 1024 +#define PARSER_STRING_MAX 1024 +#define SEARCH_STRING_MAX PARSER_STRING_MAX +#define NORMAL_TAB_WIDTH 8 +#define MAX_TAB_WIDTH 16 +#define MAXOF( a,b) (a) > (b) ? (a) : (b) + +#define INITIAL_LINE_BUFFER_SIZE 32 +#define INCREMENTAL_LINE_BUFFER_SIZE 32 +#define MINIMUM_ALLOCATION_BLOCK 32 +#define LOOKASIDE_COUNT 32 +#define LARGEST_LOOKASIDE_BLOCK (LOOKASIDE_COUNT * MINIMUM_ALLOCATION_BLOCK) +#define BIG_MALLOC_HUNK_SIZE (LARGEST_LOOKASIDE_BLOCK * 64) +#define NORMAL_TAB_WIDTH 8 + +/* + * The following is where the different machines are described so that + * conditional compilation based on different machines quirks can be done. + */ + +/* + * Unless defined otherwise, we assume 32 bits to an integer and 8 bits / char + */ +#ifndef BITS_PER_INT +#define BITS_PER_INT 32 +#endif +#ifndef BITS_PER_CHAR +#define BITS_PER_CHAR 8 +#endif + +#define ESCAPE 27 +#define RUBOUT 127 + +#define CNTRL_A 1 +#define CNTRL_D 4 +#define CNTRL_E 5 +#define CNTRL_G 7 +#define CNTRL_N 14 +#define CNTRL_R 18 +#define CNTRL_U 21 +#define CNTRL_V 22 +#define CNTRL_W 23 +#define CNTRL_X 24 +#define CNTRL_Z 26 +#define BELL 7 + +/* + * Define memory allocation block types + */ +#define TYPE_C_MINTYPE 33 +#define TYPE_C_CBUFF 33 +#define TYPE_C_CPERM 34 +#define TYPE_C_CMD 35 +#define TYPE_C_UNDO 36 +#define TYPE_C_SCR 37 +#define TYPE_C_SCRBUF 38 +#define TYPE_C_SCREEN 39 +#define TYPE_C_SCREENBUF 40 +#define TYPE_C_LINE 41 +#define TYPE_C_LINEBUF 42 +#define TYPE_C_BHDR 43 +#define TYPE_C_WINDOW 44 +#define TYPE_C_LABELFIELD 45 +#define TYPE_C_WILD 46 +#define TYPE_C_TAGS 47 +#define TYPE_C_TAGENT 48 +#define TYPE_C_TAGSTR 49 +#define TYPE_C_MAXTYPE 49 + +#define MAGIC_SCREEN 0x01010101 +#define MAGIC_BUFFER 0x02020202 +#define MAGIC_LINE 0x03030303 +#define MAGIC_LINE_LOOKASIDE 0x04040404 +#define MAGIC_FORMAT 0x05050505 +#define MAGIC_FORMAT_LOOKASIDE 0x06060606 + +#ifdef DEBUG1 +#define PREAMBLE() \ + do_preamble_checks(); + +#define RETURN \ + do_return_checks(); \ + return; + +#define RETURN_VAL(val) \ + do_return_checks(); \ + return(val); +#else +#define PREAMBLE() +#define RETURN +#define RETURN_VAL() +#endif /* DEBUG1 */ + + +#define ELEMENTS(xyzzy) (sizeof(xyzzy)/sizeof(xyzzy[0])) + +/* + * Define structures used throughout TECO + */ + struct position_cache { + struct buff_line *lbp; + int base; + }; + +#define BUFF_CACHE_CONTENTS(pos_cache) \ + (pos_cache)->lbp->buffer[(pos_cache)->base] +#define BUFF_INCREMENT_CACHE_POSITION(pos_cache) \ + if(++((pos_cache)->base) >= (pos_cache)->lbp->byte_count){ \ + (pos_cache)->lbp = (pos_cache)->lbp->next_line; (pos_cache)->base = 0; \ + } +#define BUFF_DECREMENT_CACHE_POSITION(pos_cache) \ + { \ + if((pos_cache)->base == 0){ \ + (pos_cache)->lbp = (pos_cache)->lbp->prev_line; \ + (pos_cache)->base = (pos_cache)->lbp->byte_count; \ + } \ + (pos_cache)->base -= 1; \ + } + + struct buff_header { + int buf_magic; + unsigned int buf_hash; + char *name; + int buffer_number; + struct buff_header *next_header; + char ismodified; + char isreadonly; + char isbackedup; + int dot; + int zee; + int ivalue; + struct position_cache pos_cache; + + struct buff_line *first_line; + }; + + struct buff_line { + int lin_magic; + int buffer_size; + int byte_count; + char *buffer; + struct buff_line *next_line; + struct buff_line *prev_line; + struct format_line *format_line; + }; + + struct format_line { + int fmt_magic; + struct buff_header *fmt_owning_buffer; + struct buff_line *fmt_buffer_line; + int fmt_buffer_size; + short *fmt_buffer; + int fmt_sequence; + struct format_line *fmt_next_line; + struct format_line *fmt_prev_line; + struct format_line *fmt_next_alloc; + struct format_line *fmt_prev_alloc; + struct format_line *fmt_next_window; + struct format_line *fmt_prev_window; + char fmt_in_use; + char fmt_permanent; + struct screen_line *fmt_saved_line; + short fmt_visible_line_position; + struct window *fmt_window_ptr; + }; + + struct screen_line { + int scr_magic; + short *buffer; + struct format_line *companion; + int sequence; + }; + + struct window { + struct window *win_next_window; + int win_window_number; + int win_x_size; + int win_y_size; + int win_y_base; + int win_y_end; + struct buff_header *win_buffer; + struct format_line win_label_line; + struct format_line *win_dot_format_line; + int win_dot_screen_offset; + char *win_label_field_contents[SCREEN_MAX_LABEL_FIELDS]; + }; + + struct search_element { + unsigned char type; + unsigned char value; + union { + int intarray[256/BITS_PER_INT]; + char bytearray[256/BITS_PER_CHAR]; + }bitmask; + union { + int intarray[256/BITS_PER_INT]; + char bytearray[256/BITS_PER_CHAR]; + }repmask; + }; + + struct search_buff { + int length; + char input[PARSER_STRING_MAX]; + struct search_element data[PARSER_STRING_MAX]; + char error_message_given; + }; + + struct wildcard_expansion { + struct wildcard_expansion *we_next; + char we_name[TECO_FILENAME_TOTAL_LENGTH-4]; + }; + + struct tagent { + char *te_symbol; + char *te_filename; + char *te_lineno; + char *te_search_string; + struct tagent *te_next; + }; + + struct tags { + struct tagent *current_entry; + struct tagent *tagents[TAG_HASH_ENTRIES]; + }; + +/* + * Define the various flags in a screen display short int + */ +#define SCREEN_M_DATA 0x00FF +#define SCREEN_M_FLAGS 0xFF00 +#define SCREEN_M_REVERSE 0x0100 + +/* + * Define constants for the label line fields + */ +#define LABEL_C_TECONAME 0 +#define LABEL_C_FILENAME 1 +#define LABEL_C_READONLY 2 +#define LABEL_C_MODIFIED 3 + +/* + * Define constants for EJ command + */ +#define SETOPTION_MIN_OPTION 1 + +#define SETOPTION_ALTERNATE_ESCAPE_CHAR 1 +#define SETOPTION_SCREEN_HEIGHT 2 +#define SETOPTION_SCREEN_WIDTH 3 +#define SETOPTION_ALTERNATE_DELETE_CHAR 4 +#define SETOPTION_FORCE_8_BIT_CHARS 5 +#define SETOPTION_TAB_WIDTH 6 +#define SETOPTION_HIDE_CR_CHARS 7 + +#define SETOPTION_MAX_OPTION 7 + +/* + * Define constants for FT command + */ +#define TAGS_MIN_OPTION 0 + +#define TAGS_LOAD_TAGS_FILE 0 +#define TAGS_SEARCH_TAGS_FILE 1 +#define TAGS_TEST_FOR_LOADED_TAGS 2 +#define TAGS_INSERT_TARGET_FILE 3 +#define TAGS_INSERT_SEARCH_STRING 4 +#define TAGS_INSERT_LINENO 5 +#define TAGS_INSERT_ALL 6 + +#define TAGS_MAX_OPTION 6 + +/* + * Define useful macros + */ +#define UPCASE(char) (islower(char) ? TOUPPER(char) : char) +#ifdef SYS_III_UNIX +#define TOUPPER(char) _toupper(char) +#define TOLOWER(char) _tolower(char) +#else +#define TOUPPER(char) toupper(char) +#define TOLOWER(char) tolower(char) +#endif + +typedef unsigned long teco_ptrint_t; + +/* + * We define unix except for the really different operating systems, like + * vms. It lets us write our own version of functions which simply do not + * exist outside of unix. + */ +#ifndef VMS +#define UNIX +#define JOB_CONTROL +#endif + +/* + * Include Files From GNU Autoconf/Autoheader + */ +#include "config.h" + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +/* + * sys/filio.h has the FIONREAD definition + */ +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> +#endif +#ifdef FIONREAD +#define HAS_FIONREAD +#endif + +#if HAVE_SYS_TYPES_H +#include <sys/types.h> +#if HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif +#endif + +#if HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#if STDC_HEADERS +#include <stdlib.h> +#include <stddef.h> +#else +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif +#endif + +#if HAVE_STRING_H +# if !STDC_HEADERS && HAVE_MEMORY_H +# include <memory.h> +# endif +# include <string.h> +# if !HAVE_STRCHR +# if !defined(strchr) +# define strchr index +# endif +# if !defined(strrchr) +# define strrchr rindex +# endif +# endif +#endif + +/* on SGI this gets us BZERO */ +#if HAVE_STRINGS_H +#include <strings.h> +#endif + +#if HAVE_INTTYPES_H +#include <inttypes.h> +#else +#if HAVE_STDINT_H +#include <stdint.h> +#endif +#endif + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif + +#if HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + +#if GWINSZ_IN_SYS_IOCTL +# include <sys/ioctl.h> +#define SUN_STYLE_WINDOW_SIZING 1 +#else +#if HAVE_TERMIOS_H +# include <termios.h> +#define SUN_STYLE_WINDOW_SIZING 1 +#endif +#endif + +#if HAVE_TERMIO_H +# include <termio.h> +#endif + +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#if !defined(HAVE_TERMIO_H) && !defined(HAVE_TERMIOS_H) +#if HAVE_SGTTY_H +#include <sgtty.h> +#endif +#endif + +#if HAVE_SYS_FILE_H +#include <sys/file.h> +#endif + +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#if HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#if HAVE_CTYPE_H +#include <ctype.h> +#endif + +#if HAVE_STDIO_H +#include <stdio.h> +#endif + +#if HAVE_ERRNO_H +#include <errno.h> +#endif + +#if HAVE_SIGNAL_H +#include <signal.h> +#endif + +#if HAVE_PWD_H +#include <pwd.h> +#endif + +#if HAVE_TERMCAP_H +#include <termcap.h> +#endif + +/* current end of autoconf stuff*/ + +#if HAVE_LIBTERMCAP +#define TERMCAP +#else +#if defined(HAVE_LIBTERMINFO) +#define TERMINFO +#endif +#endif + +#if !defined(TERMCAP) && !defined(TERMINFO) +#if defined(HAVE_LIBNCURSES) +#define TERMINFO +#endif +#endif + + +/* we prefer select over poll */ +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#define HAS_SELECT +#else +#if HAVE_POLL_H +#include <poll.h> +#define HAS_POLL +#endif +#endif + +/* + * Digital Equpment VMS Operating System + */ +#ifdef VMS +#include "tecvms.h" +#endif + +#ifndef CAUSE_BUS_ERROR +#define CAUSE_BUS_ERROR() \ + { \ + int fake_destination; \ + fake_destination = *((int *)(0x0FFFFFFF)); \ + } +#endif + +/* method declarations */ +void error_message(char * string); +void tec_release(unsigned char type, char *addr); +void tec_panic( char *string); +int buff_switch(struct buff_header *hbp,int map_flag); +int buff_read(struct buff_header *hbp,char *name); +int buff_readfd(struct buff_header *hbp,char *name,int iochan); +int buff_insert(struct buff_header *hbp,int position,char *buffer,int length); +int screen_label_line(struct buff_header *buffer,char *string,int field); +int buff_write(struct buff_header *hbp,int chan,int start,int end); +void tecmem_stats(); +void screen_free_format_lines(struct format_line *sbp); +int buff_delete_char(struct buff_header *hbp,int position); +int compile_search_string(struct search_buff *search_tbl); +int cmd_forward_search(int pos1,int pos2,struct search_buff *search_tbl); +int cmd_reverse_search(int pos1,int pos2,struct search_buff *search_tbl); +int buff_cached_contents(struct buff_header *,int,struct position_cache *); +int buff_contents(struct buff_header *hbp,int position); +void pause_while_in_input_wait(void); +void restore_tty(void); +void initialize_tty(void); +void screen_redraw(void); +void screen_resize(void); +void screen_refresh(void); +void screen_message(char *string); +int tty_input_pending(void); +void screen_format_windows(void); +void load_qname_register(void); +void buff_delete(struct buff_header *,int,int); +void screen_reformat_windows(void); +int tag_calc_hash(char *string); +void tag_free_struct(struct tags *tp); +void tecdebug_check_screen_magic(void); +void tecdebug_check_buffer_magic(void); +void tecdebug_check_line_magic(void); +void tecdebug_check_format_magic(void); +void tecdebug_check_companion_pointers(void); +void tecdebug_check_window_pointers(void); +void term_init(void); +int term_goto( int dest_x,int dest_y); +void term_clrtobot(void); +void parser_reset_echo(void); +void term_finish(void); +void term_standend(void); +void term_clrtoeol(void); +void term_standout(void); +void screen_save_current_message(char *,int); +int window_switch(int window_number); +void screen_delete_window( struct window *old_wptr); +void buff_reopenbuff(struct buff_header *bp); +void buff_free_line_buffer_list(struct buff_line *); +int screen_display_window(struct window *wptr); +int buff_find_offset(struct buff_header *,struct buff_line *,int); +int buff_openbuffnum(int,int); +int parse_illegal_buffer_position(int,int,char *); +int buff_insert_char(struct buff_header *,int,char); +int push_qregister(char); +void buff_bulk_insert(struct buff_header *,int,int,struct buff_line *); +void buff_destroy(struct buff_header *); +int cmd_wordmove(int count); +int cmd_search(int,int,struct search_buff *); +int buff_openbuffer(char *,int,int); +int cmd_write(struct buff_header *,char *); +void screen_scroll(int); +void parser_dump_command_line(struct buff_header *); +int parser_replace_command_line(struct buff_header *); +void tec_gc_lists(void); +void screen_deallocate_format_lookaside_list(); +void buff_deallocate_line_buffer_lookaside_list(); +int buff_init(); +void tec_error(int, char *); +int handle_command_line(int,int,char **); +void tecparse(); +//void term_putc(char); +int term_putc(int); +void term_flush(); +int screen_init(); +void screen_finish(); +int exec_doq0(); +void punt(int); +void screen_reset_message(); +int tecparse_syntax(int); +void screen_echo(char); +void cmd_pause(); +void cmd_suspend(); +int term_putnum(char *,int,int); +int term_scroll_region(int,int); +int init_term_description(); +//int tgetent(char *,char *); +//int tgetnum(char *); +char *tec_alloc(int,int); +void initialize_memory_stats( void ); +struct buff_header *buff_qfind(char,char); +char *error_text(int); +struct buff_line *buff_find_line(struct buff_header *hbp,int); +void term_insert_line(int,int); +void term_delete_line(int,int); +struct buff_header *buff_find(char *); +struct buff_header *buff_duplicate(struct buff_header *); +struct window *screen_split_window(struct window *,int,int); diff --git a/tecparse.c b/tecparse.c new file mode 100644 index 0000000..9682bf0 --- /dev/null +++ b/tecparse.c @@ -0,0 +1,2178 @@ +char *tecparse_c_version = "tecparse.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecparse.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecparse.c + * Subroutines to implement the finite state parser + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + struct cmd_token *cmd_list = NULL; + struct cmd_token *last_cmd_list = NULL; + struct cmd_token *jump_to_token; + struct cmd_token *resume_execute_ct; + struct cmd_token *last_token_executed; + struct search_buff search_string; + char user_message[PARSER_STRING_MAX]; + int remembered_dot; + char immediate_execute_flag = YES; + char trace_mode_flag = NO; + + extern int tty_input_chan; + extern int errno; + extern char exit_flag; + extern char intr_flag; + extern char susp_flag; + extern char input_pending_flag; + extern char alternate_escape_character; + extern char main_delete_character; + extern char alternate_delete_character; + extern char checkpoint_flag; + extern char checkpoint_enabled; + extern char waiting_for_input_flag; + extern char resize_flag; + extern char ring_audible_bell; + extern char suspend_is_okay_flag; + extern struct buff_header *curbuf,*buffer_headers; + extern int term_speed; + +/* + * Forward declarations + */ + struct cmd_token *parse_rubout_character(struct cmd_token *,int); + struct cmd_token *parse_rubout_cmd_token(struct cmd_token *); + void preserve_rubout_char(char); + void parser_clean_preserve_list(void); + int parser_getc(void); + int unpreserve_rubout_char(struct cmd_token *); + + + +/* TECPARSE - Main entry point of the parser + * + * Function: + * + * This routine reads input characters and builds the token + * list. It also calls the execute states as long as the go + * flag is set. When it hits the final parse state, it then + * executes any states which were not immediately executed. + */ +void +tecparse() +{ +register struct cmd_token *ct; +int status; +int c; +static char no_mem = 0; + + PREAMBLE(); + +/* + * We start our command token list with a special token that simply means that + * this is the begining of the list. We also set the current state to be the + * initial state of the parser. + */ + resume_execute_ct = NULL; + ct = allocate_cmd_token((struct cmd_token *)NULL); + if(ct != NULL){ + no_mem = 0; + ct->opcode = TOK_C_FIRSTTOKEN; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->ctx.go_flag = YES; + if(immediate_execute_flag == NO) ct->ctx.go_flag = NO; + cmd_list = ct; + screen_reset_echo(cmd_list); +/* + * Now we loop until we reach a final state, or something horrible happens. + */ + while(1){ + c = parser_getc(); + status = tecparse_syntax(c); + if(status == FAIL) break; + }/* End While */ + + screen_format_windows(); + + }/* End IF */ + + else { + if(no_mem){ + tec_panic("no memory available"); + }/* End IF */ + no_mem = 1; + error_message("<no memory>"); + }/* End Else */ + + screen_refresh(); + +/* + * Here to clean up old parse lists. Note that we always clean up the list + * on last_cmd_list, and then move the current command list over there. This + * is so that we always remember one command string back incase the user uses + * the '*' command to save it all to a q-register. Besides unchaining command + * blocks, the cleanup routine follows down undo lists and macro lists to + * reclaim all the memory they are using. + */ + parser_cleanup_ctlist(last_cmd_list); + last_cmd_list = cmd_list; + cmd_list = NULL; + +/* + * Also clean up the rubout Q-register. + */ + parser_clean_preserve_list(); + +/* + * Also, clean up any lookaside lists + */ + tec_gc_lists(); + +}/* End Routine */ + + + +/* TECPARSE_SYNTAX - Here to manipulate the tree according to new input + * + * Function: + * + * This routine is called with an input byte to manipulate the + * parser tree and execute any associated code. + */ +int +tecparse_syntax(c) +int c; +{ +register struct cmd_token *ct; +struct cmd_token trace_ct; +register struct cmd_token *final_token,*last_token; +int status; +char token_used,token_marked; +char old_modified_state; +struct buff_header *old_curbuf; +struct undo_token *ut; + + PREAMBLE(); + +/* + * Find the end of the current command list. + */ + ct = cmd_list; + while(ct->next_token){ + ct = ct->next_token; + }/* End While */ +/* + * parse_special_character tests whether this character is an immediate + * typing control character which should not be sent to the state machine but + * handled locally. This is how we handle things like rubout, rubout word, + * and Control-U. + */ + if(parse_special_character(ct,c)){ + return(1); + }/* End IF */ +/* + * We only echo the character if is is not a special character + */ + screen_echo(c); + if(input_pending_flag == NO){ + screen_format_windows(); + screen_refresh(); + }/* End IF */ +/* + * We set the token_used flag to indicate that we have not used the input + * byte yet. + */ + token_used = token_marked = NO; +/* + * Now we loop until we use the input byte, or reach a terminating state + */ + while(1){ +/* + * If the last state transition used the input byte, we return to our + * caller. + */ + if(token_used == YES){ + return(1); + }/* End IF */ +/* + * Allocate a command token to hold the next state transition in. The routine + * will automatically link it onto the end of the list. + */ + ct = allocate_cmd_token(ct); + if(ct == NULL){ + error_message("<out of memory>"); + return(1); + }/* End IF */ +/* + * Here we check for the special RETURN state. Although all this could be done + * in the state machine itself, this was a good place to consolidate things. + * We pop the state machine stack a level so as to return to the calling state. + * Note that iarg1 and iarg2 are copied from the calling state, so that they + * are preserved. This means that the substate must return it's value through + * tmpval which is not preserved. + */ + if(ct->ctx.state == STATE_C_RETURN){ + ct->ctx.state = ct->ctx.return_state; + ct->ctx.return_state = ct->ctx.caller_token->ctx.return_state; + ct->ctx.iarg1_flag = ct->ctx.caller_token->ctx.iarg1_flag; + ct->ctx.iarg2_flag = ct->ctx.caller_token->ctx.iarg2_flag; + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + ct->ctx.caller_token = ct->ctx.caller_token->ctx.caller_token; + }/* End IF */ +/* + * Set up the initial state of the command token. The COPYTOKEN opcode doesn't + * really mean anything except that the opcode hasn't really been set, so it's + * more for debugging purposes than anything else. Notice that since we are + * actually placing an input byte in input_byte, we set the default that the + * state will indeed eat the byte. If this is not the case, it is up to the + * state to clear the flag. + */ + ct->opcode = TOK_C_COPYTOKEN; + ct->flags |= TOK_M_EAT_TOKEN; + ct->input_byte = c; +/* + * If this is the first time through here for this input character, + * mark this is the input character token so that rubout works correctly. + */ + if(token_marked == NO){ + ct->opcode = TOK_C_INPUTCHAR; + token_marked = YES; + }/* End IF */ +/* + * Call the state machine to process this character. Although more command + * tokens may be added onto the list by the state machine, we leave ct where + * it is for the moment so that we can work forward through all the generated + * states. + */ + parse_input_character(ct,ct); +/* + * If the state machine indicates that the input character was eaten, remember + * this fact. + */ + if(ct->flags & TOK_M_EAT_TOKEN) token_used = YES; +/* + * If this character would cause us to transition into an error state, we + * simply don't allow it. We generate a rubout sequence to remove the offending + * character and allow the user to make a decision about what to do next. + */ + if(ct->ctx.state == STATE_C_ERRORSTATE){ + token_used = YES; + while(ct->next_token) ct = ct->next_token; + ct = parse_rubout_character(ct,0); + screen_reset_echo(cmd_list); + return(1); + }/* End IF*/ + +/* + * If ? debug mode is on, record new syntax tokens in the ? buffer + */ + if(trace_mode_flag == YES){ + last_token = ct; + while(last_token){ + trace_mode(TRACE_C_PARSE,last_token,0); + last_token = last_token->next_token; + }/* End While */ + }/* End IF */ + +/* + * Ok, the state transition seems to have gone smoothly, if the go flag is set + * we are in immediate execute mode and can perform the execution phase of the + * state transition. + */ + if(resume_execute_ct != NULL){ + ct = resume_execute_ct; + ct->ctx.go_flag = YES; + resume_execute_ct = NULL; + } + +/* + * We need a place to chain off undo tokens. If we use the current token, + * loops will distribute undo tokens all over the parse tree, with no way + * to tell which order they were created in. So we find the last token, and + * chain everything off of that. That way, if a rubout is hit, all the undo + * tokens will be undone in order back to the last keystroke token. As the + * user types more keystrokes, we keep finding the new end of the command + * chain, and chaining undo tokens off of that. Thus order will be preserved. + */ + final_token = ct; + while (final_token->next_token) final_token = final_token->next_token; + last_token = ct->prev_token; + + while(ct){ +/* + * We still want to copy tmpvals forward from state to state unless the + * parse stage is attempting a store. + */ + if(last_token){ +/* + * Carry the argument values forward to this state. If the last state was a + * RETURN state, then only copy out the tmpval, and fetch the callers iargs. + */ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = last_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = last_token->ctx.iarg2; + }/* End IF */ + + if(ct->ctx.state == STATE_C_RETURN){ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * If the next state is STOREVAL, the syntax stage is setting storeval. The + * typical example of this is when the syntax stage sees a constant like '1'. + * It uses this state to set the temporary value to 1. + */ + if( + ct->execute_state != EXEC_C_STOREVAL && + ((ct->flags & TOK_M_PARSELOAD_TMPVAL) == 0) + ){ + ct->ctx.tmpval = last_token->ctx.tmpval; + }/* End IF */ + + }/* End IF */ + + if(ct->execute_state){ +/* + * This is sort of a kludge, but it's an easy place to see if this + * state transition causes the buffer to become modified. If so, we + * link on an undo token to that if this gets undone, the modified + * status gets put back. If we didn't do it here, we would have to + * either do it in every command, and that would get tedious. + */ + old_modified_state = curbuf->ismodified; + old_curbuf = curbuf; + + if(trace_mode_flag == YES){ + trace_ct = *ct; + }/* End IF */ + + status = execute_a_state(ct,final_token); + if(status == BLOCKED){ + resume_execute_ct = ct; + } +/* + * An INVALIDATE status occurs when the execute engine changes the + * command token list in such a way that we may no longer be pointing + * at something valid. So we back out, and re-enter.. + */ + if(status == INVALIDATE){ + return(SUCCESS); + } + + if(trace_mode_flag == YES){ + trace_mode(TRACE_C_EXEC,&trace_ct,ct); + }/* End IF */ +/* + * If the modified state went from unmodified to modified, link on an + * undo token incase this gets undone - then the modified status will + * get put back to its original state. + */ + if(old_modified_state == NO && old_curbuf->ismodified == YES){ + if(ct->undo_list){ + ut = ct->undo_list; + while(ut->next_token) ut = ut->next_token; + ut->next_token = allocate_undo_token(NULL); + ut = ut->next_token; + }/* End IF */ + + else ct->undo_list = ut = allocate_undo_token(NULL); + if(ut != NULL){ + ut->opcode = UNDO_C_MODIFIED; + ut->carg1 = (char *)old_curbuf; + }/* End IF */ + }/* End IF */ + + if(susp_flag) cmd_pause(); +/* + * Since we are in immediate execute mode, if we get an error during the + * execution phase, we know it was generated by the most recently input + * character. Therefore, we can handle it the same way we do during the syntax + * parsing, by pretending a rubout character was input. + */ + if(status == BLOCKED) break; + if(intr_flag) break; + + if(status != SUCCESS){ + token_used = YES; + while(ct->next_token) ct = ct->next_token; + ct = parse_rubout_character(ct,0); + screen_reset_echo(cmd_list); + break; + }/* End IF */ + }/* End IF */ + + last_token = ct; + + if(jump_to_token){ + ct = jump_to_token; + jump_to_token = NULL; + continue; + } + + if(ct->next_token) ct = ct->next_token; + else break; + }/* End While */ +/* + * Now regardless of whether go_flag is set, chain ct forward to the end of + * the token list. + */ + while(ct->next_token) ct = ct->next_token; +/* + * If we hit FINALSTATE, this is the way the state machine has of telling us + * to reset to the begining parser state. We remember the final token address + * so that all additional undo tokens can be chained off of it. + */ + if(ct->ctx.state == STATE_C_FINALSTATE){ + return(0); + }/* End IF */ + + }/* End While */ + +}/* End Routine */ + + + +/* TECMACRO - Here to execute a Q register as a macro + * + * Function: + * + * This routine executes the specified q register as a macro. It returns + * SUCCESS or FAILURE depending on what happens within the macro. Note + * that it is very similar to the main parse routine. There are two major + * differences: First, the characters are gotton from the q-register + * rather than the input stream, and second, the entire parse is done + * to completion before any execution takes place. This means that the + * macro can modify the contents of the q-register without changing the + * way the macro will execute. + */ +int +tecmacro(qbp,input_ct,macro_cmd_list) +register struct buff_header *qbp; +struct cmd_token *input_ct; +struct cmd_token **macro_cmd_list; +{ +register struct cmd_token *ct; +struct cmd_token trace_ct; +register struct cmd_token *last_token; +int c = 0; +int status; +char token_used; +int i; +char old_modified_state; +struct buff_header *old_curbuf; +struct undo_token *ut; + + PREAMBLE(); + +/* + * We start our command token list with a special token that simply means that + * this is the begining of the list. We also set the current state to be the + * initial state of the parser. + */ + ct = allocate_cmd_token((struct cmd_token *)NULL); + if(ct == NULL) return(FAIL); + + ct->opcode = TOK_C_FIRSTTOKEN; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->ctx.go_flag = NO; +/* + * By setting STATUS_PASSED, we insure that the first command in the macro will + * get any arguments we have been passed. + */ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + ct->ctx.iarg1_flag = input_ct->ctx.iarg1_flag; + ct->ctx.iarg2_flag = input_ct->ctx.iarg2_flag; + ct->ctx.iarg1 = input_ct->ctx.iarg1; + ct->ctx.iarg2 = input_ct->ctx.iarg2; + *macro_cmd_list = ct; +/* + * We set the token_used flag so that we will call the getc routine. This flag + * in general lets us remember whether or not a state transistion ate an input + * character or not. + */ + token_used = YES; +/* + * Now we loop until we reach a final state, or something horrible happens. + */ + i = 0; + + while(i <= qbp->zee){ +/* + * Allocate a command token to hold the next state transition in. The routine + * will automatically link it onto the end of the list. + */ + ct = allocate_cmd_token(ct); + if(ct == NULL) return(FAIL); +/* + * Here we check for the special RETURN state. Although all this could be done + * in the state machine itself, this was a good place to consolidate things. + * We pop the state machine stack a level so as to return to the calling state. + * Note that iarg1 and iarg2 are copied from the calling state, so that they + * are preserved. This means that the substate must return it's value through + * tmpval which is not preserved. + */ + if(ct->ctx.state == STATE_C_RETURN){ + ct->ctx.state = ct->ctx.return_state; + ct->ctx.return_state = ct->ctx.caller_token->ctx.return_state; + ct->ctx.iarg1_flag = ct->ctx.caller_token->ctx.iarg1_flag; + ct->ctx.iarg2_flag = ct->ctx.caller_token->ctx.iarg2_flag; + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + ct->ctx.caller_token = ct->ctx.caller_token->ctx.caller_token; + }/* End IF */ +/* + * Set up the comand token a little + */ + ct->opcode = TOK_C_COPYTOKEN; + ct->flags |= TOK_M_EAT_TOKEN; + ct->input_byte = c; +/* + * If the last state ate the input character, we need to fetch another from the + * command source. + */ + if(token_used == YES){ + if(i == qbp->zee) c = ESCAPE; + else c = buff_contents(qbp,i++); +#ifdef NEEDED_WHICH_I_DONT_THINK_IT_IS + if(parse_special_character(ct,c)){ + ct = *macro_cmd_list; + while(ct->next_token) ct = ct->next_token; + continue; + }/* End IF */ +#endif + ct->opcode = TOK_C_INPUTCHAR; + ct->input_byte = c; + }/* End IF */ + + token_used = NO; +/* + * Call the state machine to process this character. Although more command + * tokens may be added onto the list by the state machine, we leave ct where + * it is for the moment so that we can work forward through all the generated + * states. + */ + parse_input_character(ct,*macro_cmd_list); +/* + * If the state machine indicates that the input character was eaten, remember + * this fact. + */ + if(ct->flags & TOK_M_EAT_TOKEN) token_used = YES; +/* + * If this character would cause us to transition into an error state, we + * terminate the macro and indicate where the error was. + */ + if(ct->ctx.state == STATE_C_ERRORSTATE){ + token_used = YES; + while(ct->next_token) ct = ct->next_token; + while(ct->opcode != TOK_C_FIRSTTOKEN){ + ct = parse_rubout_character(ct,0); + }/* End While */ + return(FAIL); + }/* End IF*/ +/* + * Always chain ct forward over any generated tokens + */ + while(ct->next_token) ct = ct->next_token; +/* + * If we hit FINALSTATE, this is the way the state machine has of telling us + * to reset to the begining parser state. + */ + if(ct->ctx.state == STATE_C_FINALSTATE) break; + + }/* End While */ + +/* + * Now that the syntax parsing has been completed, we start execution of the + * macro. + */ + ct = last_token = *macro_cmd_list; + + while(ct){ + +/* + * If we hit the final state, we don't need to go any further. + */ + if(ct->ctx.state == STATE_C_FINALSTATE) break; + +/* + * Carry the argument values forward to this state. If the last state was a + * RETURN state, then only copy out the tmpval, and fetch the callers iargs. + */ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = last_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = last_token->ctx.iarg2; + }/* End IF */ + + if(ct->ctx.state == STATE_C_RETURN){ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * If the next state is STOREVAL, the syntax stage is setting storeval. The + * typical example of this is when the syntax stage sees a constant like '1'. + * It uses this state to set the temporary value to 1. + */ + if( + ct->execute_state != EXEC_C_STOREVAL && + ((ct->flags & TOK_M_PARSELOAD_TMPVAL) == 0) + ){ + ct->ctx.tmpval = last_token->ctx.tmpval; + }/* End IF */ + +/* + * If this token has an execute state, call the execute phase to implement it. + */ + if(trace_mode_flag == YES){ + trace_mode(TRACE_C_MACRO,ct,0); + }/* End IF */ + + if(ct->execute_state){ + + old_modified_state = curbuf->ismodified; + old_curbuf = curbuf; + + if(trace_mode_flag == YES){ + trace_ct = *ct; + }/* End IF */ + + status = execute_a_state(ct,*macro_cmd_list); + + if(trace_mode_flag == YES){ + trace_mode(TRACE_C_EXEC,&trace_ct,ct); + }/* End IF */ + + if(old_modified_state == NO && old_curbuf->ismodified == YES){ + if(ct->undo_list){ + ut = ct->undo_list; + while(ut->next_token) ut = ut->next_token; + ut->next_token = allocate_undo_token(NULL); + ut = ut->next_token; + }/* End IF */ + + else ct->undo_list = ut = allocate_undo_token(NULL); + if(ut != NULL){ + ut->opcode = UNDO_C_MODIFIED; + ut->carg1 = (char *)old_curbuf; + }/* End IF */ + }/* End IF */ + + if(status != SUCCESS) goto cleanup; + if(exit_flag == YES) goto cleanup; + if(intr_flag) goto cleanup; + if(susp_flag) cmd_pause(); + }/* End IF */ +/* + * Ok, now chain to the next token, or quit if done + */ + last_token = ct; + ct = ct->next_token; + if(jump_to_token){ + ct = jump_to_token; + last_token = ct; + jump_to_token = NULL; + }/* End IF */ + }/* End While */ + +cleanup: + + if(input_ct->ctx.flags & CTOK_M_STATUS_PASSED){ + input_ct->ctx.iarg1_flag = last_token->ctx.iarg1_flag; + input_ct->ctx.iarg1 = last_token->ctx.iarg1; + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* PARSE_SPECIAL_CHARACTER - Test for and handle special characters + * + * Function: + * + * This routine is called on each input character to determine whether + * it requires special handling. It catches characters such as rubout, + * ^W, ^U. + */ +int +parse_special_character(ct,c) +register struct cmd_token *ct; +register int c; +{ +char state_seen; +int tmp; + + PREAMBLE(); + + switch(c){ + case RUBOUT: + ct = parse_rubout_character(ct,1); + break; + case CNTRL_U: +/* + * Get rid of any <CR> right at the deletion point + */ + if(ct->opcode == TOK_C_INPUTCHAR){ + ct = parse_rubout_character(ct,1); + }/* End IF */ + + while(ct != cmd_list){ + if(ct->opcode == TOK_C_INPUTCHAR && ct->input_byte == '\n'){ + break; + }/* End IF */ + ct = parse_rubout_character(ct,1); + }/* End While */ + break; + case CNTRL_W: + state_seen = 0; + tmp = 0; + + while(ct != cmd_list){ + if(ct->flags & TOK_M_WORDBOUNDARY){ + state_seen = 1; + break; + }/* End IF */ + + if(ct->opcode == TOK_C_INPUTCHAR){ + tmp += 1; + if(tmp == 1 && ct->input_byte == '\n'){ + preserve_rubout_char(ct->input_byte); + ct = parse_rubout_cmd_token(ct); + break; + }/* End IF */ + + if(ct->input_byte != ' ' && ct->input_byte != '\t') break; + preserve_rubout_char(ct->input_byte); + + }/* End IF */ + + ct = parse_rubout_cmd_token(ct); + + }/* End While */ + + while(ct != cmd_list){ + if(ct->flags & TOK_M_WORDBOUNDARY) state_seen = 1; + + if(ct->opcode == TOK_C_INPUTCHAR){ + if(state_seen){ + preserve_rubout_char(ct->input_byte); + ct = parse_rubout_cmd_token(ct); + break; + }/* End IF */ + + if(isspace((int)ct->input_byte)) break; + preserve_rubout_char(ct->input_byte); + + }/* End IF */ + + ct = parse_rubout_cmd_token(ct); + + }/* End While */ + + break; + + case CNTRL_R: + if(!unpreserve_rubout_char(ct)){ + error_message("^R no input tokens available"); + }/* End IF */ + return(1); + + default: + return(0); + }/* End Switch */ + + screen_reset_echo(cmd_list); + return(1); + +}/* End Routine */ + + + +/* PARSE_RUBOUT_CHARACTER - Rubout the most recent character + * + * Function: + * + * This routine is called when a rubout character is typed. We have to + * back the parser up to the state it was in before the original char + * was typed, as well as performing any undo functions to make sure + * that the edit buffer also gets backed up. + */ +struct cmd_token * +parse_rubout_character(ct,preserve_flag) +register struct cmd_token *ct; +int preserve_flag; +{ +register struct cmd_token *oct; +struct undo_token *ut; +char saved_opcode; + + PREAMBLE(); + + while(1){ + +/* + * If the preserve flag is on, we want to save any deleted bytes in the + * q-register that is used for this purpose. That way, the user can + * correct for mistakenly typing the wrong rubout code. + */ + if(preserve_flag && ct->opcode == TOK_C_INPUTCHAR){ + preserve_rubout_char(ct->input_byte); + }/* End IF */ + +/* + * For each of the command tokens, we need to undo all the chained undo + * tokens. + */ + while( (ut = ct->undo_list) != NULL ){ +/* + * Take the top undo token off of the list and change the listhead to point + * to it's child. + */ + ct->undo_list = ut->next_token; + ut->next_token = NULL; +/* + * Now call the undo routine to back out the changes to the edit buffer that + * this token calls for. Then place the token back on the free list. + */ + parser_undo(ut); + free_undo_token(ut); + + }/* End While */ +/* + * If this is TOK_C_FIRSTTOKEN, it means he is trying to rubout the head of the + * list. This is probably not such a hot idea... + */ + if(ct->opcode == TOK_C_FIRSTTOKEN) break; +/* + * We save the opcode so that after cleaning up the token, we will know whether + * it was an input character or not. + */ + saved_opcode = ct->opcode; + + oct = ct->prev_token; + if(oct == NULL) break; + oct->next_token = NULL; + ct->prev_token = NULL; + free_cmd_token(ct); + ct = oct; +/* + * If this was an input character, then we have backed up enough. + */ + if(saved_opcode == TOK_C_INPUTCHAR) break; + + }/* End While */ + + return(ct); + +}/* End Routine */ + + + +/* PARSE_RUBOUT_CMD_TOKEN - Rubout the most recent command token + * + * Function: + * + * This routine is called to remove the last token on the command + * list. It gets used in rubout / ^U / ^W processing. + */ +struct cmd_token * +parse_rubout_cmd_token(ct) +register struct cmd_token *ct; +{ +register struct cmd_token *oct; +struct undo_token *ut; + + PREAMBLE(); + +/* + * We need to undo all the undo tokens chained off of this command token. + */ + while( (ut = ct->undo_list) != NULL ){ +/* + * Take the top undo token off of the list and change the listhead to point + * to it's child. + */ + ct->undo_list = ut->next_token; + ut->next_token = NULL; +/* + * Now call the undo routine to back out the changes to the edit buffer that + * this token calls for. Then place the token back on the free list. + */ + parser_undo(ut); + free_undo_token(ut); + + }/* End While */ + + oct = ct->prev_token; + if(oct) oct->next_token = NULL; + ct->prev_token = NULL; + free_cmd_token(ct); + return(oct); + +}/* End Routine */ + + + +/* PARSER_GETC - Return next input character + * + * Function: + * + * This routine is called by the parser when another input byte is needed. + */ +int +parser_getc() +{ +register int i; +char inbuf[4]; + +#ifdef VMS + short qio_iosb[4]; +#endif /* VMS */ + + PREAMBLE(); + +/* + * If there are no unclaimed bytes in the buffer, we need to read one + * from the command channel. + */ + input_pending_flag = tty_input_pending(); + + if(input_pending_flag == NO){ + if(ring_audible_bell){ + term_putc(BELL); + ring_audible_bell = 0; + }/* End IF */ + screen_echo('\0'); + screen_format_windows(); + screen_refresh(); + }/* End IF */ + +#ifdef CHECKPOINT + if(checkpoint_enabled == YES && checkpoint_flag == YES){ + cmd_checkpoint(); + checkpoint_flag = NO; + }/* End IF */ +#endif /* CHECKPOINT */ + + while(1){ + +#ifdef UNIX + + if(susp_flag){ + pause_while_in_input_wait(); + continue; + }/* End IF */ + + if(resize_flag){ + screen_resize(); + }/* End IF */ + + waiting_for_input_flag = YES; + i = read(tty_input_chan,inbuf,1); + waiting_for_input_flag = NO; + + intr_flag = 0; + + if(i >= 0) break; + + if(errno == EINTR) continue; +#endif +#ifdef VMS + waiting_for_input_flag = YES; + i = sys$qiow(0,tty_input_chan,IO$_READVBLK|IO$M_NOECHO|IO$M_NOFILTR, + qio_iosb,0,0,inbuf,1,0,0,0,0); + waiting_for_input_flag = NO; + if(!(i & STS$M_SUCCESS)) exit(i); +if(qio_iosb[0] != 1) +printf("!!! iosb[0] is %d\n",qio_iosb[0]); + if(inbuf[0] == '\r') inbuf[0] = '\n'; + else if(inbuf[0] == '\n') inbuf[0] = '\r'; + if(inbuf[0] == '`') inbuf[0] = ESCAPE; + if(inbuf[0] == CNTRL_Z && suspend_is_okay_flag == YES){ + pause_while_in_input_wait(); + continue; + }/* End IF */ + + break; +#endif + perror("error reading command input"); + punt(errno); + }/* End While */ + + screen_reset_message(); + + input_pending_flag = tty_input_pending(); + + if(inbuf[0] == alternate_escape_character){ + return(ESCAPE); + }/* End IF */ + + if(inbuf[0] == main_delete_character){ + return(RUBOUT); + }/* End IF */ + + if(inbuf[0] == alternate_delete_character){ + return(RUBOUT); + }/* End IF */ + + return(inbuf[0]); + +}/* End Routine */ + + + +/* PRESERVE_RUBOUT_CHARCTER - Save rubbed out characters + * + * Function: + * + * This routine is called in response to the user rubbing out input + * characters with rubout, ^U, or ^W. Sometimes he does this by + * mistake, and can delete large amounts of typing unintentially. + * This routine saves these characters in a special Q-register so + * that he can get them back if he wants. + */ +void +preserve_rubout_char(the_byte) +char the_byte; +{ +register struct buff_header *qbp; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the characters which + * have been rubbed out. + */ + qbp = buff_qfind('@',1); + if(qbp == NULL){ + return; + }/* End IF */ + +/* + * Put the deleted byte into the Q-register where it can be retrieved by + * the user if so desired. + */ + buff_insert_char(qbp,0,the_byte); + +}/* End Routine */ + + + +/* UNPRESERVE_RUBOUT_CHAR - Poke a rubbed out char back into the parse + * + * Function: + * + * This routine is called on behalf of a ^R command which causes the + * most recent rubbed out character to be restored to the parse tree. + * The return code determines whether this was done okay or not. + */ +int +unpreserve_rubout_char(ct) +struct cmd_token *ct; +{ +register struct buff_header *qbp; +int c; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the characters which + * have been rubbed out. + */ + qbp = buff_qfind('@',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + +/* + * Make sure thre are some bytes in it + */ + if(qbp->zee <= 0) return(FAIL); + +/* + * Get the first byte saved in the Q-register + */ + c = buff_contents(qbp,0); + buff_delete(qbp,0,1); + + tecparse_syntax(c); + + return(SUCCESS); + +}/* End Routine */ + + + +/* PARSER_CLEAN_PRESERVE_LIST - Zero the rubout preserve Q-register + * + * Function: + * + * This routine is called at double-escape time to clean up the + * special Q-register so that it doesn't grow without bounds. + */ +void +parser_clean_preserve_list() +{ +register struct buff_header *qbp; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the characters which + * have been rubbed out. + */ + qbp = buff_qfind('@',1); + if(qbp == NULL){ + return; + }/* End IF */ + +/* + * Delete all the bytes in the Q-register + */ + buff_delete(qbp,0,qbp->zee); + +}/* End Routine */ + + + +/* PARSER_DUMP_COMMAND_LINE - Copy the current parse tree into a Q-register + * + * Function: + * + * This routine copies the input bytes from the current command string + * into the specified Q-register. + */ +void +parser_dump_command_line(qbp) +register struct buff_header *qbp; +{ +register struct cmd_token *ct; + + PREAMBLE(); + + ct = cmd_list; + + while(ct){ + if(ct->opcode == TOK_C_INPUTCHAR){ + buff_insert_char(qbp,qbp->zee,ct->input_byte); + }/* End IF */ + ct = ct->next_token; + }/* End While */ + +}/* End Routine */ + + + +/* PARSER_REPLACE_COMMAND_LINE - Replace current parse tree + * + * Function: + * + * This routine replaces the current command string with the contents + * the specified Q-register. + */ +int +parser_replace_command_line(qbp) +register struct buff_header *qbp; +{ +register struct cmd_token *ct; +register struct cmd_token *first_different_ct; +register int c; +char temp; +register char *command_buffer; +register int cb_zee; + + PREAMBLE(); + + cb_zee = 0; + command_buffer = tec_alloc(TYPE_C_CBUFF,qbp->zee); + if(command_buffer == NULL) return(FAIL); +/* + * Walk the parse list to find the first place there is a difference + * between the current parse list, and the one to be installed. When + * we find the first difference, remember it, and then start copying + * the changed bytes into our temporary Q-register. + */ + first_different_ct = NULL; + ct = cmd_list; + for(c = 0; c < qbp->zee - 1; c++){ + temp = buff_contents(qbp,c); + if(first_different_ct == NULL){ + while(ct && ct->opcode != TOK_C_INPUTCHAR){ + ct = ct->next_token; + }/* End While */ + if(ct && ct->input_byte != temp) first_different_ct = ct; + if(ct && ct->next_token) ct = ct->next_token; + }/* End IF */ + if(first_different_ct){ + command_buffer[cb_zee++] = temp; + }/* End IF */ + }/* End FOR */ + + if(ct != NULL && first_different_ct == NULL){ + while(ct->next_token){ + if(ct->opcode == TOK_C_INPUTCHAR) break; + ct = ct->next_token; + } + first_different_ct = ct; + } +/* + * We only have to do the following if there were any changed bytes at + * all. If he changed his mind and didn't modify the command list at all, + * nothing really has to happen at all. + */ + if(first_different_ct){ + while(first_different_ct->next_token != NULL && + first_different_ct->next_token->opcode != TOK_C_INPUTCHAR){ + first_different_ct = first_different_ct->next_token; + } + ct = cmd_list; + while(ct->next_token) ct = ct->next_token; +/* + * Remove from the tail of the parse list all the bytes up to and including + * the first different character. This leaves us with a parse list which is + * just the part that has not changed. + */ + temp = 1; + while(ct != cmd_list && temp != 0){ + if(ct == first_different_ct) temp = 0; + ct = parse_rubout_character(ct,0); + }/* End While */ + +/* + * Now we walk through the Q-register, adding these bytes to the end of the + * parse list. These are the bytes which are different from the original + * parse list. + */ + qbp->pos_cache.lbp = NULL; + input_pending_flag = YES; + for(c = 0; c < cb_zee; c++){ + temp = command_buffer[c]; + tecparse_syntax(temp); + }/* End While */ + }/* End IF */ + tec_release(TYPE_C_CBUFF,command_buffer); + parser_reset_echo(); + + return(SUCCESS); + +}/* End Routine */ + + + +/* ALLOCATE_CMD_TOKEN - Allocate a command token structure + * + * Function: + * + * This routine is called to allocate a command token structure. If there + * is one on the free list, it is used, otherwise we allocate one. If + * an old_token was specified, the context area is copied into the new + * token, which has the effect of rippling the context forward through + * the parse. + */ +struct cmd_token * +allocate_cmd_token(old_token) +register struct cmd_token *old_token; +{ +register struct cmd_token *ct; + + PREAMBLE(); + +/* + * Call the memory allocator for a command token block + */ + ct = (struct cmd_token *)tec_alloc(TYPE_C_CMD,sizeof(struct cmd_token)); + if(ct == NULL) return(NULL); + +/* + * We set up the initial state to be that of an empty token, with fields + * which are not set up yet set to their proper defaults. + */ + ct->opcode = TOK_C_UNUSED; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->ctx.iarg1 = ct->ctx.iarg2 = 0; + ct->ctx.carg = NULL; + ct->next_token = NULL; + ct->undo_list = NULL; + ct->execute_state = 0; + ct->flags = 0; + + ct->prev_token = NULL; + ct->ctx.state = 0; + ct->ctx.go_flag = NO; + ct->ctx.return_state = 0; + ct->ctx.caller_token = NULL; + ct->ctx.pnest = ct->ctx.inest = ct->ctx.cnest = 0; +/* + * If there was an old token specified, two things must happen. First, we + * have to link the new token onto the old token list. Second, we have to + * copy the context area from the old token into the new one. + */ + if(old_token){ + old_token->next_token = ct; + ct->prev_token = old_token; + ct->ctx = old_token->ctx; + }/* End IF */ + + return(ct); + +}/* End Routine */ + + + +/* FREE_CMD_TOKEN - Routine to place a cmd token on the free list + * + * Function: + * + * This routine is called with the address of the command token + * to be placed on the free list. + */ +void +free_cmd_token(ct) +register struct cmd_token *ct; +{ + + PREAMBLE(); + + tec_release(TYPE_C_CMD,(char *)ct); + +}/* End Routine */ + +/* PAUSE_WHILE_IN_INPUT_WAIT - Pauses with the terminal in a good state + * + * Function: + * + * This routine is called to pause the editor while we have been in + * an input wait state. The big deal here is that we want to remove + * the reverse video box that may be on the echo line before we pause + * back to the system command processor. + */ +void +pause_while_in_input_wait() +{ + + PREAMBLE(); + + screen_reset_echo(cmd_list); + screen_refresh(); + cmd_pause(); + screen_echo('\0'); + screen_refresh(); + +}/* End Routine */ + + + +/* PARSER_CLEANUP_CTLIST - Routine to deallocate all the blocks on a ct list + * + * Function: + * + * This function deallocates all the blocks on a command token list, + * including cmd_tokens and undo_tokens. + */ +void +parser_cleanup_ctlist(ct) +register struct cmd_token *ct; +{ +register struct cmd_token *oct; + + PREAMBLE(); + +/* + * For each command token, if it has a list of undo tokens connected we + * call the routine which knows how to clean them up. + */ + while(ct){ + if(ct->undo_list){ + tecundo_cleanup(ct->undo_list); + }/* End IF */ + + ct->undo_list = NULL; +/* + * Now we remember what the next token in the list is, and then call the + * routine to free the current one. + */ + oct = ct; + ct = ct->next_token; + free_cmd_token(oct); + + }/* End While */ + +}/* End Routine */ + + + +/* PARSE_ANY_ARGUMENTS - Called by routines that don't want any arguments + * + * Function: + * + * This function is called by states which don't want to accept any + * arguments. If there are any present, it will generate an error message + * and set the error state. + */ +int +parse_any_arguments(ct,cmd_name) +register struct cmd_token *ct; +char *cmd_name; +{ +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + +/* + * If either of the iarg flags is set, that means we received an argument. + */ + if(ct->ctx.iarg1_flag == YES || ct->ctx.iarg2_flag == YES){ + (void) strcpy(tmp_message,"?The "); + (void) strcat(tmp_message,cmd_name); + (void) strcat(tmp_message," command accepts no arguments"); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return(1); + }/* End IF */ + + return(0); + +}/* End Routine */ + +/* PARSE_MORE_THAN_ONE_ARG - Called by routines that only want one arg + * + * Function: + * + * This function is called by states which only want to accept one + * argument. If there are two present, it will generate an error message + * and set the error state. + */ +int +parse_more_than_one_arg(ct,cmd_name) +register struct cmd_token *ct; +char *cmd_name; +{ +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + +/* + * If the iarg2 flag is set, that means that we received two arguments when + * we really only want one. + */ + if(ct->ctx.iarg2_flag == YES){ + (void) strcpy(tmp_message,"?Two Arguments to "); + (void) strcat(tmp_message,cmd_name); + (void) strcat(tmp_message," not allowed"); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return(1); + }/* End IF */ + + return(0); + +}/* End Routine */ + + + +/* PARSE_ILLEGAL_BUFFER_POSITION - Check for illegal buffer positions + * + * Function: + * + * This routine is called to verify that buffer positions specified are + * legal. If they are not, it generates an error message. + */ +int +parse_illegal_buffer_position(pos1,pos2,cmd_name) +register int pos1; +register int pos2; +char *cmd_name; +{ +char illegal_position; +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + + illegal_position = 0; + if(pos1 < 0) illegal_position = 1; + if(pos2 < 0) illegal_position = 1; + if(pos1 > curbuf->zee) illegal_position = 1; + if(pos2 > curbuf->zee) illegal_position = 1; + + if(illegal_position == 0) return(0); + + (void) strcpy(tmp_message,"?Attempt to Move Pointer Off Page with "); + (void) strcat(tmp_message,cmd_name); + error_message(tmp_message); + return(1); + +}/* End Routine */ + + + +/* PARSER_RESET_ECHO - Re-echo the input line + * + * Function: + * + * This is just an entry point to restore the echo line from a module + * that doesn't want to know about the cmd_list structure. + */ +void +parser_reset_echo() +{ + PREAMBLE(); + + screen_reset_echo(cmd_list); + +}/* End Routine */ + + + +/* TRACE_MODE - Trace execution of commands + * + * Function: + * + * This routine is called at parse and execute time if tracing has been + * enabled by the ? command. It causes q-register ? to be filled with + * execution trace information. + */ +void +trace_mode(phase,ct0,ct1) +register int phase; +struct cmd_token *ct0; +struct cmd_token *ct1; +{ +register struct buff_header *qbp; +char tmp_message[LINE_BUFFER_SIZE]; +register char *cp; +register char *state_name; +char *trace_convert_state_to_name(); +char *trace_convert_opcode_to_name(); +char *trace_convert_exec_state_to_name(); + + PREAMBLE(); + + qbp = buff_qfind('?',1); + if(qbp == NULL) return; + + switch(phase){ + case TRACE_C_PARSE: + state_name = trace_convert_opcode_to_name(ct0->opcode); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message,"PARSE: 0x%08X Opcode %s %c", + (unsigned int)ct0, + state_name, +/* ct0->opcode == TOK_C_INPUTCHAR ? ct0->input_byte : ' '); */ + ct0->flags & TOK_M_EAT_TOKEN ? ct0->input_byte : ' '); + if(ct0->flags & TOK_M_EAT_TOKEN){ + strcat(tmp_message," TOK_M_EAT_TOKEN"); + }/* End IF */ + if(ct0->flags & TOK_M_WORDBOUNDARY){ + strcat(tmp_message," TOK_M_WORDBOUNDARY"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG1){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG1"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG2){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG2"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_TMPVAL){ + strcat(tmp_message," TOK_M_PARSELOAD_TMPVAL"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + +/* int execute_state; */ + state_name = trace_convert_state_to_name(ct0->ctx.state); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message," Parser State: %s '%c'", + state_name,isprint((int)ct0->q_register) ? + ct0->q_register : ' '); + if(ct0->ctx.flags & CTOK_M_COLON_SEEN){ + strcat(tmp_message," CTOK_M_COLON_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_ATSIGN_SEEN){ + strcat(tmp_message," CTOK_M_ATSIGN_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_STATUS_PASSED){ + strcat(tmp_message," CTOK_M_STATUS_PASSED"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.go_flag == 0){ + sprintf(tmp_message," Go flag clear\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + if(ct0->ctx.pnest || ct0->ctx.inest || ct0->ctx.cnest){ + sprintf(tmp_message," PNEST: %d INEST %d CNEST %d\n", + ct0->ctx.pnest,ct0->ctx.inest,ct0->ctx.cnest); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + sprintf(tmp_message," iarg1_flag %d iarg1 %d (0x%x)\n", + ct0->ctx.iarg1_flag,ct0->ctx.iarg1,ct0->ctx.iarg1); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg2_flag %d iarg2 %d (0x%x)\n", + ct0->ctx.iarg2_flag,ct0->ctx.iarg2,ct0->ctx.iarg2); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," carg 0x%x\n",(unsigned int)ct0->ctx.carg); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," tmpval %d (0x%x)\n", + ct0->ctx.tmpval,ct0->ctx.tmpval); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.return_state || ct0->ctx.caller_token){ + state_name = ""; + if(ct0->ctx.return_state){ + state_name = + trace_convert_state_to_name(ct0->ctx.return_state); + if(state_name == NULL) state_name = "UNKNOWN"; + }/* End IF */ + sprintf(tmp_message," return_state %s caller_token 0x%x\n", + state_name,(unsigned int)ct0->ctx.caller_token); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + break; + + case TRACE_C_EXEC: + state_name = trace_convert_exec_state_to_name(ct0->execute_state); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message,"EXEC: 0x%08X Exec State: %s\n", + (unsigned int)ct1,state_name); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg1_flag %d iarg1 %d (0x%x)", + ct0->ctx.iarg1_flag,ct0->ctx.iarg1,ct0->ctx.iarg1); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," iarg1_flag %d iarg1 %d (0x%x)\n", + ct1->ctx.iarg1_flag,ct1->ctx.iarg1,ct1->ctx.iarg1); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg2_flag %d iarg2 %d (0x%x)", + ct0->ctx.iarg2_flag,ct0->ctx.iarg2,ct0->ctx.iarg2); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," iarg2_flag %d iarg2 %d (0x%x)\n", + ct1->ctx.iarg2_flag,ct1->ctx.iarg2,ct1->ctx.iarg2); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," carg 0x%x",(unsigned int)ct0->ctx.carg); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," carg 0x%x\n",(unsigned int)ct1->ctx.carg); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," tmpval %d (0x%x)", + ct0->ctx.tmpval,ct0->ctx.tmpval); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," tmpval %d (0x%x)\n", + ct1->ctx.tmpval,ct1->ctx.tmpval); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + break; + + case TRACE_C_MACRO: + state_name = trace_convert_opcode_to_name(ct0->opcode); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message,"MACRO PLACEHOLDER: 0x%08X Opcode %s %c", + (unsigned int)ct0, + state_name, +/* ct0->opcode == TOK_C_INPUTCHAR ? ct0->input_byte : ' '); */ + ct0->flags & TOK_M_EAT_TOKEN ? ct0->input_byte : ' '); + if(ct0->flags & TOK_M_EAT_TOKEN){ + strcat(tmp_message," TOK_M_EAT_TOKEN"); + }/* End IF */ + if(ct0->flags & TOK_M_WORDBOUNDARY){ + strcat(tmp_message," TOK_M_WORDBOUNDARY"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG1){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG1"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG2){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG2"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_TMPVAL){ + strcat(tmp_message," TOK_M_PARSELOAD_TMPVAL"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + +/* int execute_state; */ + state_name = trace_convert_state_to_name(ct0->ctx.state); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message," Parser State: %s '%c'", + state_name, + isprint((int)ct0->q_register) ? ct0->q_register : ' '); + if(ct0->ctx.flags & CTOK_M_COLON_SEEN){ + strcat(tmp_message," CTOK_M_COLON_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_ATSIGN_SEEN){ + strcat(tmp_message," CTOK_M_ATSIGN_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_STATUS_PASSED){ + strcat(tmp_message," CTOK_M_STATUS_PASSED"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.go_flag == 0){ + sprintf(tmp_message," Go flag clear\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + if(ct0->ctx.pnest || ct0->ctx.inest || ct0->ctx.cnest){ + sprintf(tmp_message," PNEST: %d INEST %d CNEST %d\n", + ct0->ctx.pnest,ct0->ctx.inest,ct0->ctx.cnest); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + sprintf(tmp_message," iarg1_flag %d iarg1 %d (0x%x)\n", + ct0->ctx.iarg1_flag,ct0->ctx.iarg1,ct0->ctx.iarg1); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg2_flag %d iarg2 %d (0x%x)\n", + ct0->ctx.iarg2_flag,ct0->ctx.iarg2,ct0->ctx.iarg2); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," carg 0x%x\n",(unsigned int)ct0->ctx.carg); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," tmpval %d (0x%x)\n", + ct0->ctx.tmpval,ct0->ctx.tmpval); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.return_state || ct0->ctx.caller_token){ + state_name = ""; + if(ct0->ctx.return_state){ + state_name = + trace_convert_state_to_name(ct0->ctx.return_state); + if(state_name == NULL) state_name = "UNKNOWN"; + }/* End IF */ + sprintf(tmp_message," return_state %s caller_token 0x%x\n", + state_name,(unsigned int)ct0->ctx.caller_token); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + break; + + }/* End Switch */ + +} + +char * +trace_convert_opcode_to_name(opcode) +int opcode; +{ + + PREAMBLE(); + + switch(opcode){ + case TOK_C_UNUSED: + return("TOK_C_UNUSED"); + case TOK_C_FIRSTTOKEN: + return("TOK_C_FIRSTTOKEN"); + case TOK_C_INPUTCHAR: + return("TOK_C_INPUTCHAR"); + case TOK_C_COPYTOKEN: + return("TOK_C_COPYTOKEN"); + case TOK_C_ITERATION_BEGIN: + return("TOK_C_ITERATION_BEGIN"); + case TOK_C_ITERATION_END: + return("TOK_C_ITERATION_END"); + case TOK_C_CONDITIONAL_END: + return("TOK_C_CONDITIONAL_END"); + case TOK_C_LABEL_BEGIN: + return("TOK_C_LABEL_BEGIN"); + case TOK_C_LABEL_END: + return("TOK_C_LABEL_END"); + case TOK_C_GOTO_BEGIN: + return("TOK_C_GOTO_BEGIN"); + case TOK_C_GOTO_END: + return("TOK_C_GOTO_END"); + case TOK_C_FINALTOKEN: + return("TOK_C_FINALTOKEN"); + case TOK_C_INITIALSTATE: + return("TOK_C_INITIALSTATE"); + case TOK_C_CONDITIONAL_ELSE: + return("TOK_C_CONDITIONAL_ELSE"); + default: + return(NULL); + }/* End Switch */ + +}/* End Routine */ + +char * +trace_convert_state_to_name(state) +int state; +{ + + PREAMBLE(); + + switch(state){ + case STATE_C_INITIALSTATE: + return("STATE_C_INITIALSTATE"); + case STATE_C_MAINCOMMANDS: + return("STATE_C_MAINCOMMANDS"); + case STATE_C_ESCAPESEEN: + return("STATE_C_ESCAPESEEN"); + case STATE_C_ECOMMAND: + return("STATE_C_ECOMMAND"); + case STATE_C_ARG1: + return("STATE_C_ARG1"); + case STATE_C_ARG2: + return("STATE_C_ARG2"); + case STATE_C_EXPRESSION: + return("STATE_C_EXPRESSION"); + case STATE_C_OPERATOR: + return("STATE_C_OPERATOR"); + case STATE_C_PLUS: + return("STATE_C_PLUS"); + case STATE_C_MINUS: + return("STATE_C_MINUS"); + case STATE_C_TIMES: + return("STATE_C_TIMES"); + case STATE_C_DIVIDE: + return("STATE_C_DIVIDE"); + case STATE_C_MINUSSEEN: + return("STATE_C_MINUSSEEN"); + case STATE_C_SUBEXPRESSION: + return("STATE_C_SUBEXPRESSION"); + case STATE_C_OPERAND: + return("STATE_C_OPERAND"); + case STATE_C_NUMBER_SUBSTATE: + return("STATE_C_NUMBER_SUBSTATE"); + case STATE_C_QOPERAND: + return("STATE_C_QOPERAND"); + case STATE_C_INSERT: + return("STATE_C_INSERT"); + case STATE_C_QUOTED_INSERT: + return("STATE_C_QUOTED_INSERT"); + case STATE_C_UMINUS: + return("STATE_C_UMINUS"); + case STATE_C_LABEL: + return("STATE_C_LABEL"); + case STATE_C_UQREGISTER: + return("STATE_C_UQREGISTER"); + case STATE_C_WRITEFILE: + return("STATE_C_WRITEFILE"); + case STATE_C_STRING: + return("STATE_C_STRING"); + case STATE_C_SEARCH: + return("STATE_C_SEARCH"); + case STATE_C_FCOMMAND: + return("STATE_C_FCOMMAND"); + case STATE_C_FSPART1: + return("STATE_C_FSPART1"); + case STATE_C_FSPART2: + return("STATE_C_FSPART2"); + case STATE_C_EDITBUF: + return("STATE_C_EDITBUF"); + case STATE_C_READFILE: + return("STATE_C_READFILE"); + case STATE_C_XQREGISTER: + return("STATE_C_XQREGISTER"); + case STATE_C_GQREGISTER: + return("STATE_C_GQREGISTER"); + case STATE_C_MQREGISTER: + return("STATE_C_MQREGISTER"); + case STATE_C_VIEWBUF: + return("STATE_C_VIEWBUF"); + case STATE_C_FDCOMMAND: + return("STATE_C_FDCOMMAND"); + case STATE_C_CONDITIONALS: + return("STATE_C_CONDITIONALS"); + case STATE_C_GOTO: + return("STATE_C_GOTO"); + case STATE_C_FRPART1: + return("STATE_C_FRPART1"); + case STATE_C_FRPART2: + return("STATE_C_FRPART2"); + case STATE_C_MESSAGE: + return("STATE_C_MESSAGE"); + case STATE_C_FKCOMMAND: + return("STATE_C_FKCOMMAND"); + case STATE_C_ECCOMMAND: + return("STATE_C_ECCOMMAND"); + case STATE_C_SAVECOMMAND: + return("STATE_C_SAVECOMMAND"); + case STATE_C_PERCENT_OPERAND: + return("STATE_C_PERCENT_OPERAND"); + case STATE_C_ATINSERT: + return("STATE_C_ATINSERT"); + case STATE_C_ATINSERT_PART2: + return("STATE_C_ATINSERT_PART2"); + case STATE_C_ONE_EQUALS: + return("STATE_C_ONE_EQUALS"); + case STATE_C_TWO_EQUALS: + return("STATE_C_TWO_EQUALS"); + case STATE_C_SKIP_ELSE: + return("STATE_C_SKIP_ELSE"); + case STATE_C_PUSH_QREGISTER: + return("STATE_C_PUSH_QREGISTER"); + case STATE_C_POP_QREGISTER: + return("STATE_C_POP_QREGISTER"); + case STATE_C_NSEARCH: + return("STATE_C_NSEARCH"); + case STATE_C_ACCEPT_ARGS: + return("STATE_C_ACCEPT_ARGS"); + case STATE_C_EQQREGISTER1: + return("STATE_C_EQQREGISTER1"); + case STATE_C_EQQREGISTER2: + return("STATE_C_EQQREGISTER2"); + case STATE_C_RADIX: + return("STATE_C_RADIX"); + case STATE_C_HEX_NUMBER: + return("STATE_C_HEX_NUMBER"); + case STATE_C_OCTAL_NUMBER: + return("STATE_C_OCTAL_NUMBER"); + case STATE_C_HEX_NUMBER_SUBSTATE: + return("STATE_C_HEX_NUMBER_SUBSTATE"); + case STATE_C_OCTAL_NUMBER_SUBSTATE: + return("STATE_C_OCTAL_NUMBER_SUBSTATE"); + case STATE_C_BACKSLASH: + return("STATE_C_BACKSLASH"); + case STATE_C_DELAYED_MINUS: + return("STATE_C_DELAYED_MINUS"); + case STATE_C_DELAYED_PLUS: + return("STATE_C_DELAYED_PLUS"); + case STATE_C_FSPART3: + return("STATE_C_FSPART3"); + + + case STATE_C_RETURN: + return("STATE_C_RETURN"); + case STATE_C_FINALSTATE: + return("STATE_C_FINALSTATE"); + case STATE_C_ERRORSTATE: + return("STATE_C_ERRORSTATE"); + default: + return(NULL); + }/* End Switch */ + +}/* End Routine */ + + + +char * +trace_convert_exec_state_to_name(state) +int state; +{ + + PREAMBLE(); + + switch(state){ + case EXEC_C_NULLSTATE: + return("EXEC_C_NULLSTATE"); + case EXEC_C_DOTARG1: + return("EXEC_C_DOTARG1"); + case EXEC_C_ZEEARG1: + return("EXEC_C_ZEEARG1"); + case EXEC_C_HARGUMENT: + return("EXEC_C_HARGUMENT"); + case EXEC_C_EXITCOMMAND: + return("EXEC_C_EXITCOMMAND"); + case EXEC_C_UQREGISTER: + return("EXEC_C_UQREGISTER"); + case EXEC_C_JUMP: + return("EXEC_C_JUMP"); + case EXEC_C_INSERT: + return("EXEC_C_INSERT"); + case EXEC_C_LINE: + return("EXEC_C_LINE"); + case EXEC_C_CHAR: + return("EXEC_C_CHAR"); + case EXEC_C_RCHAR: + return("EXEC_C_RCHAR"); + case EXEC_C_DELETE: + return("EXEC_C_DELETE"); + case EXEC_C_HVALUE: + return("EXEC_C_HVALUE"); + case EXEC_C_DOTVALUE: + return("EXEC_C_DOTVALUE"); + case EXEC_C_ZEEVALUE: + return("EXEC_C_ZEEVALUE"); + case EXEC_C_QVALUE: + return("EXEC_C_QVALUE"); + case EXEC_C_EQUALS: + return("EXEC_C_EQUALS"); + case EXEC_C_REDRAW_SCREEN: + return("EXEC_C_REDRAW_SCREEN"); + case EXEC_C_STOREVAL: + return("EXEC_C_STOREVAL"); + case EXEC_C_STORE1: + return("EXEC_C_STORE1"); + case EXEC_C_STORE2: + return("EXEC_C_STORE2"); + case EXEC_C_UMINUS: + return("EXEC_C_UMINUS"); + case EXEC_C_PLUS: + return("EXEC_C_PLUS"); + case EXEC_C_MINUS: + return("EXEC_C_MINUS"); + case EXEC_C_TIMES: + return("EXEC_C_TIMES"); + case EXEC_C_DIVIDE: + return("EXEC_C_DIVIDE"); + case EXEC_C_KILL: + return("EXEC_C_KILL"); + case EXEC_C_WRITEFILE: + return("EXEC_C_WRITEFILE"); + case EXEC_C_SEARCH: + return("EXEC_C_SEARCH"); + case EXEC_C_SETSEARCH: + return("EXEC_C_SETSEARCH"); + case EXEC_C_FSREPLACE1: + return("EXEC_C_FSREPLACE1"); + case EXEC_C_ITERATION_BEGIN: + return("EXEC_C_ITERATION_BEGIN"); + case EXEC_C_ITERATION_END: + return("EXEC_C_ITERATION_END"); + case EXEC_C_READFILE: + return("EXEC_C_READFILE"); + case EXEC_C_EDITBUF: + return("EXEC_C_EDITBUF"); + case EXEC_C_XQREGISTER: + return("EXEC_C_XQREGISTER"); + case EXEC_C_GQREGISTER: + return("EXEC_C_GQREGISTER"); + case EXEC_C_SEMICOLON: + return("EXEC_C_SEMICOLON"); + case EXEC_C_MQREGISTER: + return("EXEC_C_MQREGISTER"); + case EXEC_C_CLOSEBUF: + return("EXEC_C_CLOSEBUF"); + case EXEC_C_VIEWBUF: + return("EXEC_C_VIEWBUF"); + case EXEC_C_FDCOMMAND: + return("EXEC_C_FDCOMMAND"); + case EXEC_C_ACOMMAND: + return("EXEC_C_ACOMMAND"); + case EXEC_C_BACKSLASH: + return("EXEC_C_BACKSLASH"); + case EXEC_C_BACKSLASHARG: + return("EXEC_C_BACKSLASHARG"); + case EXEC_C_COND_GT: + return("EXEC_C_COND_GT"); + case EXEC_C_COND_LT: + return("EXEC_C_COND_LT"); + case EXEC_C_COND_EQ: + return("EXEC_C_COND_EQ"); + case EXEC_C_COND_NE: + return("EXEC_C_COND_NE"); + case EXEC_C_COND_DIGIT: + return("EXEC_C_COND_DIGIT"); + case EXEC_C_COND_ALPHA: + return("EXEC_C_COND_ALPHA"); + case EXEC_C_COND_LOWER: + return("EXEC_C_COND_LOWER"); + case EXEC_C_COND_UPPER: + return("EXEC_C_COND_UPPER"); + case EXEC_C_COND_SYMBOL: + return("EXEC_C_COND_SYMBOL"); + case EXEC_C_GOTO: + return("EXEC_C_GOTO"); + case EXEC_C_FRREPLACE: + return("EXEC_C_FRREPLACE"); + case EXEC_C_MESSAGE: + return("EXEC_C_MESSAGE"); + case EXEC_C_RESET_MESSAGE: + return("EXEC_C_RESET_MESSAGE"); + case EXEC_C_OUTPUT_MESSAGE: + return("EXEC_C_OUTPUT_MESSAGE"); + case EXEC_C_FKCOMMAND: + return("EXEC_C_FKCOMMAND"); + case EXEC_C_REMEMBER_DOT: + return("EXEC_C_REMEMBER_DOT"); + case EXEC_C_ECCOMMAND: + return("EXEC_C_ECCOMMAND"); + case EXEC_C_SAVECOMMAND: + return("EXEC_C_SAVECOMMAND"); + case EXEC_C_SCROLL: + return("EXEC_C_SCROLL"); + case EXEC_C_UPDATE_SCREEN: + return("EXEC_C_UPDATE_SCREEN"); + case EXEC_C_SET_IMMEDIATE_MODE: + return("EXEC_C_SET_IMMEDIATE_MODE"); + case EXEC_C_PERCENT_VALUE: + return("EXEC_C_PERCENT_VALUE"); + case EXEC_C_WORD: + return("EXEC_C_WORD"); + case EXEC_C_TWO_EQUALS: + return("EXEC_C_TWO_EQUALS"); + case EXEC_C_THREE_EQUALS: + return("EXEC_C_THREE_EQUALS"); + case EXEC_C_SKIP_ELSE: + return("EXEC_C_SKIP_ELSE"); + case EXEC_C_PUSH_QREGISTER: + return("EXEC_C_PUSH_QREGISTER"); + case EXEC_C_POP_QREGISTER: + return("EXEC_C_POP_QREGISTER"); + case EXEC_C_NSEARCH: + return("EXEC_C_NSEARCH"); + case EXEC_C_EQQREGISTER: + return("EXEC_C_EQQREGISTER"); + case EXEC_C_WINDOW_CONTROL: + return("EXEC_C_WINDOW_CONTROL"); + case EXEC_C_NEXT_WINDOW: + return("EXEC_C_NEXT_WINDOW"); + case EXEC_C_RLINE: + return("EXEC_C_RLINE"); + case EXEC_C_DELWORD: + return("EXEC_C_DELWORD"); + case EXEC_C_RDELWORD: + return("EXEC_C_RDELWORD"); + case EXEC_C_OPENBRACE: + return("EXEC_C_OPENBRACE"); + case EXEC_C_CLOSEBRACE: + return("EXEC_C_CLOSEBRACE"); + case EXEC_C_SKIPLABEL: + return("EXEC_C_SKIPLABEL"); + case EXEC_C_SETOPTIONS: + return("EXEC_C_SETOPTIONS"); + case EXEC_C_FSREPLACE2: + return("EXEC_C_FSREPLACE2"); + case EXEC_C_FSREPLACE3: + return("EXEC_C_FSREPLACE3"); + default: + return(NULL); + }/* End Switch */ + +}/* End Routine */ diff --git a/tecparse.h b/tecparse.h new file mode 100644 index 0000000..3ae0268 --- /dev/null +++ b/tecparse.h @@ -0,0 +1,327 @@ +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecparse.h,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecparse.h + * Definitions for TECO parser + * %W% (PC) %G% + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +/* + * This structure holds the context which must be passed forward as the + * parse progresses. We split it out of the cmd_token structure so that + * it can be easily duplicated. + */ +struct cmd_context { + char state; + char flags; + char go_flag; + char pnest; + char inest; + char cnest; + char iarg1_flag; + char iarg2_flag; + char delimeter; + int iarg1; + int iarg2; + char *carg; + int tmpval; + int return_state; + struct cmd_token *caller_token; +}; + +/* + * Define the structure which we use to describe commands + */ +struct cmd_token { + char opcode; + char input_byte; + char q_register; + char flags; + int execute_state; + struct cmd_token *next_token; + struct cmd_token *prev_token; + struct undo_token *undo_list; + struct cmd_context ctx; +}; + +struct undo_token { + char opcode; + int iarg1; + int iarg2; + char *carg1; + struct undo_token *next_token; +}; + +#define TRACE_C_PARSE 1 +#define TRACE_C_EXEC 2 +#define TRACE_C_MACRO 3 + +#define TOK_M_EAT_TOKEN (1 << 0) +#define TOK_M_WORDBOUNDARY (1 << 1) +#define TOK_M_PARSELOAD_IARG1 (1 << 2) +#define TOK_M_PARSELOAD_IARG2 (1 << 3) +#define TOK_M_PARSELOAD_TMPVAL (1 << 4) + +#define CTOK_M_COLON_SEEN (1 << 0) +#define CTOK_M_ATSIGN_SEEN (1 << 1) +#define CTOK_M_STATUS_PASSED (1 << 2) + +#define TOK_C_UNUSED 0 /* Value before it gets set */ +#define TOK_C_FIRSTTOKEN 1 /* The begining of it all */ +#define TOK_C_INPUTCHAR 2 /* An input character was recieved here */ +#define TOK_C_COPYTOKEN 3 /* A copy of a previous command token */ +#define TOK_C_ITERATION_BEGIN 4 /* Marks the begining of an iteration */ +#define TOK_C_ITERATION_END 5 /* Marks the end of an iteration */ +#define TOK_C_CONDITIONAL_END 6 /* Marks end of a conditional cmd */ +#define TOK_C_LABEL_BEGIN 7 /* Begining of a label tag !like this! */ +#define TOK_C_LABEL_END 8 /* End of a label tag */ +#define TOK_C_GOTO_BEGIN 9 /* Begining of an Omumble$ string */ +#define TOK_C_GOTO_END 10 /* End of an Omumble$ string */ +#define TOK_C_FINALTOKEN 11 /* Like FINALSTATE */ +#define TOK_C_INITIALSTATE 12 /* Used by ^W to find begining of cmds */ +#define TOK_C_CONDITIONAL_ELSE 13/* Marks begining of an ELSE clause */ + +#define STATE_C_INITIALSTATE 0 +#define STATE_C_MAINCOMMANDS 1 +#define STATE_C_ESCAPESEEN 2 +#define STATE_C_ECOMMAND 3 +#define STATE_C_ARG1 4 +#define STATE_C_ARG2 5 +#define STATE_C_EXPRESSION 6 +#define STATE_C_OPERATOR 7 +#define STATE_C_PLUS 8 +#define STATE_C_MINUS 9 +#define STATE_C_TIMES 10 +#define STATE_C_DIVIDE 11 +#define STATE_C_MINUSSEEN 14 +#define STATE_C_SUBEXPRESSION 15 +#define STATE_C_OPERAND 16 +#define STATE_C_NUMBER_SUBSTATE 17 +#define STATE_C_QOPERAND 18 +#define STATE_C_INSERT 19 +#define STATE_C_QUOTED_INSERT 20 +#define STATE_C_UMINUS 21 +#define STATE_C_LABEL 22 +#define STATE_C_UQREGISTER 23 +#define STATE_C_WRITEFILE 24 +#define STATE_C_STRING 25 +#define STATE_C_STRING1 26 +#define STATE_C_SEARCH 27 +#define STATE_C_FCOMMAND 28 +#define STATE_C_FSPART1 29 +#define STATE_C_FSPART2 30 +#define STATE_C_EDITBUF 31 +#define STATE_C_READFILE 32 +#define STATE_C_XQREGISTER 33 +#define STATE_C_GQREGISTER 34 +#define STATE_C_MQREGISTER 35 +#define STATE_C_VIEWBUF 36 +#define STATE_C_FDCOMMAND 37 +#define STATE_C_CONDITIONALS 38 +#define STATE_C_GOTO 39 +#define STATE_C_FRPART1 40 +#define STATE_C_FRPART2 41 +#define STATE_C_MESSAGE 42 +#define STATE_C_FKCOMMAND 43 +#define STATE_C_ECCOMMAND 44 +#define STATE_C_SAVECOMMAND 45 +#define STATE_C_PERCENT_OPERAND 46 +#define STATE_C_ATINSERT 47 +#define STATE_C_ATINSERT_PART2 48 +#define STATE_C_ONE_EQUALS 49 +#define STATE_C_TWO_EQUALS 50 +#define STATE_C_SKIP_ELSE 51 +#define STATE_C_PUSH_QREGISTER 52 +#define STATE_C_POP_QREGISTER 53 +#define STATE_C_NSEARCH 54 +#define STATE_C_ACCEPT_ARGS 55 +#define STATE_C_EQQREGISTER1 56 +#define STATE_C_EQQREGISTER2 57 +#define STATE_C_RADIX 58 +#define STATE_C_HEX_NUMBER 59 +#define STATE_C_OCTAL_NUMBER 60 +#define STATE_C_HEX_NUMBER_SUBSTATE 61 +#define STATE_C_OCTAL_NUMBER_SUBSTATE 62 +#define STATE_C_BACKSLASH 63 +#define STATE_C_DELAYED_MINUS 64 +#define STATE_C_DELAYED_PLUS 65 +#define STATE_C_FSPART3 66 +#define STATE_C_FTAGS 67 + + +#define STATE_C_RETURN 97 +#define STATE_C_FINALSTATE 98 +#define STATE_C_ERRORSTATE 99 + +/* + * Here we define the execution time states + */ +#define EXEC_C_NULLSTATE 0 +#define EXEC_C_DOTARG1 1 +#define EXEC_C_ZEEARG1 2 +#define EXEC_C_HARGUMENT 3 +#define EXEC_C_EXITCOMMAND 4 +#define EXEC_C_UQREGISTER 5 +#define EXEC_C_JUMP 8 +#define EXEC_C_INSERT 9 +#define EXEC_C_LINE 10 +#define EXEC_C_CHAR 11 +#define EXEC_C_RCHAR 12 +#define EXEC_C_DELETE 13 +#define EXEC_C_HVALUE 14 +#define EXEC_C_DOTVALUE 15 +#define EXEC_C_ZEEVALUE 16 +#define EXEC_C_QVALUE 17 +#define EXEC_C_EQUALS 18 +#define EXEC_C_REDRAW_SCREEN 19 +#define EXEC_C_STOREVAL 20 +#define EXEC_C_STORE1 21 +#define EXEC_C_STORE2 22 +#define EXEC_C_UMINUS 23 +#define EXEC_C_PLUS 24 +#define EXEC_C_MINUS 25 +#define EXEC_C_TIMES 26 +#define EXEC_C_DIVIDE 27 +#define EXEC_C_KILL 28 +#define EXEC_C_WRITEFILE 29 +#define EXEC_C_SEARCH 30 +#define EXEC_C_SETSEARCH 31 +#define EXEC_C_FSREPLACE1 32 +#define EXEC_C_ITERATION_BEGIN 33 +#define EXEC_C_ITERATION_END 34 +#define EXEC_C_READFILE 35 +#define EXEC_C_EDITBUF 36 +#define EXEC_C_XQREGISTER 37 +#define EXEC_C_GQREGISTER 38 +#define EXEC_C_SEMICOLON 39 +#define EXEC_C_MQREGISTER 40 +#define EXEC_C_CLOSEBUF 41 +#define EXEC_C_VIEWBUF 42 +#define EXEC_C_FDCOMMAND 43 +#define EXEC_C_ACOMMAND 44 +#define EXEC_C_BACKSLASH 45 +#define EXEC_C_BACKSLASHARG 46 +#define EXEC_C_COND_GT 47 +#define EXEC_C_COND_LT 48 +#define EXEC_C_COND_EQ 49 +#define EXEC_C_COND_NE 50 +#define EXEC_C_COND_DIGIT 51 +#define EXEC_C_COND_ALPHA 52 +#define EXEC_C_COND_LOWER 53 +#define EXEC_C_COND_UPPER 54 +#define EXEC_C_COND_SYMBOL 55 +#define EXEC_C_GOTO 56 +#define EXEC_C_FRREPLACE 57 +#define EXEC_C_MESSAGE 58 +#define EXEC_C_RESET_MESSAGE 59 +#define EXEC_C_OUTPUT_MESSAGE 60 +#define EXEC_C_FKCOMMAND 61 +#define EXEC_C_REMEMBER_DOT 62 +#define EXEC_C_ECCOMMAND 63 +#define EXEC_C_SAVECOMMAND 64 +#define EXEC_C_SCROLL 65 +#define EXEC_C_UPDATE_SCREEN 66 +#define EXEC_C_SET_IMMEDIATE_MODE 67 +#define EXEC_C_PERCENT_VALUE 68 +#define EXEC_C_WORD 69 +#define EXEC_C_TWO_EQUALS 70 +#define EXEC_C_THREE_EQUALS 71 +#define EXEC_C_SKIP_ELSE 72 +#define EXEC_C_PUSH_QREGISTER 73 +#define EXEC_C_POP_QREGISTER 74 +#define EXEC_C_NSEARCH 75 +#define EXEC_C_EQQREGISTER 76 +#define EXEC_C_WINDOW_CONTROL 77 +#define EXEC_C_NEXT_WINDOW 78 +#define EXEC_C_RLINE 79 +#define EXEC_C_DELWORD 80 +#define EXEC_C_RDELWORD 81 +#define EXEC_C_OPENBRACE 82 +#define EXEC_C_CLOSEBRACE 83 +#define EXEC_C_SKIPLABEL 84 +#define EXEC_C_SETOPTIONS 85 +#define EXEC_C_FSREPLACE2 86 +#define EXEC_C_FSREPLACE3 87 +#define EXEC_C_FTAGS 88 + +/* + * Define the UNDO symbols + */ +#define UNDO_C_UNUSED 0 +#define UNDO_C_CHANGEDOT 1 +#define UNDO_C_DELETE 2 +#define UNDO_C_INSERT 3 +#define UNDO_C_UQREGISTER 4 +#define UNDO_C_MEMFREE 5 +#define UNDO_C_SHORTEN_STRING 6 +#define UNDO_C_SET_SEARCH_STRING 7 +#define UNDO_C_CHANGEBUFF 8 +#define UNDO_C_MACRO 9 +#define UNDO_C_SHORTEN_MESSAGE 10 +#define UNDO_C_BULK_INSERT 11 +#define UNDO_C_SET_IMMEDIATE_MODE 12 +#define UNDO_C_PUSH 13 +#define UNDO_C_POP 14 +#define UNDO_C_MODIFIED 15 +#define UNDO_C_CLOSEBUFF 16 +#define UNDO_C_WINDOW_SWITCH 17 +#define UNDO_C_WINDOW_SPLIT 18 +#define UNDO_C_REOPENBUFF 19 +#define UNDO_C_RENAME_BUFFER 20 +#define UNDO_C_PRESERVEARGS 21 +#define UNDO_C_SETOPTIONS 22 +#define UNDO_C_SET_SEARCH_GLOBALS 23 +#define UNDO_C_LOAD_TAGS 24 +#define UNDO_C_SELECT_TAGS 25 +#define UNDO_C_SET_EXIT_FLAG 26 + +int cmd_oscmd(struct cmd_token *ct); +int buff_insert_from_buffer_with_undo( struct cmd_token *, + struct buff_header *,int,struct buff_header *,int,int); +int buff_delete_with_undo( struct cmd_token *,struct buff_header *,int,int); +int buff_insert_with_undo( struct cmd_token *, + struct buff_header *,int,char *,int); +int rename_edit_buffer(struct buff_header *,char *,struct cmd_token *); +int cmd_setoptions(int,int,struct undo_token *); +void tag_dump_database(struct tags *tp,struct cmd_token *uct); +int buff_insert_char_with_undo(struct cmd_token *, + struct buff_header *,int,char); +int set_search_string_with_undo(char *,struct cmd_token *uct); +int cmd_tags(struct cmd_token *uct,int,int,int,char *); +int tecmacro(struct buff_header *,struct cmd_token *,struct cmd_token **); +void parser_cleanup_ctlist(struct cmd_token *); +void free_cmd_token(struct cmd_token *); +int parser_undo(struct undo_token *); +void free_undo_token(struct undo_token *); +void screen_reset_echo(struct cmd_token *); +int parse_special_character(struct cmd_token *,int); +void parse_input_character(struct cmd_token *,struct cmd_token *); +void trace_mode(int, struct cmd_token *, struct cmd_token *); +int execute_a_state(struct cmd_token *, struct cmd_token *); +void tecundo_cleanup(struct undo_token *); +int parse_any_arguments(struct cmd_token *,char *); +int parse_more_than_one_arg(struct cmd_token *,char *); +int parse_check_qname(struct cmd_token *,char); +struct cmd_token *allocate_cmd_token(struct cmd_token *old_token); +struct undo_token *allocate_undo_token(struct cmd_token *ct); +void buff_free_line_buffer(struct buff_line *); + diff --git a/tecstate.c b/tecstate.c new file mode 100644 index 0000000..d908e74 --- /dev/null +++ b/tecstate.c @@ -0,0 +1,2374 @@ +char *tecstate_c_version = "tecstate.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecstate.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecstate.c + * Main SWITCH/CASE statements to implement the parser syntax stage + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern char immediate_execute_flag; + extern char trace_mode_flag; + extern char suspend_is_okay_flag; + + + +/* PARSE_INPUT_CHARACTER - Continue the parse with the supplied character + * + * Function: + * + * We re-enter the parser with a new character at this point. We jump back + * to the state we left before. + */ +void +parse_input_character(ct,uct) +register struct cmd_token *ct; +struct cmd_token *uct; +{ +char tmp_message[LINE_BUFFER_SIZE]; +register struct cmd_token *oct = NULL; + + PREAMBLE(); + + switch(ct->ctx.state){ +/* + * Here on initial command state. This is the begining of a command, so we are + * looking for arguments that might go with a command. If there are no args, + * we just transfer into the main command loop. + */ + case STATE_C_INITIALSTATE: + ct->ctx.flags &= ~(CTOK_M_COLON_SEEN | CTOK_M_ATSIGN_SEEN); + ct->flags |= TOK_M_WORDBOUNDARY; + case STATE_C_ACCEPT_ARGS: + ct->ctx.carg = NULL; + switch(ct->input_byte){ +/* + * If it looks like an argument, then transfer into a subexpression parser to + * get the value of the expression. ARG1 will stuff the result into iarg1 and + * also check for a comma (',') incase he is specifing a twin argument command + */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'Q': case 'q': case '%': +#ifdef CLASSIC_B_BEHAVIOR + case 'B': case 'b': +#endif + case 'Z': case 'z': + case '\\': + case '.': + case '-': + case '(': + case '^': + ct->ctx.flags &= ~CTOK_M_STATUS_PASSED; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->ctx.state = STATE_C_EXPRESSION; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_ARG1; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; +/* + * H is a special case, since the single argument actually implies two args. + * (H actually is the same as 0,z<cmd>) + */ + case 'H': case 'h': + ct->ctx.flags &= ~CTOK_M_STATUS_PASSED; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = YES; + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->execute_state = EXEC_C_HVALUE; + return; +/* + * Here on the @ command. This says to use user specified delimeters for + * strings, rather than just terminating with escape. + */ + case '@': + ct->ctx.flags |= CTOK_M_ATSIGN_SEEN; + ct->ctx.state = STATE_C_ACCEPT_ARGS; + return; +/* + * Here on the : command. This is just a flag to many commands to tell them + * to work in a slightly different way. The most common usage is to mean that + * the command should return a value which says whether it worked or not. + */ + case ':': + ct->ctx.flags |= CTOK_M_COLON_SEEN; + ct->ctx.state = STATE_C_ACCEPT_ARGS; + return; +/* + * Well, it doesn't look like he is going to be specifying any arguments, so we + * just go and decode the command. + */ + default: + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.flags &= ~CTOK_M_STATUS_PASSED; + return; + }/* End IF */ + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + + }/* End Switch */ +/* + * Here to parse the major commands (i.e., ones that have no lead-in) + */ + case STATE_C_MAINCOMMANDS: + switch(ct->input_byte){ +/* + * Space is a nop so that it can be used to make macros more readable + */ + case ' ': + ct->ctx.state = STATE_C_INITIALSTATE; + return; +/* + * Carriage Return is a nop so that it can be used in a macro to avoid + * extremely long lines wrapping. + */ + case '\n': + ct->ctx.state = STATE_C_MAINCOMMANDS; + return; +/* + * Here on an escape. First of all, we have to remember that we have seen an + * escape since two in a row will terminate the command. Also, escape has the + * effect of blocking arguments from commands i.e. 2L will move two lines, but + * 2$L will only move one since the escape eats the argument. + */ + case ESCAPE: + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->ctx.state = STATE_C_ESCAPESEEN; + return; +/* + * Here on an Asterisk command. This causes the last double-escaped command + * sequence to be saved in the named q-register. + */ + case '*': + if(parse_any_arguments(ct,"*")) return; + ct->ctx.state = STATE_C_SAVECOMMAND; + return; +/* + * Here on the @ command. This says to use user specified delimeters for + * strings, rather than just terminating with escape. + */ + case '@': + ct->ctx.flags |= CTOK_M_ATSIGN_SEEN; + return; +/* + * Here on the : command. This is just a flag to many commands to tell them + * to work in a slightly different way. The most common usage is to mean that + * the command should return a value which says whether it worked or not. + */ + case ':': + ct->ctx.flags |= CTOK_M_COLON_SEEN; + return; +/* + * Here on the [ command. This causes the specified Q register to be pushed + * onto the Q register pushdown stack. + */ + case '[': + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + ct->ctx.state = STATE_C_PUSH_QREGISTER; + return; + +/* + * Here on the ] command. This causes a Q register to be popped off of the + * Q register pushdown stack, and replaces the specified Q register. + */ + case ']': + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + ct->ctx.state = STATE_C_POP_QREGISTER; + return; +/* + * The B command moves backwards by lines. It is strictly a Video TECO + * enhancement. In normal TECO, the B command returns the address of the + * begining of the buffer, i.e. 0. + */ + case 'B': case 'b': + if(parse_more_than_one_arg(ct,"B")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_RLINE; + return; +/* + * Here on the C command. The C command is a relative move command, i.e., the + * argument says how many spaces from the current position to move. + */ + case 'C': case 'c': + if(parse_more_than_one_arg(ct,"C")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_CHAR; + return; +/* + * The D command deletes the specified number of characters in the indicated + * direction. + */ + case 'D': case 'd': + if(parse_more_than_one_arg(ct,"D")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_DELETE; + return; +/* + * Here on an E command. This is simply the lead-in to any one of the many + * E commands. + */ + case 'E': case 'e': + ct->ctx.state = STATE_C_ECOMMAND; + return; +/* + * Here on an F command. This is a lead-in to one of the F? commands. + */ + case 'F': case 'f': + ct->ctx.state = STATE_C_FCOMMAND; + return; +/* + * The G command copies the contents of the specified q-register into the + * edit buffer at the current position. + */ + case 'G': case 'g': + if(parse_any_arguments(ct,"G")) return; + ct->ctx.state = STATE_C_GQREGISTER; + return; +/* + * The I command inserts characters until an escape is input. If this is + * entered with the tab command, not only do we enter insert mode, we also + * insert the tab itself. + */ + case '\t': + if(ct->ctx.iarg1_flag == NO) ct->flags &= ~TOK_M_EAT_TOKEN; + + + case 'I': case 'i': + if(parse_more_than_one_arg(ct,"I")) return; + + if(ct->ctx.iarg1_flag == YES){ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_INSERT; + return; + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_ATSIGN_SEEN){ + ct->ctx.state = STATE_C_ATINSERT; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_INSERT; + return; +/* + * Here on the J command. The J command jumps to an absolute position in the + * buffer. + */ + case 'J': case 'j': + if(parse_more_than_one_arg(ct,"J")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_JUMP; + return; +/* + * The K command acts just like the L command except that it deletes instead + * of moving over. Also, it is legal to specify an a,b range to K + */ + case 'K': case 'k': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_KILL; + return; +/* + * The L command moves over the specified number of new-line characters. An + * argument of zero means get to the begining of the current line. + */ + case 'L': case 'l': + if(parse_more_than_one_arg(ct,"L")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_LINE; + return; +/* + * The M command executes the contents of the specified Q register as a macro. + */ + case 'M': case 'm': + ct->ctx.state = STATE_C_MQREGISTER; + return; +/* + * The N command will search for the specified string across multiple edit + * buffers. + */ + case 'N': case 'n': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_NSEARCH; + return; +/* + * The O command goes to the specified label + */ + case 'O': case 'o': + if(parse_any_arguments(ct,"O")) return; + ct->ctx.state = STATE_C_GOTO; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_GOTO_BEGIN; + return; +/* + * The P command selects the next window + */ + case 'P': case 'p': + if(parse_more_than_one_arg(ct,"P")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_NEXT_WINDOW; + return; +/* + * The R command is exactly like the C command, except that the direction + * is reversed. + */ + case 'R': case 'r': + if(parse_more_than_one_arg(ct,"R")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_RCHAR; + return; +/* + * The S command will search for the specified string + */ + case 'S': case 's': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_SEARCH; + return; +/* + * The ^L command is temporarily used to redraw the screen + */ + case '\f': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_REDRAW_SCREEN; + return; +/* + * The U command loads the argument into the specified Q register + */ + case 'U': case 'u': + if(parse_more_than_one_arg(ct,"U")) return; + ct->ctx.state = STATE_C_UQREGISTER; + return; +/* + * The V command deletes words. This is a Vido TECO enhancement. In normal + * TECO, the V command was equivalent to 0TT + */ + case 'V': case 'v': + if(parse_more_than_one_arg(ct,"V")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_DELWORD; + return; +/* + * The W command moves by words + */ + case 'W': case 'w': + if(parse_more_than_one_arg(ct,"W")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_WORD; + return; +/* + * The X command moves a block of characters from the edit buffer into + * the specified q register. + */ + case 'X': case 'x': + ct->ctx.state = STATE_C_XQREGISTER; + return; +/* + * The Y command deletes words in reverse direction. This is a Video TECO + * enhancement. In classic TECO, the Y command 'yanked' input data into the + * edit buffer. + */ + case 'Y': case 'y': + if(parse_more_than_one_arg(ct,"Y")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_RDELWORD; + return; +/* + * The ^A command allows you to print a message to the message line + */ + case CNTRL_A: + if(parse_any_arguments(ct,"^A")) return; + ct->ctx.state = STATE_C_MESSAGE; + ct->execute_state = EXEC_C_RESET_MESSAGE; + return; +/* + * The < command opens an iteration + */ + case '<': + if(parse_more_than_one_arg(ct,"<")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_ITERATION_BEGIN; + ct->ctx.inest += 1; + ct->execute_state = EXEC_C_ITERATION_BEGIN; + return; +/* + * The > command closes an iteration + */ + case '>': + if(parse_more_than_one_arg(ct,">")) return; + if(ct->ctx.inest == 0){ + error_message("?No Iteration Present"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_ITERATION_END; + while((oct = oct->prev_token)){ + if(oct->opcode != TOK_C_ITERATION_BEGIN) continue; + if(oct->ctx.inest != ct->ctx.inest) continue; + ct->ctx.caller_token = oct; + break; + }/* End While */ +/* + * Preserve the arguments so that if the loop gets undone, it will get redone + * the correct number of iterations when the > is typed again. + */ + { + register struct undo_token *ut; + ut = allocate_undo_token(ct); + if(ut == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ut->opcode = UNDO_C_PRESERVEARGS; + ut->iarg1 = oct->ctx.iarg1; + ut->iarg2 = oct->ctx.iarg2; + ut->carg1 = (char *)oct; + + ct->ctx.inest -= 1; + ct->execute_state = EXEC_C_ITERATION_END; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_COPYTOKEN; + return; + } +/* + * The ; command terminates an iteration if the argument is >= 0. + * If there is no argument supplied, it uses the state of the last + * search operation performed as a value. + */ + case ';': + if(parse_more_than_one_arg(ct,";")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_SEMICOLON; + return; +/* + * The = command prints out the value of the supplied expression. + * A single = prints out in decimal, == prints in octal and === in hex. + */ + case '=': + if(parse_more_than_one_arg(ct,"=")) return; + ct->ctx.state = STATE_C_ONE_EQUALS; + ct->execute_state = EXEC_C_EQUALS; + return; +/* + * The " command is the conditional 'IF' operator. The following commands only + * get executed if the condition is satisfied. + */ + case '"': + if(parse_more_than_one_arg(ct,"\"")) return; + ct->ctx.state = STATE_C_CONDITIONALS; + ct->ctx.cnest += 1; + return; +/* + * The | command provides an else clause to a conditional expression + */ + case '|': + if(parse_any_arguments(ct,"|")) return; + if(ct->ctx.cnest <= 0){ + error_message("?Not in a conditional"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_SKIP_ELSE; + ct->execute_state = EXEC_C_SKIP_ELSE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; +/* + * The ' command ends a conditional expression. + */ + case '\'': + if(parse_any_arguments(ct,"'")) return; + if(ct->ctx.cnest <= 0){ + error_message("?Not in a conditional"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_CONDITIONAL_END; + ct->ctx.cnest -= 1; + return; +/* + * The Label command allows you to set a tag which can be jumped to with + * the 'O' command. It also provides a way to comment macros. + */ + case '!': + if(parse_any_arguments(ct,"!")) return; + ct->ctx.state = STATE_C_LABEL; + ct->execute_state = EXEC_C_SKIPLABEL; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_LABEL_BEGIN; + return; +/* + * The backslash command with an argument inserts the decimal + * representation of the argument into the buffer at the current position. + */ + case '\\': + if(ct->ctx.iarg2_flag == NO){ + ct->ctx.iarg2 = 10; + ct->flags |= TOK_M_PARSELOAD_IARG2; + }/* End IF */ + + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_BACKSLASH; + return; +/* + * The open-brace command copies the current command string into a special + * Q-register and places the user there so he can edit it. + */ + case '{': + if(parse_any_arguments(ct,"{")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_OPENBRACE; + return; + +/* + * The close-brace command replaces the command string with the string in + * the special Q-register. + */ + case '}': + if(parse_any_arguments(ct,"}")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_CLOSEBRACE; + return; + +/* + * Here on a system which doesn't really do suspend correctly + */ + case CNTRL_Z: + if(suspend_is_okay_flag == YES){ + cmd_suspend(); + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + return; + +/* + * Here to toggle trace mode. This inserts information into q-register ? + * to help us track execution. + */ + case '?': + trace_mode_flag = !trace_mode_flag; + screen_message(trace_mode_flag ? "Trace Mode ON" : + "Trace Mode OFF"); + ct->ctx.state = STATE_C_INITIALSTATE; + return; + +/* + * We only get here on an error, since we should never dispatch a command that + * does not have a corresponding case statement. + */ + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?MAINCOMMANDS: unknown command <%c>", + UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * Here when we have seen the begining of a conditional '"'. Now we have to + * look at the next character to determine what the actual condition test is. + */ + case STATE_C_CONDITIONALS: + switch(ct->input_byte){ + case 'G': case 'g': + case '>': + ct->execute_state = EXEC_C_COND_GT; + break; + case 'L': case 'l': + case 'T': case 't': + case 'S': case 's': + case '<': + ct->execute_state = EXEC_C_COND_LT; + break; + case 'E': case 'e': + case 'F': case 'f': + case 'U': case 'u': + case '=': + ct->execute_state = EXEC_C_COND_EQ; + break; + case 'N': case 'n': + case '!': + ct->execute_state = EXEC_C_COND_NE; + break; + case 'C': case 'c': + ct->execute_state = EXEC_C_COND_SYMBOL; + break; + case 'D': case 'd': + ct->execute_state = EXEC_C_COND_DIGIT; + break; + case 'A': case 'a': + ct->execute_state = EXEC_C_COND_ALPHA; + break; + case 'V': case 'v': + ct->execute_state = EXEC_C_COND_LOWER; + break; + case 'W': case 'w': + ct->execute_state = EXEC_C_COND_UPPER; + break; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unknown conditional command '%c'", + UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ + + ct->ctx.state = STATE_C_INITIALSTATE; + return; +/* + * Here to watch for the end of a label + */ + case STATE_C_LABEL: + switch(ct->input_byte){ + case '!': + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_LABEL_END; + return; + default: + ct->ctx.state = STATE_C_LABEL; + return; + }/* End Switch */ +/* + * Here to eat the characters in a GOTO command + */ + case STATE_C_GOTO: + switch(ct->input_byte){ + case ESCAPE: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_GOTO_END; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->execute_state = EXEC_C_GOTO; + return; + default: + ct->ctx.state = STATE_C_GOTO; + return; + }/* End Switch */ +/* + * Here if we have seen an escape. If we see another, then he wants us to + * complete the parse and execute any remaining commands. + */ + case STATE_C_ESCAPESEEN: + switch(ct->input_byte){ + case ESCAPE: + if(ct->ctx.inest){ + error_message("?Unterminated Iteration"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + if(ct->ctx.cnest){ + error_message("?Unterminated Conditional"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_FINALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_FINALTOKEN; + return; + + default: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * Here if we have seen an E as a lead-in to one of the E commands. + */ + case STATE_C_ECOMMAND: + switch(ct->input_byte){ +/* + * The EB command creates a new edit buffer and reads the specified file in. + * If the command is given an argument, then this is a shorthand switch to + * the buffer that has that number. + */ + case 'B': case 'b': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + if(ct->ctx.iarg1_flag == YES){ + ct->execute_state = EXEC_C_EDITBUF; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_EDITBUF; + return; +/* + * The EC command allows you to execute a command, and the output is placed + * into the edit buffer. + */ + case 'C': case 'c': + if(ct->ctx.iarg1_flag == YES){ + ct->execute_state = EXEC_C_ECCOMMAND; + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_ECCOMMAND; + return; +/* + * The EF command closes the current edit buffer. If the current buffer + * is modified, the command will fail unless the user specifies an argument + * to the command. + */ + case 'F': case 'f': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->execute_state = EXEC_C_CLOSEBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The EI command allows you to alter immediate execution mode. With no + * argument, execute mode is turned off for the remainder of the current + * command. An argument of 0 turns it off until further notice and an + * argument of 1 turns it back on. + */ + case 'I': case 'i': + if(parse_more_than_one_arg(ct,"EI")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + + if(ct->ctx.iarg1_flag == NO){ + ct->ctx.go_flag = NO; + return; + }/* End IF */ + + ct->execute_state = EXEC_C_SET_IMMEDIATE_MODE; + return; +/* + * The EJ command allows you to load runtime options into TECO. + */ + case 'J': case 'j': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_SETOPTIONS; + return; + +/* + * The EP command splits the current window in half. If an argument is + * supplied, the command deletes the current window. + */ + case 'P': case 'p': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_WINDOW_CONTROL; + return; +/* + * The EV command works exactly like the EB command except that the buffer + * which is created is 'readonly'. Thus this command stands for 'VIEW' file. + */ + case 'V': case 'v': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + if(ct->ctx.iarg1_flag == YES){ + ct->execute_state = EXEC_C_VIEWBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_VIEWBUF; + return; +/* + * The EQ command reads a file into the specified Q-register. + */ + case 'Q': case 'q': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + if(parse_any_arguments(ct,"EQ")) return; + ct->ctx.state = STATE_C_EQQREGISTER1; + return; +/* + * The ER command reads the specified file into the current buffer location. + */ + case 'R': case 'r': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_READFILE; + return; +/* + * The ES command causes the screen to scroll + */ + case 'S': case 's': + if(parse_more_than_one_arg(ct,"ES")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_SCROLL; + return; +/* + * The ET command causes the screen to update + */ + case 'T': case 't': + if(parse_any_arguments(ct,"ET")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_UPDATE_SCREEN; + return; +/* + * The EW command writes out the current buffer + */ + case 'W': case 'w': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_WRITEFILE; + return; +/* + * The EX command causes the editor to exit + */ + case 'X': case 'x': + ct->execute_state = EXEC_C_EXITCOMMAND; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->ctx.go_flag = NO; + return; + + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unknown command E%c",UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * Here if we have seen an F as a lead-in to one of the F commands. + */ + case STATE_C_FCOMMAND: + switch(ct->input_byte){ +/* + * The FD command finds the string and deletes it. + */ + case 'D': case 'd': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FDCOMMAND; + return; +/* + * The FK command clears the text between the current position and the position + * of the searched for text. The searched for text is left unmodified. + */ + case 'K': case 'k': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FKCOMMAND; + return; +/* + * The FS command finds the first string and replaces it with the second + */ + case 'S': case 's': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FSPART1; + return; +/* + * The FT command is a Video TECO extension to read & use a unix style + * tags file. + */ + case 'T': case 't': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FTAGS; + return; + +/* + * The FR command acts like the FS command except that if the second argument + * is NULL, the string is replaced with the last replace value. + */ + case 'R': case 'r': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->execute_state = EXEC_C_SEARCH; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->execute_state = EXEC_C_FRREPLACE; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + + return; +/* + * Here when we have an 'F' command with an unknown second character. This + * makes it an illegal command, and we don't waste any time letting the user + * know about it. + */ + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unknown command F%c",UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * The following state is the return-state from STRING when the first part + * of an FS command has been parsed. + */ + case STATE_C_FSPART1: + ct->ctx.state = STATE_C_FSPART2; + ct->execute_state = EXEC_C_SEARCH; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->execute_state = EXEC_C_FSREPLACE1; + return; + + case STATE_C_FSPART2: + ct->flags |= TOK_M_WORDBOUNDARY; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_FSPART3; + return; + + case STATE_C_FSPART3: + if(ct->ctx.flags & CTOK_M_ATSIGN_SEEN){ + if(ct->input_byte == ct->ctx.delimeter){ + ct->ctx.state = STATE_C_ESCAPESEEN; + ct->execute_state = EXEC_C_FSREPLACE3; + return; + } + + else { + ct->ctx.state = STATE_C_FSPART3; + ct->execute_state = EXEC_C_FSREPLACE2; + return; + + } + } + + if(ct->input_byte == ESCAPE){ + ct->ctx.state = STATE_C_ESCAPESEEN; + ct->execute_state = EXEC_C_FSREPLACE3; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; + } + + else { + ct->ctx.state = STATE_C_FSPART3; + ct->execute_state = EXEC_C_FSREPLACE2; + return; + + } + +#ifdef WIMPY_CODER_WHO_HAS_NOT_HANDLED_THIS_YET + case CNTRL_V: + ct->ctx.state = STATE_C_QUOTED_INSERT; + return; +#endif /* WIMPY_CODER_WHO_HAS_NOT_HANDLED_THIS_YET */ + +/* + * This state is the return state from STRING and the TAGS command is + * ready to execute. + */ + case STATE_C_FTAGS: + ct->execute_state = EXEC_C_FTAGS; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; + +/* + * The following state is the return-state from STRING when a search string + * has been completely specified. + */ + case STATE_C_SEARCH: + ct->execute_state = EXEC_C_SEARCH; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is the return-state from STRING when a search string + * has been completely specified for the 'N' search command. + */ + case STATE_C_NSEARCH: + ct->execute_state = EXEC_C_NSEARCH; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is the return-state from STRING when the search part + * of an FD command has been parsed. + */ + case STATE_C_FDCOMMAND: + ct->execute_state = EXEC_C_SEARCH; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.carg = NULL; + ct->execute_state = EXEC_C_FDCOMMAND; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is the return-state from STRING when the search part + * of an FK command has been parsed. + */ + case STATE_C_FKCOMMAND: + ct->execute_state = EXEC_C_REMEMBER_DOT; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->execute_state = EXEC_C_SEARCH; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.carg = NULL; + ct->execute_state = EXEC_C_FKCOMMAND; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file to + * write. + */ + case STATE_C_WRITEFILE: + ct->execute_state = EXEC_C_WRITEFILE; + ct->ctx.go_flag = NO; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file to + * read into the current buffer location. + */ + case STATE_C_READFILE: + ct->execute_state = EXEC_C_READFILE; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file which + * we want to create a buffer for and load. + */ + case STATE_C_EDITBUF: + ct->execute_state = EXEC_C_EDITBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file which + * we want to create a readonly buffer for and load. + */ + case STATE_C_VIEWBUF: + ct->execute_state = EXEC_C_VIEWBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the command the user wants + * to execute. + */ + case STATE_C_ECCOMMAND: + ct->execute_state = EXEC_C_ECCOMMAND; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + return; +/* + * The following state gets entered when the user has typed ^A + * to print out a message. + */ + case STATE_C_MESSAGE: + switch(ct->input_byte){ + case CNTRL_A: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_OUTPUT_MESSAGE; + return; + default: + ct->execute_state = EXEC_C_MESSAGE; + ct->ctx.state = STATE_C_MESSAGE; + return; + }/* End Switch */ + +/* + * The following state is a return-state from EXPRESSION when we have seen + * what appears to be a first argument. It stashes the argument and tests if + * there is a second argument available (indicated by a comma). + */ + case STATE_C_ARG1: + ct->ctx.iarg1_flag = YES; + ct->execute_state = EXEC_C_STORE1; + switch(ct->input_byte){ + case ',': + ct->ctx.state = STATE_C_EXPRESSION; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_ARG2; + return; + + default: + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * Here if the first argument was followed by a comma. This indicates that a + * second argument is being specified. Only certain commands accept a double + * argument. + */ + case STATE_C_ARG2: + ct->ctx.iarg2_flag = YES; + ct->execute_state = EXEC_C_STORE2; + switch(ct->input_byte){ + case ',': + ct->ctx.state = STATE_C_ERRORSTATE; + error_message("?Maximum of two arguments exceeded"); + return; + + default: + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * The next state is the expression substate. States outside of the expression + * parser call through here so that pnest (the current nesting level of + * parentheses) gets set to zero. Then it calls the expression parser's own + * internal sub-expression state. + */ + case STATE_C_EXPRESSION: + ct->ctx.pnest = 0; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_SUBEXPRESSION; + return; +/* + * This is the sub-expression state. It gets called from within the expression + * parser when we want to recursively parse an expression. This lets us handle + * precedence and parenthesis correctly. + */ + case STATE_C_SUBEXPRESSION: + switch(ct->input_byte){ +/* + * When we see an open parenthesis, we want to recursively call the + * subexpression state to parse the contents of the parenthesis. Since the open + * parenthesis always occurs in place of an operand, we set that to be the next + * state to call. + */ + case '(': + ct->ctx.pnest += 1; + ct->ctx.state = STATE_C_OPERAND; + + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_OPERATOR; + + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_OPERATOR; + return; +/* + * If we see a minus sign here, it is a unary minus. The difference between + * the unary minus and the normal minus is one of precedence. The unary minus + * is very high precedence. + */ + case '-': + ct->ctx.state = STATE_C_MINUSSEEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_UMINUS; + return; +/* + * Here on a leading % command. This just means he is going to default to + * incrementing the queue register by one. + */ + case '%': + ct->ctx.tmpval = 1; + ct->flags |= TOK_M_PARSELOAD_TMPVAL; +/* ;;; the following line shouldn't be required, but is. A bug. */ + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.state = STATE_C_PERCENT_OPERAND; + return; +/* + * Well, not much exciting going on here, so we just call the normal operand + * state to parse the current token. + */ + default: + ct->ctx.state = STATE_C_OPERAND; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_OPERATOR; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * Here if we saw a unary minus. The reason for calling this state at all is to + * catch constructs like -L which really implies -1L. In a case like this, + * the OPERAND state will not see anything it understands, and will return. + * So as to make the defaulting work correctly, we catch that case here and + * default so that we get our -1. + */ + case STATE_C_MINUSSEEN: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'Q': case 'q': case '%': + case '.': +#ifdef CLASSIC_B_BEHAVIOR + case 'B': case 'b': +#endif + case 'Z': case 'z': + case '(': + case '^': + ct->ctx.state = STATE_C_OPERAND; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + + default: + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 1; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch */ +/* + * Here on the return from the OPERAND state. Whatever it parsed, we want to + * make it minus. + */ + case STATE_C_UMINUS: + ct->execute_state = EXEC_C_UMINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here when we are expecting an operator like + = * /. Note that ')' also gets + * handled here since it always appears in the place an operator would be + * expected to appear. + */ + case STATE_C_OPERATOR: + switch(ct->input_byte){ +/* + * Here on an a-b case. Note that since minus is a low precedence operator, + * we stash the temporary value, and call subexpression to parse the rest + * of the expression. In that way, if the next operator was * or /, it will + * get processed first. + */ + case '-': + ct->execute_state = EXEC_C_STORE2; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_MINUS; + return; +/* + * Here on an a+b case. This is similar to the case above in that we want to + * just stash the first value, and then recursively call the parser to handle + * the rest of the expression first. Note that this causes a+b+c to actually + * get handled as a+(b+c) which is a little weird, but works out ok. + */ + case '+': + ct->execute_state = EXEC_C_STORE2; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_PLUS; + return; +/* + * Here on the a*b case. Unlike the above two cases, we want to immediately + * handle this case since it is the highest precedence of the four operators. + */ + case '*': + ct->execute_state = EXEC_C_STORE1; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_TIMES; + return; +/* + * Here on the a/b case. This is just like the multiply state + */ + case '/': + ct->execute_state = EXEC_C_STORE1; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_DIVIDE; + return; + +/* + * Here on the n%q case. This just collapses to the value of the Q-register + * after it has been incremented by 'n' + */ + case '%': + ct->ctx.state = STATE_C_PERCENT_OPERAND; + return; +/* + * Here on the 'A' command which returns a value + */ + case 'A': case 'a': + ct->execute_state = EXEC_C_ACOMMAND; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here on a close parenthesis. This will force the end of a subexpression + * parse even though it would not normally have terminated yet. Note the check + * for the guy typing too many of these. + */ + case ')': + if(ct->ctx.pnest == 0){ + error_message("?No Matching Open Parenthesis"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.pnest -= 1; + ct->ctx.state = STATE_C_RETURN; + return; +/* + * If this is not an operator character, it must be the end of the expression. + * In this case, we must be back at the zero level for parenthesis nesting. + */ + default: + if(ct->ctx.pnest > 0){ + sprintf(tmp_message,"?Missing %d Close Parenthesis", + ct->ctx.pnest); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch */ +/* + * Here when we have the 'b' part of an a+b expression. This happens either + * when the end of the expression has been completed, or a parenthesis has + * forced a temporary end as in: (a+b) + */ + case STATE_C_PLUS: + switch(ct->input_byte){ + case '-': + case '+': + case ')': + ct->execute_state = EXEC_C_PLUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + default: + ct->ctx.state = STATE_C_OPERATOR; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_DELAYED_PLUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ + + case STATE_C_DELAYED_PLUS: + ct->execute_state = EXEC_C_PLUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + +/* + * Here when we have the 'b' part of an a-b expression. This happens either + * when the end of the expression has been completed, or a parenthesis has + * forced a temporary end as in: (a-b) + */ + case STATE_C_MINUS: + switch(ct->input_byte){ + case '-': + case '+': + case ')': + ct->execute_state = EXEC_C_MINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + default: + ct->ctx.state = STATE_C_OPERATOR; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_DELAYED_MINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ + + case STATE_C_DELAYED_MINUS: + ct->execute_state = EXEC_C_MINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + +/* + * Here when we have both arguments to a multiply. Because multiply is a higher + * precedence operator than +-, it happens immediately unless parenthesis get + * involved. + */ + case STATE_C_TIMES: + ct->execute_state = EXEC_C_TIMES; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here when we have both arguments to a divide. Because division is a higher + * precedence operator than -+, it generally happens immediately. Note that we + * check to be sure that the divisor is non-zero. + */ + case STATE_C_DIVIDE: + ct->execute_state = EXEC_C_DIVIDE; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here to parse any legal TECO operand. These would include numbers and + * commands that return values. Since open parenthesis occur in the same place + * as operands, we also catch them here. + */ + case STATE_C_OPERAND: + switch(ct->input_byte){ +/* + * If we see a numeric digit, call a substate which will continue eating bytes + * until a non-digit is seen. + */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - '0'; + return; +/* + * Here if he wants to input in a different radix + */ + case '^': + ct->ctx.state = STATE_C_RADIX; + ct->ctx.tmpval = 0; + return; +/* + * Here if he is specifying the numeric value of a q-register. We have to go + * to another state to determine which q-register he wants to access. + */ + case 'Q': case 'q': + ct->ctx.state = STATE_C_QOPERAND; + return; +/* + * Here when he has specified <dot>. This will return as a value the current + * position in the edit buffer. + */ + case '.': + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_DOTVALUE; + return; +#ifdef CLASSIC_B_BEHAVIOR +/* + * B is a special operand which returns the address of the first character + * in the buffer. This is currently always 0, but may change if some really + * obscure features get put in one of these days. + */ + case 'B': case 'b': + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 0; + return; +#endif +/* + * Z is a special operand which is the number of characters currently in the + * edit buffer. + */ + case 'Z': case 'z': + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_ZEEVALUE; + return; +/* + * BACKSLASH with no argument actually looks in the buffer at the current + * position and eats numeric digits until it hits a non-digit. It then + * returns the number as a value. + */ + case '\\': + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_BACKSLASH; + ct->ctx.tmpval = 10; + ct->execute_state = EXEC_C_STOREVAL; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->execute_state = EXEC_C_STORE1; + return; +/* + * If we see a parenthesis in place of an operand, we want to parse it as + * a complete expression of its own until a matching close parenthesis. + */ + case '(': + ct->ctx.state = STATE_C_SUBEXPRESSION; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; +/* + * We really should not get here if the guy is typing good syntax, so we + * tell him it is junk and we don't allow it. + */ + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unexpected character <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * The following is a substate to parse a number. It will continue until + * it sees a non-digit, and then return to the calling state. + */ + case STATE_C_NUMBER_SUBSTATE: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->ctx.tmpval * 10 + ct->input_byte - '0'; + return; + default: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch*/ +/* + * The following is a substate to parse a hex number. It will continue until + * it sees a non hex digit, and then return to the calling state. + */ + case STATE_C_HEX_NUMBER_SUBSTATE: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 16; + ct->ctx.tmpval += ct->input_byte - '0'; + return; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 16; + ct->ctx.tmpval += ct->input_byte - 'a' + 10; + return; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 16; + ct->ctx.tmpval += ct->input_byte - 'A' + 10; + return; + default: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch*/ +/* + * The following is a substate to parse a octal number. It will continue until + * it sees a non octal digit, and then return to the calling state. + */ + case STATE_C_OCTAL_NUMBER_SUBSTATE: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + ct->ctx.state = STATE_C_OCTAL_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 8; + ct->ctx.tmpval += ct->input_byte - '0'; + return; + case '8': case '9': + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Illegal octal digit <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + default: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch*/ +/* + * The following state gets the value of the specified q-register + */ + case STATE_C_QOPERAND: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_RETURN; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_QVALUE; + return; +/* + * This is just like QOPERAND except the execute state is different + */ + case STATE_C_PERCENT_OPERAND: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_OPERATOR; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_PERCENT_VALUE; + return; +/* + * Here on a input radix character + */ + case STATE_C_RADIX: + switch(ct->input_byte){ + case 'X': case 'x': + ct->ctx.state = STATE_C_HEX_NUMBER; + ct->execute_state = EXEC_C_STOREVAL; + return; + case 'O': case 'o': + ct->ctx.state = STATE_C_OCTAL_NUMBER; + ct->execute_state = EXEC_C_STOREVAL; + return; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Unknown radix ^<%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch */ + + case STATE_C_HEX_NUMBER: + switch(ct->input_byte){ + case '\\': + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_BACKSLASH; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 16; + return; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - '0'; + return; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - 'a' + 10; + return; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - 'A' + 10; + return; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Illegal hex digit <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch */ + + case STATE_C_OCTAL_NUMBER: + switch(ct->input_byte){ + case '\\': + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_BACKSLASH; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 8; + return; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + ct->ctx.state = STATE_C_OCTAL_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - '0'; + return; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Illegal octal digit <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch*/ + +/* + * The next state is the string substate. Outside states call in through here + * which does the initial tec_alloc of the string storage. Strings are limited + * to PARSER_STRING_MAX bytes at this time. + */ + case STATE_C_STRING: + if((ct->ctx.flags & CTOK_M_ATSIGN_SEEN) == 0){ + ct->ctx.delimeter = ESCAPE; + ct->flags &= ~TOK_M_EAT_TOKEN; + } + else { + ct->ctx.delimeter = ct->input_byte; + } + ct->ctx.state = STATE_C_STRING1; + return; + + case STATE_C_STRING1: +/* + * First, check if this is the termination character. This would normally be + * an escape, but could be another character if the user used the @ form. + */ + if( ( (ct->input_byte == ESCAPE) && + (!(ct->ctx.flags & CTOK_M_ATSIGN_SEEN)) + ) || + ( (ct->ctx.flags & CTOK_M_ATSIGN_SEEN) && + (ct->input_byte == ct->ctx.delimeter) && + (ct->ctx.carg != NULL) + ) + ){ + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End IF */ + +/* + * Here when we see a normal character inside of the string. Note that we + * have to do the undo stuff to manage the string since it gets stored in + * a regular string array rather than being spread through the command + * tokens. + */ + {/* Local Block */ + register char *cp; + register struct undo_token *ut; +/* + * Get the address of the string + */ + cp = ct->ctx.carg; +/* + * If there is no string allocated yet, then we have to allocate one. + * Also, if an @ was seen, then record the delimeter character. + */ + if(cp == NULL){ + ct->ctx.carg = cp = tec_alloc(TYPE_C_CBUFF,PARSER_STRING_MAX); + if(cp == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + *cp = '\0'; + ut = allocate_undo_token(uct); + if(ut == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ut->opcode = UNDO_C_MEMFREE; + ut->carg1 = cp; + }/* End IF */ +/* + * Each time we append a character to the string, we also have to allocate + * an undo token which will shorten the string if the input character gets + * rubbed out. + */ + ut = allocate_undo_token(uct); + if(ut == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ut->opcode = UNDO_C_SHORTEN_STRING; + ut->carg1 = cp; + ut->iarg1 = 0; + while(*cp){ + ut->iarg1 += 1; + cp++; + }/* End While */ + if(ut->iarg1 >= (PARSER_STRING_MAX-1)){ + sprintf(tmp_message,"?Exceeded maximum string length of %d", + PARSER_STRING_MAX-1); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ +/* + * Finally, we append the input byte to the string + */ + *cp++ = ct->input_byte; + *cp++ = '\0'; + ct->ctx.state = STATE_C_STRING1; + return; + }/* End Local Block */ +/* + * The following state gets the Q register that will be used with the + * G command. + */ + case STATE_C_GQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_GQREGISTER; + return; +/* + * The following state gets the Q register that will be used with the + * M command. + */ + case STATE_C_MQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_MQREGISTER; + return; +/* + * The following two states implement the EQ command which reads a file into + * the specified Q-register. + */ + case STATE_C_EQQREGISTER1: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte; + ct->ctx.state = STATE_C_STRING; + + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_EQQREGISTER2; + return; + case STATE_C_EQQREGISTER2: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_EQQREGISTER; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state gets the Q register that will be loaded with the + * U command. + */ + case STATE_C_UQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_UQREGISTER; + return; +/* + * The following state gets the Q register that will be loaded with the + * X command. + */ + case STATE_C_XQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_XQREGISTER; + return; +/* + * The following state gets the Q register that will be loaded with the + * previous command line in response to the '*' command. + */ + case STATE_C_SAVECOMMAND: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_SAVECOMMAND; + return; +/* + * The following state gets the Q register which will be pushed onto the + * Q register stack. + */ + case STATE_C_PUSH_QREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_PUSH_QREGISTER; + return; +/* + * The following state gets the Q register which will be popped from the + * Q register stack. + */ + case STATE_C_POP_QREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_POP_QREGISTER; + return; + +/* + * Here to insert characters. The insert state continues until an escape + * is seen. Escapes can also be quoted if they are to be inserted into the + * buffer. If there are many escapes to be inserted, it may be easier to use + * the @ insert command... + */ + case STATE_C_INSERT: + switch(ct->input_byte){ +/* + * Here when we see a non-quoted escape. This terminates the insert. + */ + case ESCAPE: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_INITIALSTATE; + return; +/* + * Here to enter a quote character. This lets you insert characters that would + * normally be handled otherwise. ESCAPE is a good example of this. + */ + case CNTRL_V: + ct->ctx.state = STATE_C_QUOTED_INSERT; + return; +/* + * Here to handle a normal character. It will simply be inserted into the + * edit buffer. + */ + default: + ct->ctx.state = STATE_C_INSERT; + ct->execute_state = EXEC_C_INSERT; + return; + }/* End Switch */ +/* + * Here to insert a character directly following the quote character. If + * this is a special character such as an escape, it will be inserted and + * will not terminate the insert command. + */ + case STATE_C_QUOTED_INSERT: + ct->ctx.state = STATE_C_INSERT; + ct->execute_state = EXEC_C_INSERT; + return; + +/* + * Here for the alternate form of insert, @I/text/ + */ + case STATE_C_ATINSERT: + ct->ctx.tmpval = ct->input_byte; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.state = STATE_C_ATINSERT_PART2; + return; + + case STATE_C_ATINSERT_PART2: + if(ct->input_byte == ct->ctx.tmpval){ + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_ATINSERT_PART2; + ct->execute_state = EXEC_C_INSERT; + return; +/* + * Here after seeing an equals command. This lets us test for two or three + * in a row which output in octal and hex respectively. + */ + case STATE_C_ONE_EQUALS: + if(ct->input_byte != '='){ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End IF */ + ct->ctx.state = STATE_C_TWO_EQUALS; + ct->execute_state = EXEC_C_TWO_EQUALS; + return; + case STATE_C_TWO_EQUALS: + if(ct->input_byte != '='){ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_THREE_EQUALS; + return; + +/* + * Here when we have seen a vertical bar. We have to put a mark here so + * that if the condition is not met, the else clause can be found. + */ + case STATE_C_SKIP_ELSE: + ct->opcode = TOK_C_CONDITIONAL_ELSE; + ct->ctx.state = STATE_C_INITIALSTATE; + return; + + case STATE_C_BACKSLASH: + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_BACKSLASHARG; + return; + + default: + sprintf(tmp_message,"?Dispatched unknown state %d",ct->ctx.state); + error_message(tmp_message); + return; + }/* End Switch */ + +}/* End Routine */ + + + +/* PARSE_CHECK_QNAME - Check that the specified Q-register name is ok + * + * Function: + * + * This routine is called when a parse state wants to verify that the + * specified Q-register name is syntactically correct. It does not + * verify that the Q-register exists, because the execute phase may just + * not have got around to that yet. + */ +int +parse_check_qname(ct,name) +register struct cmd_token *ct; +register char name; +{ +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + + switch(name){ + case '*': + case '_': + case '-': + case '@': + case '?': + return(SUCCESS); + }/* End Switch */ + + if(isalnum((int)name)) return(SUCCESS); + + sprintf(tmp_message,"?Illegal Q-register Name <%c>",name); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return(FAIL); + +}/* End Routine */ diff --git a/tecterm.c b/tecterm.c new file mode 100644 index 0000000..23b506f --- /dev/null +++ b/tecterm.c @@ -0,0 +1,1579 @@ +char *tecterm_c_version = "tecterm.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecterm.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecterm.c + * Terminal Escape Sequence Subroutines (termcap/terminfo et all) + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +#ifdef TERMINFO +#include <curses.h> +#include <term.h> +#endif + +/* + * Global Storage is defined here for lack of a better place. + */ + +/* + * Global Variables + */ + extern int curx; + extern int cury; + extern int term_columns; + extern int term_lines; + char scr_outbuf[SCREEN_OUTPUT_BUFFER_SIZE]; + char *scr_outbuf_ptr; + int scr_outbuf_left; + char insert_delete_line_capability; + + void term_insert_line(); + void term_delete_line(); + void term_puts(); + int term_putc(int); + void term_flush(); + +#ifdef TERMCAP + char *tgetstr(); +#endif + +#ifdef TERMCAP + int termcap_co; + int termcap_li; + int termcap_sg; + char *termcap_cm; + char *termcap_ti; + char *termcap_te; + char *termcap_pc; + char *termcap_so; + char *termcap_se; + char *termcap_cd; + char *termcap_ce; + char *termcap_cs; + char *termcap_sf; + char *termcap_sr; + char *termcap_al; + char *termcap_dl; + char *termcap_AL_arg; + char *termcap_DL_arg; +#endif + +#ifdef TERMINFO + int terminfo_co; + int terminfo_li; + int terminfo_magic_cookie_glitch; +#endif + +/* + * External Routines, etc. + */ + + extern int term_speed; + extern char teco_startup; + extern int tty_input_chan; + extern int tty_output_chan; + +/* + * The following array gives the number of tens of milliseconds per + * character for each speed as returned by gtty. Thus since 300 + * baud returns a 7, there are 33.3 milliseconds per char at 300 baud. + */ +#ifdef TERMCAP +static +short tmspc10[] = { + 0, 2000, 1333, 909, 743, 666, 500, 333, 166, 83, 55, 41, 20, 10 +}; +#endif + +/* TERM_INIT - Initialize the terminal for cursor operations + * + * Function: + * + * This entry point gives the terminal package a chance to initialize + * the terminal for cursor type operations. + */ +void +term_init() +{ +/* + * Initialize our output buffer. + */ + scr_outbuf_ptr = scr_outbuf; + scr_outbuf_left = SCREEN_OUTPUT_BUFFER_SIZE; + +#ifdef TERMCAP +/* + * "TI - String to begin programs that use cm" + */ + term_puts(termcap_ti,1); + +/* + * Set a fast flag which says whether or not terminal is capable of insert and + * delete line functions. + */ + insert_delete_line_capability = 1; + + if(termcap_dl == NULL && termcap_DL_arg == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(termcap_al == NULL && termcap_AL_arg == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(termcap_cs && termcap_sf && termcap_sr){ + insert_delete_line_capability = 1; + }/* End IF */ +#endif + +#ifdef TERMINFO +/* + * Initialize terminfo package + */ + setupterm((char *)0, 1, (int*)0); + + insert_delete_line_capability = 1; + + if(parm_insert_line == NULL && insert_line == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(parm_delete_line == NULL && delete_line == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(change_scroll_region && scroll_forward && scroll_reverse){ + insert_delete_line_capability = 1; + }/* End IF */ + + terminfo_magic_cookie_glitch = magic_cookie_glitch > 0 ? + magic_cookie_glitch : 0; + +#endif + +}/* End Routine */ + + + +/* TERM_FINISH - Here when we are exiting the editor + * + * Function: + * + * 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 +term_finish() +{ + +/* + * We want to leave the cursor at the bottom of the screen + */ + term_goto(0,term_lines-1); + +#ifdef TERMCAP + +/* + * Output any termination escape sequences + */ + term_puts(termcap_te,1); + +#endif + +/* + * Now flush that all out + */ + term_flush(); + +}/* End Routine */ + + + +/* TERM_STANDOUT - Set standout mode on the terminal + * + * Function: + * + * This routine is called to set the highlighting standout + * mode. All characters output from now on will typically + * be reverse video, or whatever attribute the terminal uses. + */ +void +term_standout() +{ + +#ifdef TERMCAP + + if(!termcap_sg) term_puts(termcap_so,1); + +#endif + +#ifdef TERMINFO + + if(terminfo_magic_cookie_glitch <= 0){ + tputs(enter_standout_mode,1,term_putc); + } + +#endif + +}/* End Routine */ + +/* TERM_STANDEND - Clear standout mode on the terminal + * + * Function: + * + * This routine is called to reset the highlighting standout + * mode. All characters output from now on will be in the + * normal rendition mode. + */ +void +term_standend() +{ + +#ifdef TERMCAP + + if(!termcap_sg) term_puts(termcap_se,1); + +#endif + +#ifdef TERMINFO + + if(terminfo_magic_cookie_glitch <= 0){ + tputs(exit_standout_mode,1,term_putc); + } + +#endif + +}/* End Routine */ + +/* TERM_CLRTOEOL - Clear to End Of Line + * + * Function: + * + * This routine sends the ESCape sequence to clear all characters + * from the present position until the end of the line. + */ +void +term_clrtoeol() +{ +#ifdef TERMCAP + + term_puts(termcap_ce,1); + +#endif + +#ifdef TERMINFO + + tputs(clr_eol,1,term_putc); + +#endif + +}/* End Routine */ + +/* TERM_CLRTOBOT - Clear to End Of Screen + * + * Function: + * + * This routine sends the ESCape sequence to clear all characters + * from the present position until the end of the terminal screen. + */ +void +term_clrtobot() +{ + +#ifdef TERMCAP + + term_puts(termcap_cd,1); + + +#endif + +#ifdef TERMINFO + + tputs(clr_eos,1,term_putc); + +#endif + +}/* End Routine */ + + + +/* TERM_INSERT_LINE - Perform an insert-line terminal function + * + * Function: + * + * This routine is called to output the insert-line escape sequence. + * It checks to see what insert-line capabilities the terminal has, + * and tries to use the optimum one. + */ +void +term_insert_line(position,count) +int position; +register int count; +{ +/* + * Test whether the terminal supports an insert line escape sequence which + * takes an argument + */ +#ifdef TERMCAP + if(count > 1 && termcap_AL_arg){ + term_goto(0,position); + term_putnum(termcap_AL_arg,count,term_lines-position); + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(count > 1 && parm_insert_line){ + term_goto(0,position); + tputs( + tparm(parm_insert_line,count), + term_lines-position, + term_putc + ); + return; + }/* End IF */ +#endif + +/* + * Ok, here if either the terminal does not support insert line with an + * argument, or if count happens to be 1 + */ +#ifdef TERMCAP + if(termcap_al){ + term_goto(0,position); + while(count--){ + term_puts(termcap_al,term_lines-position); + }/* End While */ + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(insert_line){ + term_goto(0,position); + while(count--){ + tputs(insert_line,term_lines-position,term_putc); + }/* End While */ + return; + }/* End IF */ +#endif + + +/* + * Hmm, termcap_al must be undefined. That must mean that cs is defined and + * that we have to do this by using scroll regions. + */ + if(position) term_scroll_region(position,term_lines-1); + if(curx != 0 || cury != position) term_goto(0,position); + while(count--){ + +#ifdef TERMCAP + term_puts(termcap_sr,term_lines-position); +#endif + +#ifdef TERMINFO + tputs(scroll_reverse,term_lines-position,term_putc); +#endif + + }/* End While */ + if(position) term_scroll_region(0,term_lines-1); + +#ifdef TERMCAP + if(curx != 0 || cury != position) term_goto(0,term_lines-1); +#endif +#ifdef TERMINFO + curx = cury = -1; /* Unsure of present position... */ +#endif + +}/* End Routine */ + + + +/* TERM_DELETE_LINE - Perform an delete-line terminal function + * + * Function: + * + * This routine is called to output the delete-line escape sequence. + * It checks to see what delete-line capabilities the terminal has, + * and tries to use the optimum one. + */ +void +term_delete_line(position,count) +int position; +register int count; +{ + term_goto(0,position); + +/* + * Test whether the terminal supports an delete line escape sequence which + * takes an argument + */ +#ifdef TERMCAP + if(count > 1 && termcap_DL_arg){ + term_putnum(termcap_DL_arg,count,term_lines-position); + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(count > 1 && parm_delete_line){ + term_goto(0,position); + tputs( + tparm(parm_delete_line,count), + term_lines-position, + term_putc + ); + return; + }/* End IF */ +#endif + +/* + * Ok, here if either the terminal does not support delete line with an + * argument, or if count happens to be 1 + */ +#ifdef TERMCAP + if(termcap_dl){ + while(count--){ + term_puts(termcap_dl,term_lines-position); + }/* End While */ + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(delete_line){ + term_goto(0,position); + while(count--){ + tputs(delete_line,term_lines-position,term_putc); + }/* End While */ + return; + }/* End IF */ +#endif + +/* + * Hmm, termcap_dl must be undefined. That must mean that cs is defined and + * that we have to do this by using scroll regions. + */ + if(position) term_scroll_region(position,term_lines-1); + if(curx != 0 || cury != (term_lines-1)) term_goto(0,term_lines-1); + while(count--){ + +#ifdef TERMCAP + term_puts(termcap_sf,term_lines-position); +#endif + +#ifdef TERMINFO + tputs(scroll_forward,term_lines-position,term_putc); +#endif + + }/* End While */ + if(position) term_scroll_region(0,term_lines-1); + +#ifdef TERMCAP + if(curx != 0 || cury != (term_lines-1)) term_goto(0,term_lines-1); +#endif +#ifdef TERMINFO + curx = cury = -1; /* Unsure of present position... */ +#endif + + +}/* End Routine */ + + + +#ifdef TERMCAP + +/* TERM_PUTNUM - Output a termcap string which takes a single argument + * + * Function: + * + * This routine is called to output a termcap string which has an + * imbedded %d to be replaced with the single argument. + */ +int +term_putnum(termcap_string,argument,affected) +char *termcap_string; +int argument; +int affected; +{ +register char *cp; +register char *dp; +register int c; +static char result[40]; + + cp = termcap_string; + if(cp == NULL) return(FAIL); + dp = result; + +/* + * Loop until we hit the end of the string + */ + while((c = *cp++)){ + if(c != '%'){ + *dp++ = c; + continue; + }/* End IF */ + + switch(c = *cp++){ + case 'd': + if(argument < 10) goto one; + if(argument < 100) goto two; + /* fall through */ + case '3': + *dp++ = (argument / 100) + '0'; + argument %= 100; + /* fall through */ + case '2': +two: + *dp++ = argument / 10 | '0'; +one: + *dp++ = argument % 10 | '0'; + continue; + case '%': + *dp++ = c; + continue; + + case 'B': + argument = (argument/10 << 4) + argument%10; + continue; + + case 'D': + argument = argument - 2 * (argument%16); + continue; + case 'p': + cp++; /* eat p1, p2 type stuff */ + continue; + + default: + return(FAIL); + }/* End Switch */ + }/* End While */ + + *dp++ = '\0'; + term_puts(result,affected); + return(SUCCESS); + +}/* End Routine */ + +#endif + + + +#ifdef TERMCAP + +/* TERM_PUTS - Output a termcap string with padding + * + * Function: + * + * This routine is called to output a termcap string with padding added. + */ +void +term_puts(termcap_string,lines_affected) +register char *termcap_string; +int lines_affected; +{ +register int delay; +register int mspc10; + +/* + * If this termcap string is undefined, we can't do much for him. + */ + if(termcap_string == NULL) return; + +/* + * Convert the number representing the delay + */ + delay = 0; + while(isdigit((int)*termcap_string)){ + delay = delay * 10 + *termcap_string++ - '0'; + }/* End While */ + +/* + * If there is a decimal place, read up to one decimal and then + * eat all remaining decimal places. + */ + delay *= 10; + if(*termcap_string == '.'){ + termcap_string++; + if(isdigit((int)*termcap_string)) delay += *termcap_string - '0'; + while(isdigit((int)*termcap_string)) termcap_string++; + }/* End IF */ + +/* + * If the delay is followed by a `*', then multiply by the affected + * lines count. + */ + if(*termcap_string == '*'){ + termcap_string++; + delay *= lines_affected; + }/* End IF */ + +/* + * Output all the rest of the string + */ + while(*termcap_string){ + *scr_outbuf_ptr++ = *termcap_string++; + scr_outbuf_left -= 1; + if(scr_outbuf_left <= 0){ + term_flush(); + }/* End IF */ + }/* End While */ + +/* + * If no delay is needed, or we can't figure out what the output + * speed is, then we just give up on the delay. + */ + if(delay == 0) return; + if(term_speed <= 0) return; + if(term_speed >= (sizeof(tmspc10)/sizeof(tmspc10[0]))) return; + +/* + * Round up by half a character frame, and then do the delay. + */ + mspc10 = tmspc10[term_speed]; + delay += mspc10 / 2; + delay /= mspc10; + while(delay > 0){ + delay -= 1; + *scr_outbuf_ptr++ = *termcap_pc; + scr_outbuf_left -= 1; + if(scr_outbuf_left <= 0){ + term_flush(); + }/* End IF */ + }/* End While */ + +}/* End Routine */ + +#endif + + + +/* TERM_GOTO - Send sequence to position the terminal cursor + * + * Function: + * + * This routine provides direct cursoring capability by using the + * CM termcap/terminfo string. This string looks like a printf which + * must be interpreted by this code. This allows pretty much + * arbitrary terminal escape sequences to be supported. The meaning + * of the special characters in the string are: + * + * %d - Same as in printf + * %2 - like %2d + * %3 - like %3d + * %. - gives %c hacking special case characters + * %+x - like %c but adding x first + * + * The following codes affect the state but don't use up a value + * + * %>xy if value > x add y + * %r reverses row column + * %i increments row column (for one origin indexing) + * %% gives % + * %B BCD (2 decimal digits encoded in one byte) + * %D Delta Data (backwards bcd) + * + * all other characters simply get output + */ +int +term_goto(dest_x,dest_y) +int dest_x; +int dest_y; +{ +#ifdef TERMCAP +register char *cp; +register char *dp; +register int c; +register int which; +static char result[40]; +static char added[10]; +char *UP = NULL; +char *BC = NULL; +#endif /* TERMCAP */ + +#ifdef TERMCAP +int oncol = 0; +#endif /* TERMCAP */ + +#ifdef TERMINFO + if(cursor_address == NULL) return(FAIL); + tputs(tparm(cursor_address,dest_y,dest_x),1,term_putc); + curx = dest_x; + cury = dest_y; + return(SUCCESS); +#endif + +#ifdef TERMCAP + cp = termcap_cm; + + if(cp == NULL) return(FAIL); + dp = result; + which = dest_y; + + added[0] = '\0'; + + curx = dest_x; + cury = dest_y; + +/* + * Loop until we hit the end of the string + */ + while((c = *cp++)){ + if(c != '%'){ + *dp++ = c; + continue; + }/* End IF */ + + switch(c = *cp++){ + case 'p': + cp++; /* for p1, p2, or p<n>, eat the digit, because I don't understand what it means yet */ + continue; + case 'n': + dest_x ^= 0140; + dest_y ^= 0140; + goto setwhich; + case 'd': + if(which < 10) goto one; + if(which < 100) goto two; + /* fall through */ + case '3': + *dp++ = (which / 100) + '0'; + which %= 100; + /* fall through */ + case '2': + two: + *dp++ = which / 10 | '0'; + one: + *dp++ = which % 10 | '0'; + swap: + oncol = 1 - oncol; + setwhich: + which = oncol ? dest_x : dest_y; + continue; + + case '>': + if (which > *cp++) which += *cp++; + else cp++; + continue; + + case '+': + which += *cp++; + /* fall into... */ + + case '.': + if( + which == 0 || + which == CNTRL_D || + which == '\t' || + which == '\n' + ){ + if (oncol || UP){ + do { + (void) strcat( + added, + oncol ? (BC ? BC : "\b") : UP + ); + which++; + } while (which == '\n'); + }/* End IF */ + }/* End IF */ + + *dp++ = which; + goto swap; + + case 'r': + oncol = 1; + goto setwhich; + + case 'i': + dest_x++; + dest_y++; + which++; + continue; + + case '%': + *dp++ = c; + continue; + + case 'B': + which = (which/10 << 4) + which%10; + continue; + + case 'D': + which = which - 2 * (which%16); + continue; + + default: +fprintf(stderr,"\n\r\n\rBad char in TGOTO string: %c\n",c); +sleep(5); + return(FAIL); + }/* End Switch */ + }/* End While */ + + (void) strcpy(dp,added); + term_puts(result,1); + return(SUCCESS); + +#else + return(SUCCESS); +#endif + + +}/* End Routine */ + + + +#ifdef TERMCAP + +/* TERM_SCROLL_REGION - Send sequence to set up a scroll region ala VT100 + * + * Function: + * + * This routine is identical to term_goto in most respects, except that + * it is sending the 'set scroll region' escape sequence instead of the + * cursor position sequence. The following escapes are defined for + * substituting row and column: + * + * %d - Same as in printf + * %2 - like %2d + * %3 - like %3d + * %. - gives %c hacking special case characters + * %+x - like %c but adding x first + * + * The following codes affect the state but don't use up a value + * + * %>xy if value > x add y + * %r reverses row column + * %i increments row column (for one origin indexing) + * %% gives % + * %B BCD (2 decimal digits encoded in one byte) + * %D Delta Data (backwards bcd) + * + * all other characters simply get output + */ +int +term_scroll_region(top,bottom) +int top; +int bottom; +{ +register char *cp; +register char *dp; +register int c; +register int which; +int onbottom = 0; +static char result[40]; +static char added[10]; + +char *UP = NULL; +char *BC = NULL; + + cp = termcap_cs; + if(cp == NULL) return(FAIL); + dp = result; + which = top; + + added[0] = '\0'; + + curx = -1; + cury = -1; + +/* + * Loop until we hit the end of the string + */ + while((c = *cp++)){ + if(c != '%'){ + *dp++ = c; + continue; + }/* End IF */ + + switch(c = *cp++){ + case 'n': + top ^= 0140; + bottom ^= 0140; + goto setwhich; + case 'd': + if(which < 10) goto one; + if(which < 100) goto two; + /* fall through */ + case '3': + *dp++ = (which / 100) + '0'; + which %= 100; + /* fall through */ + case '2': +two: + *dp++ = which / 10 | '0'; +one: + *dp++ = which % 10 | '0'; +swap: + onbottom = 1 - onbottom; +setwhich: + which = onbottom ? bottom : top; + continue; + + case '>': + if (which > *cp++) which += *cp++; + else cp++; + continue; + + case '+': + which += *cp++; + /* fall into... */ + + case '.': + if( + which == 0 || + which == CNTRL_D || + which == '\t' || + which == '\n' + ){ + if (onbottom || UP){ + do { + (void) strcat( + added, + onbottom ? (BC ? BC : "\b") : UP + ); + which++; + } while (which == '\n'); + }/* End IF */ + }/* End IF */ + + *dp++ = which; + goto swap; + + case 'r': + onbottom = 1; + goto setwhich; + + case 'i': + top++; + bottom++; + which++; + continue; + + case '%': + *dp++ = c; + continue; + + case 'B': + which = (which/10 << 4) + which%10; + continue; + + case 'D': + which = which - 2 * (which%16); + continue; + + default: + return(FAIL); + }/* End Switch */ + }/* End While */ + + (void) strcpy(dp,added); + term_puts(result,1); + return(SUCCESS); + +}/* End Routine */ + +#endif + +#ifdef TERMINFO + +/* TERM_SCROLL_REGION - Set up a scroll region + * + * Function: + * + * This routine is called to set up a scrolling region on the + * terminal. This is typically being used to emulate insert/delete + * line. + */ +int +term_scroll_region(top,bottom) +int top; +int bottom; +{ + if(change_scroll_region == NULL) return(FAIL); + + tputs(tparm(change_scroll_region,top,bottom),1,term_putc); + return(SUCCESS); + +}/* End Routine */ + +#endif + + + +/* TERM_PUTC - Output a single character + * + * Function: + * + * This routine is called to output a single character to the screen + * output buffer. If this fills the output buffer, flush the buffer. + */ +int +term_putc(data) +//char data; +int data; +{ + *scr_outbuf_ptr++ = data; + scr_outbuf_left -= 1; + + if(scr_outbuf_left <= 0){ + term_flush(); + }/* End IF */ + + return( data ); + +}/* End Routine */ + +/* TERM_FLUSH - Flush any data in the output buffer + * + * Function: + * + * This routine flushes any bytes sitting in the screen output + * buffer to the terminal. + */ +void +term_flush() +{ + register int i; + + i = SCREEN_OUTPUT_BUFFER_SIZE - scr_outbuf_left; + +#ifdef UNIX + if(i) write(tty_output_chan,scr_outbuf,(unsigned)i); +#endif + +#ifdef VMS + if(i){ + i = sys$qiow(0,tty_output_chan,IO$_WRITEVBLK|IO$M_NOFORMAT,0,0,0, + scr_outbuf,i,0,0,0,0); + if(!(i & STS$M_SUCCESS)) exit(i); + }/* End IF*/ +#endif + + scr_outbuf_left = SCREEN_OUTPUT_BUFFER_SIZE; + scr_outbuf_ptr = scr_outbuf; + +}/* End Routine */ + + + +/* INIT_TERMCAP - Read in the termcap description of the tty + * + * Function: + * + * This routine looks up the terminal description in the termcap file, and + * sets up the stuff for the screen package. It also checks that the tty + * has certain basic capabilities that we require. + */ +int +init_term_description() +{ + char termcap_description_buffer[TERMCAP_BUFFER_SIZE]; + char *terminal_name; +#ifdef TERMCAP + char *cp; +#endif + char temp_string[256]; + register int status; + + if(teco_startup == NO) return( SUCCESS ); + + if((terminal_name = getenv("TECO_TERM")) == NULL){ + if((terminal_name = getenv("TERM")) == NULL){ + tec_error(ENOENT,"Environment variable TERM not found"); + }/* End IF */ + }/* End IF */ + + status = tgetent(termcap_description_buffer,terminal_name); + if(status == -1) tec_error(EIO,"Cannot read termcap file"); + if(status == 0){ + sprintf(temp_string,"No termcap entry for terminal type %s", + terminal_name); + tec_error(ESRCH,temp_string); + }/* End IF */ + +/* + * Determine how many rows and columns this terminal has. If either number + * is already non-zero, then it has been set up by some previous code that + * really knows the size of the terminal window, and in this case, we will + * leave it alone. + */ +#ifdef TERMCAP + if(!term_columns) term_columns = tgetnum("co"); + if(!term_lines) term_lines = tgetnum("li"); +#endif + +#ifdef TERMINFO + if(!term_columns) term_columns = tgetnum("columns"); + if(!term_lines) term_lines = tgetnum("lines"); +#endif + +#ifdef TERMCAP +/* + * Now we read the escape sequence description strings for termcap + */ + +/* + * CM is the basic cursor movement string. This allows you to position the + * cursor to any line,column on the terminal. + */ + cp = &temp_string[0]; + termcap_cm = NULL; + if(tgetstr("cm",&cp)){ + termcap_cm = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_cm == NULL) return(FAIL); + (void) strcpy(termcap_cm,temp_string); + }/* End IF */ + else { + tec_error(ENOTTY,"?TECO: Terminal does not support cursor addressing"); + }/* End Else */ + +/* + * TI is a string which needs to be output at init time by programs + * which are going to use CM + */ + cp = &temp_string[0]; + termcap_ti = NULL; + if(tgetstr("ti",&cp)){ + termcap_ti = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_ti == NULL) return(FAIL); + (void) strcpy(termcap_ti,temp_string); + }/* End IF */ + +/* + * TE is a string which needs to be output before exit time by programs + * which have been using CM + */ + cp = &temp_string[0]; + termcap_te = NULL; + if(tgetstr("te",&cp)){ + termcap_te = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_te == NULL) return(FAIL); + (void) strcpy(termcap_te,temp_string); + }/* End IF */ + +/* + * PC is the padd character which is used to fill for terminals which + * require some time after certain sequences. + */ + cp = &temp_string[0]; + termcap_pc = NULL; + if(tgetstr("pc",&cp)){ + termcap_pc = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_pc == NULL) return(FAIL); + (void) strcpy(termcap_pc,temp_string); + }/* End IF */ + else termcap_pc = ""; + +/* + * SG is the number of screen locations it takes to set or clear + * standout mode. It alerts us to brain damaged magic cookie ttys + */ + termcap_sg = tgetnum("sg"); + if(termcap_sg < 0) termcap_sg = 0; + +/* + * SO is the string which will cause the terminal to enter reverse + * video (stand-out) mode. + */ + cp = &temp_string[0]; + termcap_so = NULL; + if(tgetstr("so",&cp)){ + termcap_so = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_so == NULL) return(FAIL); + (void) strcpy(termcap_so,temp_string); + }/* End IF */ + +/* + * SE is the string which will cause the terminal to leave reverse + * video (stand-out) mode and re-enter normal drawing mode. + */ + cp = &temp_string[0]; + termcap_se = NULL; + if(tgetstr("se",&cp)){ + termcap_se = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_se == NULL) return(FAIL); + (void) strcpy(termcap_se,temp_string); + }/* End IF */ + +/* + * CD is the string which will cause the terminal to clear from + * the present cursor location to the end of the screen + */ + cp = &temp_string[0]; + termcap_cd = NULL; + if(tgetstr("cd",&cp)){ + termcap_cd = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_cd == NULL) return(FAIL); + (void) strcpy(termcap_cd,temp_string); + }/* End IF */ + +/* + * CE is the string which will cause the terminal to clear from + * the present cursor location to the end of the current line + */ + cp = &temp_string[0]; + termcap_ce = NULL; + if(tgetstr("ce",&cp)){ + termcap_ce = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_ce == NULL) return(FAIL); + (void) strcpy(termcap_ce,temp_string); + }/* End IF */ + +/* + * cs is the string which will cause the terminal to change the scroll region + * This is mostly useful in emulating insert / delete line on vt100 terminals + */ + cp = &temp_string[0]; + termcap_cs = NULL; + if(tgetstr("cs",&cp)){ + termcap_cs = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_cs == NULL) return(FAIL); + (void) strcpy(termcap_cs,temp_string); + }/* End IF */ + +/* + * sf is the string which will cause the terminal to scroll forward. This is + * used in conjunction with scroll regions for delete line. + */ + cp = &temp_string[0]; + termcap_sf = NULL; + if(tgetstr("sf",&cp)){ + termcap_sf = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_sf == NULL) return(FAIL); + (void) strcpy(termcap_sf,temp_string); + }/* End IF */ + +/* + * sr is the string which will cause the terminal to scroll in reverse. This + * is used in conjunction with scroll regions for insert line. + */ + cp = &temp_string[0]; + termcap_sr = NULL; + if(tgetstr("sr",&cp)){ + termcap_sr = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_sr == NULL) return(FAIL); + (void) strcpy(termcap_sr,temp_string); + }/* End IF */ + +/* + * al is the string which will cause the terminal to insert a blank line at the + * present cursor location. + */ + cp = &temp_string[0]; + termcap_al = NULL; + if(tgetstr("al",&cp)){ + termcap_al = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_al == NULL) return(FAIL); + (void) strcpy(termcap_al,temp_string); + }/* End IF */ + +/* + * dl is the string which will cause the terminal to delete the current line. + * The following lines will all scroll up. + */ + cp = &temp_string[0]; + termcap_dl = NULL; + if(tgetstr("dl",&cp)){ + termcap_dl = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_dl == NULL) return(FAIL); + (void) strcpy(termcap_dl,temp_string); + }/* End IF */ + +/* + * AL is the string which will cause the terminal to insert a blank line at the + * present cursor location. + */ + cp = &temp_string[0]; + termcap_AL_arg = NULL; + if(tgetstr("AL",&cp)){ + termcap_AL_arg = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_AL_arg == NULL) return(FAIL); + (void) strcpy(termcap_AL_arg,temp_string); + }/* End IF */ + +/* + * DL is the string which will cause the terminal to delete the current line. + * The following lines will all scroll up. + */ + cp = &temp_string[0]; + termcap_DL_arg = NULL; + if(tgetstr("DL",&cp)){ + termcap_DL_arg = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_DL_arg == NULL) return(FAIL); + (void) strcpy(termcap_DL_arg,temp_string); + }/* End IF */ + +#endif + + return(SUCCESS); + +}/* End Routine */ + + + +#ifdef VMS +char *current_termcap_description; +char *termcap = +"d1|vt100-80|vt-100|dec vt100:\ +:co#80:li#24:cl=50\\E[;H\\E[2J:bs:am:cm=5\\E[%i%2;%2H:nd=2\\E[C:up=2\\E[A:\ +:ce=3\\E[K:cd=50\\E[J:so=2\\E[7m:se=2\\E[m:us=2\\E[4m:ue=2\\E[m:\ +:is=\\E>\\E[?3l\\E[?4l\\E[?5l\\E[?7h\\E[?8h:ks=\\E[?1h\\E=:ke=\\E[?1l\\E>:\ +:ku=\\E[A:kd=\\E[B:kr=\\E[C:kl=\\E[D:\ +:cs=\\E[%i%d;%dr:sf=5\\ED:\ +:kh=\\E[H:k1=\\EOP:k2=\\EOQ:k3=\\EOR:k4=\\EOS:pt:sr=5\\EM:xn:xv:"; + + +/* TGETENT - Version of TGETENT for Non-UNIX systems + * + * Function: + * + * This routine is used on non-UNIX systems to return a termcap + * description into the specified buffer. + */ +int +tgetent(buffer,term_name) +char *buffer; +char *term_name; +{ +register char *cp = NULL; +register char *dp; +char *temp_buffer = NULL; +char *termcap_filename; +FILE *fd; + +/* + * If current_termcap_description isn't set up yet, check for a logical + * name which points to a termcap file, and see if we can find the entry + * in there. + */ + if(current_termcap_description == NULL){ + termcap_filename = getenv("TERMCAP"); + if(termcap_filename != NULL){ + fd = fopen(termcap_filename,"r"); + if(fd != NULL){ + temp_buffer = malloc(TERMCAP_BUFFER_SIZE); + if(temp_buffer == NULL) return(FAIL); + if(scan_termcap(fd,temp_buffer,term_name)){ + cp = temp_buffer; + }/* End IF */ + fclose(fd); + }/* End IF */ + }/* End IF */ + }/* End IF */ + + if(cp == NULL) cp = termcap; + dp = current_termcap_description = buffer; + + while(*cp != NULL){ + if(cp[0] == '\\'){ + if(cp[1] == 'E' || cp[1] == 'e'){ + *dp++ = ESCAPE; + cp += 2; + continue; + }/* End IF */ + }/* End IF */ + + *dp++ = *cp++; + + }/* End While */ + + if(temp_buffer){ + free(temp_buffer); + temp_buffer = NULL; + }/* End IF */ + + return(1); + +}/* End Routine */ + +/* SCAN_TERMCAP - Attempt to find a terminal description in the termcap file + * + * Function: + * + * This routine scans the termcap file for an entry which matches our + * terminal, and returns non-zero if it finds it. + */ +scan_termcap(fd,temp_buffer,term_name) +FILE *fd; +char *temp_buffer; +char *term_name; +{ +register char *cp; +char *temp_name; +char status; + +/* + * Loop reading terminal descriptions into the buffer + */ + while(1){ + cp = temp_buffer; + bzero(cp,TERMCAP_BUFFER_SIZE); + if(fgets(cp,TERMCAP_BUFFER_SIZE,fd) == NULL) return(0); + if(*cp == '#') continue; +/* + * Ok, we have the start of an entry. Check for continuation. + */ + while(cp[0]){ + if(cp[0] == '\n'){ + cp[1] = '\0'; + break; + }/* End IF */ + if(cp[0] == '\\' && cp[1] == '\n'){ + if(fgets(cp,TERMCAP_BUFFER_SIZE-(cp-temp_buffer),fd) == NULL){ + cp[0] = '\n'; cp[1] = '\0'; + }/* End IF */ + }/* End IF */ + cp++; + }/* End While */ + + cp = temp_name = temp_buffer; + + while(*cp){ + if(*cp == '|'){ + *cp = '\0'; + status = strcmp(temp_name,term_name); + *cp = '|'; + if(status == 0) return(1); + cp++; + temp_name = cp; + continue; + }/* End IF */ + if(*cp == ':'){ + *cp = '\0'; + status = strcmp(temp_name,term_name); + *cp = ':'; + if(status == 0) return(1); + break; + }/* End IF */ + cp++; + }/* End While */ + + }/* End While */ + + return(0); + +}/* End Routine */ + + + +/* TGETNUM - Version of TGETNUM for Non-UNIX systems + * + * Function: + * + * This routine is used on non-UNIX systems to return a specific + * termcap number from the termcap description of our terminal. + */ +int +tgetnum(num_name) +char *num_name; +{ +register char *cp; +int temp; +char minus_seen = 0; + + cp = current_termcap_description; + if(strlen(num_name) != 2){ + fprintf(stderr,"TGETNUM: capability name %s should be 2 bytes long\n", + num_name); + exit(1); + }/* End IF */ + + for(cp = current_termcap_description; *cp != NULL; cp++){ + if(cp[0] != ':') continue; + if(cp[1] != num_name[0]) continue; + if(cp[2] != num_name[1]) continue; + + cp += 3; + if(*cp == '#') cp++; + temp = 0; + if(*cp == '-'){ + minus_seen = 1; + cp++; + }/* End IF */ + + while(isdigit(*cp)){ + temp = temp * 10 + *cp++ - '0'; + }/* End While */ + + if(minus_seen) temp = 0 - temp; + return(temp); + + }/* End FOR */ + + return(0); + +}/* End Routine */ + +/* TGETSTR - Version of TGETSTR for Non-UNIX systems + * + * Function: + * + * This routine is used on non-UNIX systems to return a specific + * termcap string from the termcap description of our terminal. + */ +char * +tgetstr(str_name,buffer_ptr) +char *str_name; +char **buffer_ptr; +{ +register char *cp; +register char *dp; +char *buffer; + + buffer = *buffer_ptr; + cp = current_termcap_description; + dp = buffer; + if(strlen(str_name) != 2){ + fprintf(stderr,"TGETSTR: capability name %s should be 2 bytes long\n", + str_name); + exit(1); + }/* End IF */ + + for(cp = current_termcap_description; *cp != NULL; cp++){ + if(cp[0] != ':') continue; + if(cp[1] != str_name[0]) continue; + if(cp[2] != str_name[1]) continue; + + cp += 3; + if(*cp == '=') cp += 1; + while(*cp != ':' && *cp != NULL){ + *dp++ = *cp++; + }/* End While */ + *dp++ = '\0'; + + return(buffer); + + }/* End FOR */ + + return(NULL); +} + +/* END OF VMS CONDITIONAL CODE */ + +#endif diff --git a/tecundo.c b/tecundo.c new file mode 100644 index 0000000..e0f9422 --- /dev/null +++ b/tecundo.c @@ -0,0 +1,504 @@ +char *tecundo_c_version = "tecundo.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecundo.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecundo.c + * Subroutines to implement the undo capabilities of the parser + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern struct search_buff search_string; + extern char user_message[]; + extern char exit_flag; + extern char immediate_execute_flag; + extern int last_search_pos1,last_search_pos2; + extern int last_search_status; + + extern struct buff_header *curbuf,*buffer_headers; + extern struct buff_header *qregister_push_down_list; + extern struct tags *current_tags; + void tecundo_list(struct undo_token *); + + + +/* PARSER_UNDO - Execute an UNDO command + * + * Function: + * + * This routine is called with an undo token to reverse some changes + * to the program state. + */ +int +parser_undo(ut) +register struct undo_token *ut; +{ +char tmp_message[LINE_BUFFER_SIZE]; +register struct cmd_token *ct; + + PREAMBLE(); + + switch(ut->opcode){ +/* + * CHANGEDOT is used to undo a movement of dot. iarg1 contains the buffer + * position we want to go back to. + */ + case UNDO_C_CHANGEDOT: + curbuf->dot = ut->iarg1; + break; +/* + * DELETE is used to undo the addition of characters to the buffer. iarg1 + * contains the buffer position to begin deleting at, iarg2 contains the + * number of characters that should be deleted. + */ + case UNDO_C_DELETE: + if(ut->iarg2 > 0){ + buff_delete((struct buff_header *)ut->carg1,ut->iarg1, + ut->iarg2); + }/* End IF */ + break; +/* + * INSERT is used to undo the deletion of characters from the buffer. + * iarg1 contains the position in the buffer of the deletion. iarg2 + * contains a count of the number of bytes which were deleted. carg1 + * is a pointer to a tec_alloc'ed buffer which holds the deleted bytes. + */ + case UNDO_C_INSERT: + buff_insert(curbuf,ut->iarg1,ut->carg1,ut->iarg2); + break; +/* + * UQREGISTER is used to undo the loading of an integer value into a + * Q register. iarg1 is the Q register name, iarg2 is the old value. + */ + case UNDO_C_UQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; + + hbp = buff_qfind(ut->iarg1,0); + if(hbp == NULL){ + sprintf(tmp_message,"?Cannot find Q-register <%c>", + ut->iarg1); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + hbp->ivalue = ut->iarg2; + break; + }/* End Local Block */ +/* + * MEMFREE allows us to free up memory allocated to a command token or an + * undo token. + */ + case UNDO_C_MEMFREE: + tec_release(TYPE_C_CBUFF,ut->carg1); + ut->carg1 = NULL; + return(SUCCESS); +/* + * SHORTEN_STRING allows us to handle rubouts etc while a string was being + * built. + */ + case UNDO_C_SHORTEN_STRING: + {/* Local Block */ + register int i; + register char *cp; +/* + * IARG1 holds the length we want to shorten the string to. If the string is + * already shorter than that, we leave it alone of course. Note that we use + * IARG2 to hold the address of the string. + */ + i = ut->iarg1; + cp = ut->carg1; + while(i--){ + if(*cp) cp++; + }/* End While */ + *cp = '\0'; + return(SUCCESS); + }/* End Local Block */ +/* + * SET_SEARCH_STRING sets the default search string + */ + case UNDO_C_SET_SEARCH_STRING: + return(SUCCESS); + +/* + * CHANGEBUFF changes the current buffer. We pass a flag to buff_openbuffnum + * so that if we return to buffer zero, it doesn't build a new map. + */ + case UNDO_C_CHANGEBUFF: + + buff_openbuffnum(ut->iarg1,0); + return(SUCCESS); + +/* + * MACRO undoes all the changes a macro made + */ + case UNDO_C_MACRO: + ct = (struct cmd_token *)ut->carg1; + while(ct->next_token) ct = ct->next_token; + while(ct){ +/* + * If this command token has an undo list associated with it, call the + * routine which will undo the entire undo list under this token. + */ + if(ct->undo_list){ + tecundo_list(ct->undo_list); + ct->undo_list = NULL; + }/* End IF */ +/* + * If the command token is not at the head of the list, back up one so + * as to keep it's address while we free up this command token. + */ + if(ct->prev_token){ + ct = ct->prev_token; + free_cmd_token(ct->next_token); + ct->next_token = NULL; + }/* End IF */ +/* + * Else, it IS the head of the list and we can simply free it up and break + * out of the loop. + */ + else { + free_cmd_token(ct); + ut->carg1 = NULL; + break; + }/* End Else */ + }/* End While */ + + return(SUCCESS); + +/* + * SHORTEN_MESSAGE handles rubouts in ^A messages + */ + case UNDO_C_SHORTEN_MESSAGE: + {/* Local Block */ + register int i; + i = strlen(user_message); + if(i > 0) user_message[i-1] = '\0'; + return(SUCCESS); + }/* End Local Block */ + +/* + * BULK_INSERT is used to undo a deleted region + */ + case UNDO_C_BULK_INSERT: + buff_bulk_insert(curbuf, + ut->iarg1,ut->iarg2,(struct buff_line *)ut->carg1); + return(SUCCESS); +/* + * SET_IMMEDIATE_MODE resets the immediate execute state + */ + case UNDO_C_SET_IMMEDIATE_MODE: + immediate_execute_flag = ut->iarg1; + return(SUCCESS); + +/* + * PUSH is used to re-push a Q register onto the Q register push down + * list (stack). + */ + case UNDO_C_PUSH: + push_qregister(ut->iarg1); + return(SUCCESS); +/* + * POP is used to undo a Q register push. It just takes an entry off of + * the Q register pushdown list and destroys it. + */ + case UNDO_C_POP: + {/* Local Block */ + register struct buff_header *hbp; + + hbp = qregister_push_down_list; + if(hbp){ + qregister_push_down_list = hbp->next_header; +/* + * Place it on the main buffer list so that buff_destroy can find it + */ + hbp->next_header = buffer_headers; + buffer_headers = hbp; + buff_destroy(hbp); + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ + +/* + * MODIFIED is used to flag a change in the state of the current edit buffers + * modified status. We undo it by clearing the modified flag and clearing the + * indication in the buffer status line display. + */ + case UNDO_C_MODIFIED: + ((struct buff_header *)(ut->carg1))->ismodified = NO; + if(screen_label_line((struct buff_header *)ut->carg1, + "",LABEL_C_MODIFIED) == FAIL){ + return(FAIL); + }/* End IF */ + return(SUCCESS); +/* + * This function undoes the creation of a buffer by the EB command + */ + case UNDO_C_CLOSEBUFF: + buff_destroy((struct buff_header *)ut->carg1); + break; +/* + * This function undoes a window switch + */ + case UNDO_C_WINDOW_SWITCH: + window_switch(ut->iarg1); + break; +/* + * This function undoes a window split + */ + case UNDO_C_WINDOW_SPLIT: + window_switch(ut->iarg1); + screen_delete_window((struct window *)ut->carg1); + break; +/* + * This function reopens a buffer which was closed with EF + */ + case UNDO_C_REOPENBUFF: + buff_reopenbuff((struct buff_header *)ut->carg1); + break; +/* + * This function replaces the buffer name which was changed by a rename + * operation. + */ + case UNDO_C_RENAME_BUFFER: + tec_release(TYPE_C_CBUFF,curbuf->name); + curbuf->name = ut->carg1; + break; + +/* + * This function replaces arguments which may have been overwritten during + * a loop. + */ + case UNDO_C_PRESERVEARGS: + ct = (struct cmd_token *)ut->carg1; + ct->ctx.iarg1 = ut->iarg1; + ct->ctx.iarg2 = ut->iarg2; + break; + +/* + * This function replaces runtime options changed by the EJ command + */ + case UNDO_C_SETOPTIONS: + cmd_setoptions(ut->iarg1,ut->iarg2,NULL); + break; +/* + * This function restores some globals used by the searching system + */ + case UNDO_C_SET_SEARCH_GLOBALS: + last_search_pos1 = ut->iarg1; + last_search_pos2 = ut->iarg2; + last_search_status = (int)ut->carg1; + break; +/* + * This function puts the previous tags file back + */ + case UNDO_C_LOAD_TAGS: + tag_free_struct(current_tags); + current_tags = (struct tags *)ut->carg1; + break; + +/* + * This function puts the previous tag entry selection back + */ + case UNDO_C_SELECT_TAGS: + current_tags->current_entry = (struct tagent *)ut->carg1; + break; + + case UNDO_C_SET_EXIT_FLAG: + exit_flag = 0; + break; + +/* + * Here when we get an undo code we don't know about. This is an internal + * error to teco! + */ + default: + sprintf(tmp_message,"?Unknown undo opcode %d",ut->opcode); + error_message(tmp_message); + return(FAIL); + }/* End Switch */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* ALLOCATE_UNDO_TOKEN - Allocate an undo token structure + * + * Function: + * + * This routine is called to allocate an undo token structure. If there + * is one on the free list, it is used, otherwise we allocate one. UNDO + * tokens are used so that we can back out of operations which change + * some state other than that of the parser. The normal case is that we + * make some change to the edit buffer, and want to be able to back out + * of it if we want. + */ +struct undo_token * +allocate_undo_token(ct) +register struct cmd_token *ct; +{ +register struct undo_token *ut; + + PREAMBLE(); + + ut = (struct undo_token *)tec_alloc(TYPE_C_UNDO,sizeof(struct undo_token)); + if(ut == NULL) return(NULL); + + ut->opcode = UNDO_C_UNUSED; + ut->iarg1 = 0; + ut->iarg2 = 0; + ut->carg1 = NULL; + ut->next_token = NULL; + + if(ct != NULL){ + ut->next_token = ct->undo_list; + ct->undo_list = ut; + }/* End IF */ + + return(ut); + +}/* End Routine */ + + + +/* FREE_UNDO_TOKEN - Routine to place an undo token on the free list + * + * Function: + * + * This routine is called with the address of the undo token + * to be placed on the free list. + */ +void +free_undo_token(ut) +register struct undo_token *ut; +{ + + PREAMBLE(); + + tec_release(TYPE_C_UNDO,(char *)ut); + +}/* End Routine */ + + + +/* TECUNDO_LIST - Cause a list of undo structures to be backed out + * + * Function: + * + * This routine is called with the head of a list of undo tokens that + * need to be undone. They are placed on the list such that the head + * of the list is the first that needs to be undone. + */ +void +tecundo_list(nut) +register struct undo_token *nut; +{ +register struct undo_token *ut; + + PREAMBLE(); + + while((ut = nut)){ + +/* + * Remember what the next token address is and unchain it from the current + * undo token. + */ + nut = ut->next_token; + ut->next_token = NULL; +/* + * Now call the undo routine to back out the changes to the edit buffer that + * this token calls for. Then place the token back on the free list. + */ + parser_undo(ut); + free_undo_token(ut); + + }/* End While */ + +}/* End Routine */ + + + +/* TECUNDO_CLEANUP - Place the list of undo tokens back on the free list + * + * Function: + * + * This routine is called with the head of a list of undo tokens. It + * places them back on the undo free list while releasing any resources + * they may have allocated. For example, the MEMFREE token has to free + * the memory the block points to. + */ +void +tecundo_cleanup(tut) +register struct undo_token *tut; +{ +register struct undo_token *ut; + + PREAMBLE(); + + while((ut = tut)){ + tut = tut->next_token; + switch(ut->opcode){ + case UNDO_C_MEMFREE: + if(ut->carg1) tec_release(TYPE_C_CBUFF,ut->carg1); + ut->carg1 = NULL; + free_undo_token(ut); + break; + case UNDO_C_MACRO: + if(ut->carg1){ + parser_cleanup_ctlist((struct cmd_token *)ut->carg1); + }/* End IF */ + ut->carg1 = NULL; + free_undo_token(ut); + break; + case UNDO_C_BULK_INSERT: + buff_free_line_buffer_list((struct buff_line *)ut->carg1); + ut->carg1 = NULL; + free_undo_token(ut); + break; + case UNDO_C_REOPENBUFF: + {/* Local Block */ + register struct buff_header *hbp; + hbp = (struct buff_header *)ut->carg1; + hbp->next_header = buffer_headers; + buffer_headers = hbp; + buff_destroy(hbp); + }/* End Local Block */ + free_undo_token(ut); + break; + case UNDO_C_RENAME_BUFFER: + tec_release(TYPE_C_CBUFF,ut->carg1); + free_undo_token(ut); + break; + case UNDO_C_LOAD_TAGS: + tag_free_struct((struct tags *)ut->carg1); + break; + default: + free_undo_token(ut); + }/* End Switch */ + }/* End While */ +}/* End Routine */ diff --git a/tecvms.h b/tecvms.h new file mode 100644 index 0000000..fb1cb6c --- /dev/null +++ b/tecvms.h @@ -0,0 +1,51 @@ +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecvms.h,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecvms.h + * Include file for VMS build + * %W% (PC) %G% + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#define VMS + +#include file +#include errno +#include stdio +#include ctype +#include perror +#include signal +#include jpidef +#include ssdef +#include iodef +#include stsdef +#include descrip +#include ttdef + +#ifndef LIB$K_CLI_LOCAL_SYM +#define LIB$K_CLI_LOCAL_SYM 1 +#endif +#ifndef CLI$M_NOWAIT +#define CLI$M_NOWAIT (1<<0) +#endif +#ifndef CLI$M_NOTIFY +#define CLI$M_NOTIFY (1<<4) +#endif |