text: /* tio.c */
  
  /* Author:
   *	Steve Kirkendall
   *	14407 SW Teal Blvd. #C
   *	Beaverton, OR 97005
   *	kirkenda@cs.pdx.edu
   */
  
  
  /* This file contains terminal I/O functions */
  
  #include "config.h"
  #include "vi.h"
  #include "ctype.h"
  
  
  /* This function reads in a line from the terminal. */
  int vgets(prompt, buf, bsize)
  	char	prompt;	/* the prompt character, or '\0' for none */
  	char	*buf;	/* buffer into which the string is read */
  	int	bsize;	/* size of the buffer */
  {
  	int	len;	/* how much we've read so far */
  	int	ch;	/* a character from the user */
  	int	quoted;	/* is the next char quoted? */
  	int	tab;	/* column position of cursor */
  	char	widths[132];	/* widths of characters */
  	int	word;	/* index of first letter of word */
  #ifndef NO_DIGRAPH
  	int	erased;	/* 0, or first char of a digraph */
  #endif
  
  	/* show the prompt */
  	move(LINES - 1, 0);
  	tab = 0;
  	if (prompt)
  	{
  		addch(prompt);
  		tab = 1;
  	}
  	clrtoeol();
  	refresh();
  
  	/* read in the line */
  #ifndef NO_DIGRAPH
  	erased =
  #endif
  	quoted = len = 0;
  	for (;;)
  	{
  #ifndef NO_ABBR
  		if (quoted || mode == MODE_EX)
  		{
  			ch = getkey(0);
  		}
  		else
  		{
  			/* maybe expand an abbreviation while getting key */
  			for (word = len; --word >= 0 && isalnum(buf[word]); )
  			{
  			}
  			word++;
  			ch = getabkey(WHEN_EX, &buf[word], len - word);
  		}
  #else
  		ch = getkey(0);
  #endif
  #ifndef NO_EXTENSIONS
  		if (ch == ctrl('O'))
  		{
  			ch = getkey(quoted ? 0 : WHEN_EX);
  		}
  #endif
  
  		/* some special conversions */
  		if (ch == ctrl('D') && len == 0)
  			ch = ctrl('[');
  #ifndef NO_DIGRAPH
  		if (*o_digraph && erased != 0 && ch != '\b')
  		{
  			ch = digraph(erased, ch);
  			erased = 0;
  		}
  #endif
  
  		/* inhibit detection of special chars (except ^J) after a ^V */
  		if (quoted && ch != '\n')
  		{
  			ch |= 256;
  		}
  
  		/* process the character */
  		switch(ch)
  		{
  		  case ctrl('V'):
  			qaddch('^');
  			qaddch('\b');
  			quoted = TRUE;
  			break;
  
  		  case ctrl('['):
  			return -1;
  
  		  case '\n':
  #if OSK
  		  case '\l':
  #else
  		  case '\r':
  #endif
  			clrtoeol();
  			goto BreakBreak;
  
  		  case '\b':
  			if (len > 0)
  			{
  				len--;
  #ifndef NO_DIGRAPH
  				erased = buf[len];
  #endif
  				for (ch = widths[len]; ch > 0; ch--)
  					addch('\b');
  				if (mode == MODE_EX)
  				{
  					clrtoeol();
  				}
  				tab -= widths[len];
  			}
  			else
  			{
  				return -1;
  			}
  			break;
  
  		  default:
  			/* strip off quotation bit */
  			if (ch & 256)
  			{
  				ch &= ~256;
  				qaddch(' ');
  				qaddch('\b');
  			}
  
  			/* add & echo the char */
  			if (len < bsize - 1)
  			{
  				if (ch == '\t' && !quoted)
  				{
  					widths[len] = *o_tabstop - (tab % *o_tabstop);
  					addstr("        " + 8 - widths[len]);
  					tab += widths[len];
  				}
  				else if (ch > 0 && ch < ' ') /* > 0 by GB */
  				{
  					addch('^');
  					addch(ch + '@');
  					widths[len] = 2;
  					tab += 2;
  				}
  				else if (ch == '\177')
  				{
  					addch('^');
  					addch('?');
  					widths[len] = 2;
  					tab += 2;
  				}
  				else
  				{
  					addch(ch);
  					widths[len] = 1;
  					tab++;
  				}
  				buf[len++] = ch;
  			}
  			else
  			{
  				beep();
  			}
  			quoted = FALSE;
  		}
  	}
  BreakBreak:
  	refresh();
  	buf[len] = '\0';
  	return len;
  }
  
  
  static int	manymsgs; /* This variable keeps msgs from overwriting each other */
  static char	pmsg[80]; /* previous message (waiting to be displayed) */
  
  
  static int showmsg()
  {
  	/* if there is no message to show, then don't */
  	if (!manymsgs)
  		return FALSE;
  
  	/* display the message */
  	move(LINES - 1, 0);
  	if (*pmsg)
  	{
  		standout();
  		qaddch(' ');
  		qaddstr(pmsg);
  		qaddch(' ');
  		standend();
  	}
  	clrtoeol();
  
  	manymsgs = FALSE;
  	return TRUE;
  }
  
  
  void endmsgs()
  {
  	if (manymsgs)
  	{
  		showmsg();
  		addch('\n');
  	}
  }
  
  /* Write a message in an appropriate way.  This should really be a varargs
   * function, but there is no such thing as vwprintw.  Hack!!!
   *
   * In MODE_EX or MODE_COLON, the message is written immediately, with a
   * newline at the end.
   *
   * In MODE_VI, the message is stored in a character buffer.  It is not
   * displayed until getkey() is called.  msg() will call getkey() itself,
   * if necessary, to prevent messages from being lost.
   *
   * msg("")		- clears the message line
   * msg("%s %d", ...)	- does a printf onto the message line
   */
  /*VARARGS1*/
  void msg(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
  	char	*fmt;
  	long	arg1, arg2, arg3, arg4, arg5, arg6, arg7;
  {
  	if (mode != MODE_VI)
  	{
  		sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
  		qaddstr(pmsg);
  		addch('\n');
  		exrefresh();
  	}
  	else
  	{
  		/* wait for keypress between consecutive msgs */
  		if (manymsgs)
  		{
  			getkey(WHEN_MSG);
  		}
  
  		/* real message */
  		sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
  		if (*fmt)
  		{
  			manymsgs = TRUE;
  		}
  	}
  }
  
  
  /* This function calls refresh() if the option exrefresh is set */
  void exrefresh()
  {
  	char	*scan;
  
  	/* If this ex command wrote ANYTHING set exwrote so vi's  :  command
  	 * can tell that it must wait for a user keystroke before redrawing.
  	 */
  	for (scan=kbuf; scan<stdscr; scan++)
  		if (*scan == '\n')
  			exwrote = TRUE;
  
  	/* now we do the refresh thing */
  	if (*o_exrefresh)
  	{
  		refresh();
  	}
  	else
  	{
  		wqrefresh();
  	}
  	if (mode != MODE_VI)
  	{
  		manymsgs = FALSE;
  	}
  }
  
  
  /* This structure is used to store maps and abbreviations.  The distinction
   * between them is that maps are stored in the list referenced by the "maps"
   * pointer, while abbreviations are referenced by the "abbrs" pointer.
   */
  typedef struct _map
  {
  	struct _map	*next;	/* another abbreviation */
  	short		len;	/* length of the "rawin" characters */
  	short		flags;	/* various flags */
  	char		*label;	/* label of the map/abbr, or NULL */
  	char		*rawin;	/* the "rawin" characters */
  	char		*cooked;/* the "cooked" characters */
  } MAP;
  
  static char	keybuf[KEYBUFSIZE];
  static int	cend;	/* end of input characters */
  static int	user;	/* from user through end are chars typed by user */
  static int	next;	/* index of the next character to be returned */
  static MAP	*match;	/* the matching map, found by countmatch() */
  static MAP	*maps;	/* the map table */
  #ifndef NO_ABBR
  static MAP	*abbrs;	/* the abbreviation table */
  #endif
  
  
  
  /* ring the terminal's bell */
  void beep()
  {
  	/* do a visible/audible bell */
  	if (*o_flash)
  	{
  		do_VB();
  		refresh();
  	}
  	else if (*o_errorbells)
  	{
  		ttywrite("\007", 1);
  	}
  
  	/* discard any buffered input, and abort macros */
  	next = user = cend;
  }
  
  
  
  /* This function replaces a "rawin" character sequence with the "cooked" version,
   * by modifying the internal type-ahead buffer.
   */
  void execmap(rawlen, cookedstr, visual)
  	int	rawlen;		/* length of rawin text -- string to delete */
  	char	*cookedstr;	/* the cooked text -- string to insert */
  	int	visual;		/* boolean -- chars to be executed in visual mode? */
  {
  	int	cookedlen;
  	char	*src, *dst;
  	int	i;
  
  	/* find the length of the cooked string */
  	cookedlen = strlen(cookedstr);
  #ifndef NO_EXTENSIONS
  	if (visual)
  	{
  		cookedlen *= 2;
  	}
  #endif
  
  	/* if too big to fit in type-ahead buffer, then don't do it */
  	if (cookedlen + (cend - next) - rawlen > KEYBUFSIZE)
  	{
  		return;
  	}
  
  	/* shift to make room for cookedstr at the front of keybuf */
  	src = &keybuf[next + rawlen];
  	dst = &keybuf[cookedlen];
  	i = cend - (next + rawlen);
  	if (src >= dst)
  	{
  		while (i-- > 0)
  		{
  			*dst++ = *src++;
  		}
  	}
  	else
  	{
  		src += i;
  		dst += i;
  		while (i-- > 0)
  		{
  			*--dst = *--src;
  		}
  	}
  
  	/* insert cookedstr, and adjust offsets */
  	cend += cookedlen - rawlen - next;
  	user += cookedlen - rawlen - next;
  	next = 0;
  	for (dst = keybuf, src = cookedstr; *src; )
  	{
  #ifndef NO_EXTENSIONS
  		if (visual)
  		{
  			*dst++ = ctrl('O');
  			cookedlen--;
  		}
  #endif
  		*dst++ = *src++;
  	}
  
  #ifdef DEBUG2
  	{
  #include <stdio.h>
  		FILE	*debout;
  		int		i;
  
  		debout = fopen("debug.out", "a");
  		fprintf(debout, "After execmap(%d, \"%s\", %d)...\n", rawlen, cookedstr, visual);
  		for (i = 0; i < cend; i++)
  		{
  			if (i == next) fprintf(debout, "(next)");
  			if (i == user) fprintf(debout, "(user)");
  			if (UCHAR(keybuf[i]) < ' ')
  				fprintf(debout, "^%c", keybuf[i] ^ '@');
  			else
  				fprintf(debout, "%c", keybuf[i]);
  		}
  		fprintf(debout, "(end)\n");
  		fclose(debout);
  	}
  #endif
  }
  
  /* This function calls ttyread().  If necessary, it will also redraw the screen,
   * change the cursor shape, display the mode, and update the ruler.  If the
   * number of characters read is 0, and we didn't time-out, then it exits because
   * we've apparently reached the end of an EX script.
   */
  static int fillkeybuf(when, timeout)
  	int	when;	/* mixture of WHEN_XXX flags */
  	int	timeout;/* timeout in 1/10 second increments, or 0 */
  {
  	int	nkeys;
  #ifndef NO_SHOWMODE
  	static int	oldwhen;	/* "when" from last time */
  	static int	oldleft;
  	static long	oldtop;
  	static long	oldnlines;
  	char		*str;
  #endif
  #ifndef NO_CURSORSHAPE
  	static int	oldcurs;
  #endif
  
  #ifdef DEBUG
  	watch();
  #endif
  
  
  #ifndef NO_CURSORSHAPE
  	/* make sure the cursor is the right shape */
  	if (has_CQ)
  	{
  		if (when != oldcurs)
  		{
  			switch (when)
  			{
  			  case WHEN_EX:		do_CX();	break;
  			  case WHEN_VICMD:	do_CV();	break;
  			  case WHEN_VIINP:	do_CI();	break;
  			  case WHEN_VIREP:	do_CR();	break;
  			}
  			oldcurs = when;
  		}
  	}
  #endif
  
  #ifndef NO_SHOWMODE
  	/* if "showmode" then say which mode we're in */
  	if (*o_smd && (when & WHENMASK))
  	{
  		/* redraw the screen before we check to see whether the
  		 * "showmode" message needs to be redrawn.
  		 */
  		redraw(cursor, !(when & WHEN_VICMD));
  
  		/* now the "topline" test should be valid */
  		if (when != oldwhen || topline != oldtop || leftcol != oldleft || nlines != oldnlines)
  		{
  			oldwhen = when;
  			oldtop = topline;
  			oldleft = leftcol;
  			oldnlines = nlines;
  
  			if (when & WHEN_VICMD)	    str = "Command";
  			else if (when & WHEN_VIINP) str = " Input ";
  			else if (when & WHEN_VIREP) str = "Replace";
  			else if (when & WHEN_REP1)  str = " Rep 1 ";
  			else if (when & WHEN_CUT)   str = "BufName";
  			else if (when & WHEN_MARK)  str = "Mark AZ";
  			else if (when & WHEN_CHAR)  str = "Dest Ch";
  			else			    str = (char *)0;
  
  			if (str)
  			{
  				move(LINES - 1, COLS - 10);
  				standout();
  				qaddstr(str);
  				standend();
  			}
  		}
  	}
  #endif
  
  #ifndef NO_EXTENSIONS
  	/* maybe display the ruler */
  	if (*o_ruler && (when & (WHEN_VICMD|WHEN_VIINP|WHEN_VIREP)))
  	{
  		char	buf[20];
  
  		redraw(cursor, !(when & WHEN_VICMD));
  		pfetch(markline(cursor));
  		sprintf(buf, "%7ld,%-4d", markline(cursor), 1 + idx2col(cursor, ptext, when & (WHEN_VIINP|WHEN_VIREP)));
  		move(LINES - 1, COLS - 22);
  		addstr(buf);
  	}
  #endif
  
  	/* redraw, so the cursor is in the right place */
  	if (when & WHENMASK)
  	{
  		redraw(cursor, !(when & (WHENMASK & ~(WHEN_VIREP|WHEN_VIINP))));
  	}
  
  	/* Okay, now we can finally read the rawin keystrokes */
  	refresh();
  	nkeys = ttyread(keybuf + cend, sizeof keybuf - cend, timeout);
  
  	/* if nkeys == 0 then we've reached EOF of an ex script. */
  	if (nkeys == 0 && timeout == 0)
  	{
  		tmpabort(TRUE);
  		move(LINES - 1, 0);
  		clrtoeol();
  		refresh();
  		endwin();
  		exit(1);
  	}
  
  	cend += nkeys;
  	user += nkeys;
  	return nkeys;
  }
  
  
  /* This function counts the number of maps that could match the characters
   * between &keybuf[next] and &keybuf[cend], including incomplete matches.
   * The longest comlete match is remembered via the "match" variable.
   */
  static int countmatch(when)
  	int	when;	/* mixture of WHEN_XXX flags */
  {
  	MAP	*map;
  	int	count;
  
  	/* clear the "match" variable */
  	match = (MAP *)0;
  
  	/* check every map */
  	for (count = 0, map = maps; map; map = map->next)
  	{
  		/* can't match if wrong mode */
  		if ((map->flags & when) == 0)
  		{
  			continue;
  		}
  
  		/* would this be a complete match? */
  		if (map->len <= cend - next)
  		{
  			/* Yes, it would be.  Now does it really match? */
  			if (!strncmp(map->rawin, &keybuf[next], map->len))
  			{
  				count++;
  
  				/* if this is the longest complete match,
  				 * then remember it.
  				 */
  				if (!match || match->len < map->len)
  				{
  					match = map;
  				}
  			}
  		}
  		else
  		{
  			/* No, it wouldn't.  But check for partial match */
  			if (!strncmp(map->rawin, &keybuf[next], cend - next))
  			{
  				count++;
  			}
  		}
  	}
  	return count;
  }
  
  
  #ifndef NO_ABBR
  /* This function checks to see whether a word is an abbreviation.  If it is,
   * then an appropriate number of backspoace characters is inserted into the
   * type-ahead buffer, followed by the expanded form of the abbreviation.
   */
  static void expandabbr(word, wlen)
  	char	*word;
  	int	wlen;
  {
  	MAP	*abbr;
  
  	/* if the next character wouldn't end the word, then don't expand */
  	if (isalnum(keybuf[next]) || keybuf[next] == ctrl('V'))
  	{
  		return;
  	}
  
  	/* find the abbreviation, if any */
  	for (abbr = abbrs;
  	     abbr && (abbr->len != wlen || strncmp(abbr->rawin, word, wlen));
  	     abbr = abbr->next)
  	{
  	}
  
  	/* If an abbreviation was found, then expand it by inserting the long
  	 * version into the type-ahead buffer, and then inserting (in front of
  	 * the long version) enough backspaces to erase to the short version.
  	 */
  	if (abbr)
  	{
  		execmap(0, abbr->cooked, FALSE);
  		while (wlen > 15)
  		{
  			execmap(0, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", FALSE);
  			wlen -= 15;
  		}
  		if (wlen > 0)
  		{
  			execmap(0, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + 15 - wlen, FALSE);
  		}
  	}
  }
  #endif
  
  
  /* This function calls getabkey() without attempting to expand abbreviations */
  int getkey(when)
  	int	when;	/* mixture of WHEN_XXX flags */
  {
  	return getabkey(when, "", 0);
  }
  
  
  /* This is it.  This function returns keystrokes one-at-a-time, after mapping
   * and abbreviations have been taken into account.
   */
  int getabkey(when, word, wlen)
  	int	when;	/* mixture of WHEN_XXX flags */
  	char	*word;	/* a word that may need to be expanded as an abbr */
  	int	wlen;	/* length of "word" -- since "word" might not have \0 */
  {
  	int	matches;
  
  	/* if this key is needed for delay between multiple error messages,
  	 * then reset the manymsgs flag and abort any mapped key sequence.
  	 */
  	if (showmsg())
  	{
  		if (when == WHEN_MSG)
  		{
  #ifndef CRUNCH
  			if (!*o_more)
  			{
  				refresh();
  				return ' ';
  			}
  #endif
  			qaddstr("[More...]");
  			refresh();
  			execmap(user, "", FALSE);
  		}
  	}
  
  #ifdef DEBUG
  	/* periodically check for screwed up internal tables */
  	watch();
  #endif
  
  	/* if buffer empty, read some characters without timeout */
  	if (next >= cend)
  	{
  		next = user = cend = 0;
  		fillkeybuf(when, 0);
  	}
  
  	/* try to map the key, unless already mapped and not ":set noremap" */
  	if (next >= user || *o_remap)
  	{
  		do
  		{
  			do
  			{
  				matches = countmatch(when);
  			} while (matches > 1 && fillkeybuf(when, *o_keytime) > 0);
  			if (matches == 1)
  			{
  				execmap(match->len, match->cooked,
  					(match->flags & WHEN_INMV) != 0 
  					 && (when & (WHEN_VIINP|WHEN_VIREP)) != 0);
  			}
  		} while (*o_remap && matches == 1);
  	}
  
  #ifndef NO_ABBR
  	/* try to expand an abbreviation, except in visual command mode */
  	if (wlen > 0 && (mode & (WHEN_EX|WHEN_VIINP|WHEN_VIREP)) != 0)
  	{
  		expandabbr(word, wlen);
  	}
  #endif
  
  	/* ERASEKEY should always be mapped to '\b'. */
  	if (keybuf[next] == ERASEKEY)
  	{
  		keybuf[next] = '\b';
  	}
  
  	/* return the next key */
  	return keybuf[next++];
  }
  
  /* This function maps or unmaps a key */
  void mapkey(rawin, cooked, when, name)
  	char	*rawin;	/* the input key sequence, before mapping */
  	char	*cooked;/* after mapping -- or NULL to remove map */
  	short	when;	/* bitmap of when mapping should happen */
  	char	*name;	/* name of the key, NULL for no name, "abbr" for abbr */
  {
  	MAP	**head;	/* head of list of maps or abbreviations */
  	MAP	*scan;	/* used for scanning through the list */
  	MAP	*prev;	/* used during deletions */
  
  	/* Is this a map or an abbreviation?  Choose the right list. */
  #ifndef NO_ABBR
  	head = ((!name || strcmp(name, "abbr")) ? &maps : &abbrs);
  #else
  	head = &maps;
  #endif
  
  	/* try to find the map in the list */
  	for (scan = *head, prev = (MAP *)0;
  	     scan && (strcmp(rawin, scan->rawin) ||
  		!(scan->flags & when & (WHEN_EX|WHEN_VICMD|WHEN_VIINP|WHEN_VIREP)));
  	     prev = scan, scan = scan->next)
  	{
  	}
  
  	/* trying to map? (not unmap) */
  	if (cooked && *cooked)
  	{
  		/* if map starts with "visual ", then mark it as a visual map */
  		if (head == &maps && !strncmp(cooked, "visual ", 7))
  		{
  			cooked += 7;
  			when |= WHEN_INMV;
  		}
  
  		/* "visual" maps always work in input mode */
  		if (when & WHEN_INMV)
  		{
  			when |= WHEN_VIINP|WHEN_VIREP|WHEN_POPUP;
  		}
  
  		/* if not already in the list, then allocate a new structure */
  		if (!scan)
  		{
  			scan = (MAP *)malloc(sizeof(MAP));
  			scan->len = strlen(rawin);
  			scan->rawin = malloc(scan->len + 1);
  			strcpy(scan->rawin, rawin);
  			scan->flags = when;
  			scan->label = name;
  			if (*head)
  			{
  				prev->next = scan;
  			}
  			else
  			{
  				*head = scan;
  			}
  			scan->next = (MAP *)0;
  		}
  		else /* recycle old structure */
  		{
  			free(scan->cooked);
  		}
  		scan->cooked = malloc(strlen(cooked) + 1);
  		strcpy(scan->cooked, cooked);
  	}
  	else /* unmapping */
  	{
  		/* if nothing to unmap, then exit silently */
  		if (!scan)
  		{
  			return;
  		}
  
  		/* unlink the structure from the list */
  		if (prev)
  		{
  			prev->next = scan->next;
  		}
  		else
  		{
  			*head = scan->next;
  		}
  
  		/* free it, and the strings that it refers to */
  		free(scan->rawin);
  		free(scan->cooked);
  		free(scan);
  	}
  }
  
  
  /* This function returns a printable version of a string.  It uses tmpblk.c */
  char *printable(str)
  	char	*str;	/* the string to convert */
  {
  	char	*build;	/* used for building the string */
  
  	for (build = tmpblk.c; *str; str++)
  	{
  #if AMIGA
  		if (*str == '\233')
  		{
  			*build++ = '<';
  			*build++ = 'C';
  			*build++ = 'S';
  			*build++ = 'I';
  			*build++ = '>';
  		} else 
  #endif
  		if (UCHAR(*str) < ' ' || *str == '\177')
  		{
  			*build++ = '^';
  			*build++ = *str ^ '@';
  		}
  		else
  		{
  			*build++ = *str;
  		}
  	}
  	*build = '\0';
  	return tmpblk.c;
  }
  
  /* This function displays the contents of either the map table or the
   * abbreviation table.  User commands call this function as follows:
   *	:map	dumpkey(WHEN_VICMD, FALSE);
   *	:map!	dumpkey(WHEN_VIREP|WHEN_VIINP, FALSE);
   *	:abbr	dumpkey(WHEN_VIINP|WHEN_VIREP, TRUE);
   *	:abbr!	dumpkey(WHEN_EX|WHEN_VIINP|WHEN_VIREP, TRUE);
   */
  void dumpkey(when, abbr)
  	int	when;	/* WHEN_XXXX of mappings to be dumped */
  	int	abbr;	/* boolean: dump abbreviations instead of maps? */
  {
  	MAP	*scan;
  	char	*str;
  	int	len;
  
  #ifndef NO_ABBR
  	for (scan = (abbr ? abbrs : maps); scan; scan = scan->next)
  #else
  	for (scan = maps; scan; scan = scan->next)
  #endif
  	{
  		/* skip entries that don't match "when" */
  		if ((scan->flags & when) == 0)
  		{
  			continue;
  		}
  
  		/* dump the key label, if any */
  		if (!abbr)
  		{
  			len = 8;
  			if (scan->label)
  			{
  				qaddstr(scan->label);
  				len -= strlen(scan->label);
  			}
  			do
  			{
  				qaddch(' ');
  			} while (len-- > 0);
  		}
  
  		/* dump the rawin version */
  		str = printable(scan->rawin);
  		qaddstr(str);
  		len = strlen(str);
  		do
  		{
  			qaddch(' ');
  		} while (len++ < 8);
  			
  		/* dump the mapped version */
  #ifndef NO_EXTENSIONS
  		if ((scan->flags & WHEN_INMV) && (when & (WHEN_VIINP|WHEN_VIREP)))
  		{
  			qaddstr("visual ");
  		}
  #endif
  		str = printable(scan->cooked);
  		qaddstr(str);
  		addch('\n');
  		exrefresh();
  	}
  }
  
  #ifndef NO_MKEXRC
  
  static safequote(str)
  	char	*str;
  {
  	char	*build;
  
  	build = tmpblk.c + strlen(tmpblk.c);
  	while (*str)
  	{
  		if (*str <= ' ' && *str >= 1 || *str == '|')
  		{
  			*build++ = ctrl('V');
  		}
  		*build++ = *str++;
  	}
  	*build = '\0';
  }
  
  /* This function saves the contents of either the map table or the
   * abbreviation table into a file.  Both the "bang" and "no bang" versions
   * are saved.
   *	:map	dumpkey(WHEN_VICMD, FALSE);
   *	:map!	dumpkey(WHEN_VIREP|WHEN_VIINP, FALSE);
   *	:abbr	dumpkey(WHEN_VIINP|WHEN_VIREP, TRUE);
   *	:abbr!	dumpkey(WHEN_EX|WHEN_VIINP|WHEN_VIREP, TRUE);
   */
  savemaps(fd, abbr)
  	int	fd;	/* file descriptor of an open file to write to */
  	int	abbr;	/* boolean: do abbr table? (else do map table) */
  {
  	MAP	*scan;
  	char	*str;
  	int	bang;
  	int	when;
  	int	len;
  
  # ifndef NO_ABBR
  	for (scan = (abbr ? abbrs : maps); scan; scan = scan->next)
  # else
  	for (scan = maps; scan; scan = scan->next)
  # endif
  	{
  		/* skip maps that have labels, except for function keys */
  		if (scan->label && *scan->label != '#')
  		{
  			continue;
  		}
  
  		for (bang = 0; bang < 2; bang++)
  		{
  			/* decide which "when" flags we want */
  # ifndef NO_ABBR
  			if (abbr)
  				when = (bang ? WHEN_EX|WHEN_VIINP|WHEN_VIREP : WHEN_VIINP|WHEN_VIREP);
  			else
  # endif
  				when = (bang ? WHEN_VIREP|WHEN_VIINP : WHEN_VICMD);
  
  			/* skip entries that don't match "when" */
  			if ((scan->flags & when) == 0)
  			{
  				continue;
  			}
  
  			/* write a "map" or "abbr" command name */
  # ifndef NO_ABBR
  			if (abbr)
  				strcpy(tmpblk.c, "abbr");
  			else
  # endif
  				strcpy(tmpblk.c, "map");
  
  			/* maybe write a bang.  Definitely write a space */
  			if (bang)
  				strcat(tmpblk.c, "! ");
  			else
  				strcat(tmpblk.c, " ");
  
  			/* write the rawin version */
  # ifndef NO_FKEY
  			if (scan->label)
  				strcat(tmpblk.c, scan->label);
  			else
  # endif
  				safequote(scan->rawin);
  			strcat(tmpblk.c, " ");
  				
  			/* dump the mapped version */
  # ifndef NO_EXTENSIONS
  			if ((scan->flags & WHEN_INMV) && (when & (WHEN_VIINP|WHEN_VIREP)))
  			{
  				strcat(tmpblk.c, "visual ");
  			}
  # endif
  			safequote(scan->cooked);
  			strcat(tmpblk.c, "\n");
  			twrite(fd, tmpblk.c, strlen(tmpblk.c));
  		}
  	}
  }
  #endif
  