static char rcsid[] = "$Id: led.c,v 1.2 1992/11/10 20:04:22 mike Exp $"; /* $Log: led.c,v $ * Revision 1.2 1992/11/10 20:04:22 mike * - Changed according to new version of `Eget_key'. * * Revision 1.1 1992/09/14 13:02:14 mike * Initial revision * */ /* LED.c : Line Editor library * External routines: * t_open() : Open terminal. Probably set Lncol. If terminal width * changes, you'll need to manage Lvline and Lpline. * t_close() : Close terminal. * t_putchar() : Handles BS, CR and NL. No echo, ^C trapping. * t_getchar() : No echo. * t_flush() : Flush the terminal. * t_clearline(n) : Put the cursor at the start of a blank line. * n == 0: Put the cursor at the start of a clear line. * n == 1: Move the cursor to start of the line. * Returns: Same as n. * Note: If you can't do n==1, pretend n==0 and return 0; * Variables of Interest: * Lncol: Number columns per line MINUS 1 or 2. * Lpline, Lvline: pointers to char arrays that hold at least Lncol chars * each. If Lncol ever changes, make sure the arrays track. * Lflags: Control some internal stuff. * LFnohist : if set, don't insert test into the history stack. Default * is 0. * LFrecdraw : if set and led recurses, don't redraw the line. Default is * 0. Used if you want the new prompt to overwrite the current line. * Returns: * Led() return code: Same as Ldo_fcn(). * Ldo_fcn() return codes: * LF_DONE: User said all done now - process my input. Ltext points * the entered text. NOTE: Ltext is only good on LF_DONE. It may * change from call to call. It is buffer local. Text in buffer is * only good till next call to Led() or Ldo_fcn(); * LF_ABORT: User wants to stop - ignore this and get me out. * LF_OK: Command(s) executed OK. * LF_ERROR: Something bad happened. Lerrorno has the error number. * Led(prompt,prefload,callback) * callback(kc,n): When a key is hit that is bound to LF_CALLBACK and * callback is != NULL, callback(kc,n) is called (where kc is the key * pressed and n is what key was bound to). Return code sez what to do: * return LF_NOOP if nothing is to be done, LF_DONE to simulate the user * pressing LF_DONE, etc. Normally, just "return Ldo_fcn(...);". * Recursion is OK. * Global callbacks: * Lregister_callback(0, (pfi)foo); foo(kc,n,op) will be called for any * key that is bound to LF_EXTEND. If foo returns TRUE, no other * callbacks will be called and Ldo_fcn() will be called for what ever * is in op. If foo return FALSE, the next callback in the list will be * called. * Ldo_fcn(list-of-functions-to-do) * Execute a bunch of functions. The list is terminated be LF_STOP. * For example: * Ldo_fcn(LF_CLEAR_LINE, LF_INSERT_STRING,"hello world", LF_STOP); * This replaces the line with "hello world". * Ldo_fcn(LF_BINDKEY, LF_RIGHT, CTRL|'F', LF_STOP, LF_STOP); * This binds move-cursor-right to control-F. Note the need for two * LF_STOPs - one to terminate the key binding list and one to * termainate the Ldo_fcn list. * See led.h for list of functions. * Ldot : return where the cursor is in Ttext. * Lline_length : return the strlen of Ttext. * C Durland 1990 */ /* * Other ideas: * A command que for things like signal handlers (^C and the like). * Make sure buffers Line is inited at init time. */ /* * how to use: * t_open(); * [set Lncol, Lvline, Lpline] * init_led(); * while(something) * { * Led(....); * do something with results * } * [t_close();] * exit(); */ /* * Implementation notes: * The Line in the Buffer is a bunch of characters with a length. The * line ALWAYS has enough room at the end for a \0 to turn it into a C * string. * The first thing Led() does is set the prompt. This will make sure that * the line has space in it (even if prompt is ""). * line_is_garbage : * 0 : physical line is NOT garbage. * 1 : physical line is garbage. * 2 : pline is in sync but hardware cursor is out of sync. */ /* Copyright 1990, 1991 Craig Durland * Distributed under the terms of the GNU General Public License. * Distributed "as is", without warranties of any kind, but comments, * suggestions and bug reports are welcome. */ static char what[] = "@(#)LED (Line EDitor) v1.0 5/2/90"; #include #include #include "ed.h" #include "led.h" #ifdef atarist #include "../me2/stio.h" #endif #include /* for iscntrl() only */ #ifdef __STDC__ #include #define VA_START va_start #else /* __STDC__ */ #include #define VA_START(a,b) va_start(a) #endif /* ******************************************************************** */ /* ************* Types and Structures ********************************* */ /* ******************************************************************** */ typedef declare_dTable_of(char) Line; #define LINE_LENGTH sizeof_dTable(lp) #define TEXT (lp->table) typedef struct /* keys bound to programs */ { KeyCode keycode; /* Key code used to invoke it */ char fcn; /* fcn to do */ short int x; /* for callbacks, ?? */ } Key; typedef declare_dTable_of(Key) KeyTable; /* ******************************************************************** */ /* ************* Extern Routines ************************************** */ /* ******************************************************************** */ extern char *malloc(); static int grok_key(), push_line(), pop_line(); static void LEDkeys(), movecursor(), update(), updateline(); /* ******************************************************************** */ /* ************* Global Variables ************************************* */ /* ******************************************************************** */ char *Lpline = NULL, *Lvline = NULL, *Ltext; /* the text the user input */ int Lerrorno, /* LEDs error number */ Lncol, Lflags; /* ******************************************************************** */ /* ************* Variables ******************************************** */ /* ******************************************************************** */ static int lmargin, line_is_garbage, dot, prompt_len, hstep, ttcol; static pfi Lcallback; static KeyCode keypressed; static Line text = initial_dTable_data(text), *lp = &text; static declare_pkeys(pkeys,SHIFT,SHIFT,SHIFT,SHIFT); static KeyTable gkeys = initial_dTable_data(gkeys); /* global key table */ /* ******************************************************************** */ /* ******************************************************************** */ /* ******************************************************************** */ #ifdef __STDC__ int Ldo_fcn(int fcn, ...); #endif /* t_open() has been called */ init_Led() { extern void init_linestuff(); if (!Lpline) Lpline = malloc(Lncol); if (!Lvline) Lvline = malloc(Lncol); hstep = Lncol/3; LEDkeys(); Lflags = 0; init_linestuff(); return TRUE; } #define INSERT 0 #define CALLBACK 1 #define DOFCN 2 #define DOZIP 3 Led(prompt,preload,callback) char *prompt, *preload; pfi callback; { static int rlevel = 0; int s, f; Key *key; f = (rlevel && (Lflags & LFrecdraw)) ? LF_NOOP : LF_REDRAW; if (rlevel++) push_line(); /* we are recursing */ else Htop(); /* A first time call */ Lcallback = callback; s = Ldo_fcn(LF_PROMPT,prompt,LF_INSERT_STRING,preload, f,LF_UPDATE,LF_STOP); while (s != LF_DONE && s != LF_ABORT && s != LF_ERROR) { key = (Key *)Efind_key(&gkeys,keypressed = Eget_key(pkeys,0L)); switch (grok_key(key,keypressed)) { case INSERT: s = Ldo_fcn(LF_INSERT_KEYCODE,(int)keypressed,LF_UPDATE,LF_STOP); break; case DOFCN: s = Ldo_fcn(key->fcn,LF_UPDATE,LF_STOP); break; case CALLBACK: s = Ldo_fcn(key->fcn,key->x,LF_UPDATE,LF_STOP); break; } } if (--rlevel) pop_line(); /* we were recursing */ return s; } static int grok_key(key,kc) Key *key; KeyCode kc; { if (key) { if (key->fcn != LF_CALLBACK && key->fcn != LF_EXTEND) return DOFCN; if ((key->fcn == LF_CALLBACK && Lcallback) || (key->fcn == LF_EXTEND)) return CALLBACK; } if ((kc & 0xFF00) == 0) return INSERT; return DOZIP; } /* ******************************************************************** */ /* ******************************************************************** */ /* ******************************************************************** */ static int do_callbacks(), bind_key(), linsert(); static void set_Ltext(); /*VARARGS1*/ #ifdef __STDC__ Ldo_fcn(int fcn, ...) #else Ldo_fcn(fcn, va_alist) int fcn; va_dcl #endif { char tmp[20], *ptr; register KeyCode kc; register int n; va_list ap; VA_START(ap,fcn); while (TRUE) { execute_fcn: switch (fcn) { case LF_NOOP: break; /* also LF_OK. no op is a easy thing to do */ case LF_DONE: /* done with this line, give it back */ set_Ltext(); if (!(Lflags & LFnohist)) Hadd(Ltext); alldone: va_end(ap); return fcn; case LF_ERROR: /* Assumes Lerrorno set. Useful for callbacks. */ error: fcn = LF_ERROR; goto alldone; case LF_ABORT: goto alldone; case LF_STOP: fcn = LF_OK; goto alldone; case LF_BoL: /* begining of line */ dot = prompt_len; lmargin = 0; /* make sure line updates correctly */ break; case LF_RIGHT: /* right arrow */ if (dot < LINE_LENGTH) dot++; break; case LF_LEFT: /* left arrow */ if (prompt_lenkeycode = keycode; key->fcn = fcn; key->x = x; return TRUE; } /* ******************************************************************** */ /* ******************* Allocate Lines ********************************* */ /* ******************************************************************** */ #define LINES 10 typedef struct { int lmargin, dot, prompt_len; pfi Lcallback; Line line, *lp; } LineStuff; static LineStuff lines[LINES]; static int nth_line = 0; void init_linestuff() { int j; for (j = LINES; j--; ) INIT_dTable(&lines[j].line); } /* save context of current line and set up a new line * New line inited by Led(). * !!!!!!!!!!!assumes that lines[] is initied!!!!!!!!!!!!!!!! * Can't init line because it might have been used before and have * allocated some data. */ static int push_line() { LineStuff *lsp; if (nth_line == LINES) return FALSE; lsp = &lines[nth_line++]; lsp->lmargin = lmargin; lsp->dot = dot; lsp->prompt_len = prompt_len; lsp->Lcallback = Lcallback; lsp->lp = lp; lp = &lsp->line; reset_dTable(lp); return TRUE; } /* restore last used line */ static int pop_line() { LineStuff *lsp; lsp = &lines[--nth_line]; lmargin = lsp->lmargin; dot = lsp->dot; prompt_len = lsp->prompt_len; Lcallback = lsp->Lcallback; lp = lsp->lp; return TRUE; } /* ******************************************************************** */ /* **************** Manage Contents of Lines ************************** */ /* ******************************************************************** */ /* blkmovm(to,from,n) * F = source, T = dest * |---------**from*F----------| * |---------------***to**T----| */ /* Open a hole of size n @ dot in a line. */ static void hole(line,dot,n) Line *line; int dot, n; { register char *ptr, *qtr; register int x; qtr = &line->table[sizeof_dTable(line) -1]; ptr = qtr +n; x = sizeof_dTable(line) -dot; while (x--) *ptr-- = *qtr--; } static char deadline[1] = { '\0' }; static int linsert(text) char *text; { int n = strlen(text), a = sizeof_dTable(lp); if (!xpand_dTable(lp,n+1,32,32)) { lp->table = deadline; Lerrorno = LEmem; return FALSE; } sizeof_dTable(lp) = a; hole(lp,dot,n); sizeof_dTable(lp) = a+n; memcpy(&lp->table[dot], text, n); dot += n; return TRUE; } static int getccol(text,dot) register char *text; { register char c; register int i, col = 0; for (i = dot; i--;) { c = *text++; if (c == '\t') col = NEXT_TAB_COLUMN_MINUS_1(col); else if (iscntrl(c)) ++col; ++col; } return col; } /* ******************************************************************** */ /* ******************* Video routines ********************************* */ /* ******************************************************************** */ /* Format a screen line: convert tabs to spaces, control characters to * printable. Right pad with blanks. Shift text left as needed to fit it * between the screen margins. You have to look at the text from the * beginning to ensure correct tab and control character expansion. */ #define ADD_CHAR(c) if (lmargin <= col++) *qtr++ = c static void vblast(ptr,len,lmargin) unsigned char *ptr; register int lmargin; { register unsigned char *qtr = (unsigned char *)Lvline; register int col = 0; register unsigned char c; register int rmargin = lmargin +Lncol; while (col < rmargin && len--) { c = *ptr++; if (iscntrl(c)) { if (c == '\t') /* expand tab */ { register int n = imin(rmargin, NEXT_TAB_COLUMN(col)); do { ADD_CHAR(' '); } while (col < n); continue; } ADD_CHAR('^'); /* LineFeed => "^J" */ c ^= 0x40; /* Convert ASCII LineFeed to "J" */ if (col == rmargin) continue; /* no room for the "J" */ /* fall though and put the "J" in the video */ } ADD_CHAR(c); } if (0 < len) *(qtr-1) = '$'; /* hit rmargin before wrote all of row */ else /* clear to end of line */ memset(qtr,' ',(col <= lmargin) ? Lncol : rmargin -col); if (0 < lmargin) Lvline[0] = '$'; } /* * Make sure that the display is right. This is a three part process: * First, scan through all of the windows looking for dirty ones. Check * the framing, and refresh the screen. * Second, make sure that "currow" and "curcol" are correct for the * current window. * Third, make the virtual and physical screens the same. */ static void update() { register int j, k, curcol; curcol = getccol(TEXT,dot); /* find the cursor column */ if (hstep!=0) /* check to see if have to horizontal scroll */ { k = lmargin; if (curcol < k) /* cursor is left of window margin */ { j = ((k -curcol +hstep -1)/hstep)*hstep; if ((k -= j)<0) k = 0; } else if (curcol >= k+Lncol) /* cursor is right of window */ { j = ((curcol -(k +Lncol) +hstep)/hstep)*hstep; k += j; } lmargin = k; } vblast(TEXT,LINE_LENGTH,lmargin); /* Special hacking if the screen is garbage. Clear the hardware screen, * and update your copy to agree with it. Set all the virtual screen * change bits, to force a full update. */ if (line_is_garbage) { j = t_clearline(line_is_garbage-1); if (j == 0) memset(Lpline,' ',Lncol); line_is_garbage = 0; ttcol = 0; } updateline(Lvline,Lpline); /* Finally, update the hardware cursor and flush out buffers */ j = curcol -lmargin; if (Lncol <= j) j = Lncol-1; /* cursor off right side of screen */ else if (j < 0) j = 0; /* cursor off left side of screen */ movecursor(j); t_flush(); } /* * Update a single line. This does not know how to use insert or delete * character sequences; we are using Backspace functionality. Update the * physical row and column variables. */ static void updateline(vline,pline) char *vline, *pline; { register char *cp1 = vline, *cp2 = pline, *cp4, *cp3 = &vline[Lncol], *cp5 = cp3; /* Compute left match */ while (cp1 != cp5 && *cp1 == *cp2) { ++cp1; ++cp2; } if (cp1 == cp5) return; /* lines are equal */ /* Compute right match */ cp4 = &pline[Lncol]; while (cp3[-1] == cp4[-1]) { --cp3; --cp4; } cp5 = cp3; movecursor(cp1-vline); while (cp1 != cp5) /* write out mismatched part */ { t_putchar(*cp1); ++ttcol; *cp2++ = *cp1++; } } #define BS 0x08 /* BackSpace */ /* * Send a command to the terminal to move the hardware cursor to * (row,column). Can only use BS and write; * The row and column arguments are origin 0. * Optimize out random calls. * Update "ttcol". */ static void movecursor(col) register int col; { register char *ptr; /* move cursor left with backspace */ while (col < ttcol) { ttcol--; t_putchar(BS); } if (ttcol < col) /* move cursor right with write */ { ptr = &Lvline[ttcol]; while (ttcol < col) { ttcol++; t_putchar(*ptr++); } } }