119304Speter/*- 219304Speter * Copyright (c) 1992, 1993, 1994 319304Speter * The Regents of the University of California. All rights reserved. 419304Speter * Copyright (c) 1992, 1993, 1994, 1995, 1996 519304Speter * Keith Bostic. All rights reserved. 619304Speter * 719304Speter * See the LICENSE file for redistribution information. 819304Speter */ 919304Speter 1019304Speter#include "config.h" 1119304Speter 1219304Speter#ifndef lint 13254225Speterstatic const char sccsid[] = "$Id: ex_txt.c,v 10.23 2001/06/25 15:19:21 skimo Exp $"; 1419304Speter#endif /* not lint */ 1519304Speter 1619304Speter#include <sys/types.h> 1719304Speter#include <sys/queue.h> 18254225Speter#include <sys/time.h> 1919304Speter 2019304Speter#include <bitstring.h> 2119304Speter#include <ctype.h> 2219304Speter#include <limits.h> 2319304Speter#include <stdio.h> 2419304Speter#include <stdlib.h> 2519304Speter#include <string.h> 2619304Speter 2719304Speter#include "../common/common.h" 28254225Speter#include "../vi/vi.h" 2919304Speter 3019304Speter/* 3119304Speter * !!! 3219304Speter * The backslash characters was special when it preceded a newline as part of 3319304Speter * a substitution replacement pattern. For example, the input ":a\<cr>" would 3419304Speter * failed immediately with an error, as the <cr> wasn't part of a substitution 3519304Speter * replacement pattern. This implies a frightening integration of the editor 3619304Speter * and the parser and/or the RE engine. There's no way I'm going to reproduce 3719304Speter * those semantics. 3819304Speter * 3919304Speter * So, if backslashes are special, this code inserts the backslash and the next 4019304Speter * character into the string, without regard for the character or the command 4119304Speter * being entered. Since "\<cr>" was illegal historically (except for the one 4219304Speter * special case), and the command will fail eventually, no historical scripts 4319304Speter * should break (presuming they didn't depend on the failure mode itself or the 4419304Speter * characters remaining when failure occurred. 4519304Speter */ 4619304Speter 47281373Sbaptstatic int txt_dent(SCR *, TEXT *); 48281373Sbaptstatic void txt_prompt(SCR *, TEXT *, ARG_CHAR_T, u_int32_t); 4919304Speter 5019304Speter/* 5119304Speter * ex_txt -- 5219304Speter * Get lines from the terminal for ex. 5319304Speter * 54281373Sbapt * PUBLIC: int ex_txt(SCR *, TEXTH *, ARG_CHAR_T, u_int32_t); 5519304Speter */ 5619304Speterint 57254225Speterex_txt(SCR *sp, TEXTH *tiqh, ARG_CHAR_T prompt, u_int32_t flags) 5819304Speter{ 5919304Speter EVENT ev; 6019304Speter GS *gp; 6119304Speter TEXT ait, *ntp, *tp; 6219304Speter carat_t carat_st; 6319304Speter size_t cnt; 6419304Speter int rval; 65254225Speter int nochange; 6619304Speter 6719304Speter rval = 0; 6819304Speter 6919304Speter /* 7019304Speter * Get a TEXT structure with some initial buffer space, reusing the 7119304Speter * last one if it's big enough. (All TEXT bookkeeping fields default 7219304Speter * to 0 -- text_init() handles this.) 7319304Speter */ 74254225Speter if (!TAILQ_EMPTY(tiqh)) { 75254225Speter tp = TAILQ_FIRST(tiqh); 76254225Speter if (TAILQ_NEXT(tp, q) != NULL || tp->lb_len < 32) { 7719304Speter text_lfree(tiqh); 7819304Speter goto newtp; 7919304Speter } 8019304Speter tp->len = 0; 8119304Speter } else { 8219304Speternewtp: if ((tp = text_init(sp, NULL, 0, 32)) == NULL) 8319304Speter goto err; 84254225Speter TAILQ_INSERT_HEAD(tiqh, tp, q); 8519304Speter } 8619304Speter 8719304Speter /* Set the starting line number. */ 8819304Speter tp->lno = sp->lno + 1; 8919304Speter 9019304Speter /* 9119304Speter * If it's a terminal, set up autoindent, put out the prompt, and 9219304Speter * set it up so we know we were suspended. Otherwise, turn off 9319304Speter * the autoindent flag, as that requires less special casing below. 9419304Speter * 9519304Speter * XXX 9619304Speter * Historic practice is that ^Z suspended command mode (but, because 9719304Speter * it ran in cooked mode, it was unaffected by the autowrite option.) 9819304Speter * On restart, any "current" input was discarded, whether in insert 9919304Speter * mode or not, and ex was in command mode. This code matches historic 10019304Speter * practice, but not 'cause it's easier. 10119304Speter */ 10219304Speter gp = sp->gp; 10319304Speter if (F_ISSET(gp, G_SCRIPTED)) 10419304Speter LF_CLR(TXT_AUTOINDENT); 10519304Speter else { 10619304Speter if (LF_ISSET(TXT_AUTOINDENT)) { 10719304Speter LF_SET(TXT_EOFCHAR); 10819304Speter if (v_txt_auto(sp, sp->lno, NULL, 0, tp)) 10919304Speter goto err; 11019304Speter } 11119304Speter txt_prompt(sp, tp, prompt, flags); 11219304Speter } 11319304Speter 114254225Speter for (carat_st = C_NOTSET, nochange = 0;;) { 11519304Speter if (v_event_get(sp, &ev, 0, 0)) 11619304Speter goto err; 11719304Speter 11819304Speter /* Deal with all non-character events. */ 11919304Speter switch (ev.e_event) { 12019304Speter case E_CHARACTER: 12119304Speter break; 12219304Speter case E_ERR: 12319304Speter goto err; 12419304Speter case E_REPAINT: 12519304Speter case E_WRESIZE: 12619304Speter continue; 12719304Speter case E_EOF: 12819304Speter rval = 1; 12919304Speter /* FALLTHROUGH */ 13019304Speter case E_INTERRUPT: 13119304Speter /* 13219304Speter * Handle EOF/SIGINT events by discarding partially 13319304Speter * entered text and returning. EOF returns failure, 13419304Speter * E_INTERRUPT returns success. 13519304Speter */ 13619304Speter goto notlast; 13719304Speter default: 13819304Speter v_event_err(sp, &ev); 13919304Speter goto notlast; 14019304Speter } 14119304Speter 14219304Speter /* 14319304Speter * Deal with character events. 14419304Speter * 14519304Speter * Check to see if the character fits into the input buffer. 14619304Speter * (Use tp->len, ignore overwrite and non-printable chars.) 14719304Speter */ 148254225Speter BINC_GOTOW(sp, tp->lb, tp->lb_len, tp->len + 1); 14919304Speter 15019304Speter switch (ev.e_value) { 15119304Speter case K_CR: 15219304Speter /* 15319304Speter * !!! 15419304Speter * Historically, <carriage-return>'s in the command 15519304Speter * weren't special, so the ex parser would return an 15619304Speter * unknown command error message. However, if they 15719304Speter * terminated the command if they were in a map. I'm 15819304Speter * pretty sure this still isn't right, but it handles 15919304Speter * what I've seen so far. 16019304Speter */ 16119304Speter if (!F_ISSET(&ev.e_ch, CH_MAPPED)) 16219304Speter goto ins_ch; 16319304Speter /* FALLTHROUGH */ 16419304Speter case K_NL: 16519304Speter /* 16619304Speter * '\' can escape <carriage-return>/<newline>. We 16719304Speter * don't discard the backslash because we need it 16819304Speter * to get the <newline> through the ex parser. 16919304Speter */ 17019304Speter if (LF_ISSET(TXT_BACKSLASH) && 17119304Speter tp->len != 0 && tp->lb[tp->len - 1] == '\\') 17219304Speter goto ins_ch; 17319304Speter 17419304Speter /* 17519304Speter * CR returns from the ex command line. 17619304Speter * 17719304Speter * XXX 17819304Speter * Terminate with a nul, needed by filter. 17919304Speter */ 18019304Speter if (LF_ISSET(TXT_CR)) { 18119304Speter tp->lb[tp->len] = '\0'; 18219304Speter goto done; 18319304Speter } 18419304Speter 18519304Speter /* 18619304Speter * '.' may terminate text input mode; free the current 18719304Speter * TEXT. 18819304Speter */ 18919304Speter if (LF_ISSET(TXT_DOTTERM) && tp->len == tp->ai + 1 && 19019304Speter tp->lb[tp->len - 1] == '.') { 191254225Speternotlast: TAILQ_REMOVE(tiqh, tp, q); 19219304Speter text_free(tp); 19319304Speter goto done; 19419304Speter } 19519304Speter 19619304Speter /* Set up bookkeeping for the new line. */ 19719304Speter if ((ntp = text_init(sp, NULL, 0, 32)) == NULL) 19819304Speter goto err; 19919304Speter ntp->lno = tp->lno + 1; 20019304Speter 20119304Speter /* 20219304Speter * Reset the autoindent line value. 0^D keeps the ai 20319304Speter * line from changing, ^D changes the level, even if 20419304Speter * there were no characters in the old line. Note, if 20519304Speter * using the current tp structure, use the cursor as 20619304Speter * the length, the autoindent characters may have been 20719304Speter * erased. 20819304Speter */ 20919304Speter if (LF_ISSET(TXT_AUTOINDENT)) { 210254225Speter if (nochange) { 211254225Speter nochange = 0; 21219304Speter if (v_txt_auto(sp, 21319304Speter OOBLNO, &ait, ait.ai, ntp)) 21419304Speter goto err; 21519304Speter free(ait.lb); 21619304Speter } else 21719304Speter if (v_txt_auto(sp, 21819304Speter OOBLNO, tp, tp->len, ntp)) 21919304Speter goto err; 22019304Speter carat_st = C_NOTSET; 22119304Speter } 22219304Speter txt_prompt(sp, ntp, prompt, flags); 22319304Speter 22419304Speter /* 22519304Speter * Swap old and new TEXT's, and insert the new TEXT 22619304Speter * into the queue. 22719304Speter */ 22819304Speter tp = ntp; 229254225Speter TAILQ_INSERT_TAIL(tiqh, tp, q); 23019304Speter break; 23119304Speter case K_CARAT: /* Delete autoindent chars. */ 23219304Speter if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) 23319304Speter carat_st = C_CARATSET; 23419304Speter goto ins_ch; 23519304Speter case K_ZERO: /* Delete autoindent chars. */ 23619304Speter if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) 23719304Speter carat_st = C_ZEROSET; 23819304Speter goto ins_ch; 23919304Speter case K_CNTRLD: /* Delete autoindent char. */ 24019304Speter /* 24119304Speter * !!! 24219304Speter * Historically, the ^D command took (but then ignored) 24319304Speter * a count. For simplicity, we don't return it unless 24419304Speter * it's the first character entered. The check for len 24519304Speter * equal to 0 is okay, TXT_AUTOINDENT won't be set. 24619304Speter */ 24719304Speter if (LF_ISSET(TXT_CNTRLD)) { 24819304Speter for (cnt = 0; cnt < tp->len; ++cnt) 24919304Speter if (!isblank(tp->lb[cnt])) 25019304Speter break; 25119304Speter if (cnt == tp->len) { 25219304Speter tp->len = 1; 25319304Speter tp->lb[0] = ev.e_c; 25419304Speter tp->lb[1] = '\0'; 25519304Speter 25619304Speter /* 25719304Speter * Put out a line separator, in case 25819304Speter * the command fails. 25919304Speter */ 26019304Speter (void)putchar('\n'); 26119304Speter goto done; 26219304Speter } 26319304Speter } 26419304Speter 26519304Speter /* 26619304Speter * POSIX 1003.1b-1993, paragraph 7.1.1.9, states that 26719304Speter * the EOF characters are discarded if there are other 26819304Speter * characters to process in the line, i.e. if the EOF 26919304Speter * is not the first character in the line. For this 27019304Speter * reason, historic ex discarded the EOF characters, 27119304Speter * even if occurring in the middle of the input line. 27219304Speter * We match that historic practice. 27319304Speter * 27419304Speter * !!! 27519304Speter * The test for discarding in the middle of the line is 27619304Speter * done in the switch, because the CARAT forms are N+1, 27719304Speter * not N. 27819304Speter * 27919304Speter * !!! 28019304Speter * There's considerable magic to make the terminal code 28119304Speter * return the EOF character at all. See that code for 28219304Speter * details. 28319304Speter */ 28419304Speter if (!LF_ISSET(TXT_AUTOINDENT) || tp->len == 0) 28519304Speter continue; 28619304Speter switch (carat_st) { 28719304Speter case C_CARATSET: /* ^^D */ 28819304Speter if (tp->len > tp->ai + 1) 28919304Speter continue; 29019304Speter 29119304Speter /* Save the ai string for later. */ 29219304Speter ait.lb = NULL; 29319304Speter ait.lb_len = 0; 294254225Speter BINC_GOTOW(sp, ait.lb, ait.lb_len, tp->ai); 295254225Speter MEMCPY(ait.lb, tp->lb, tp->ai); 29619304Speter ait.ai = ait.len = tp->ai; 29719304Speter 298254225Speter carat_st = C_NOTSET; 299254225Speter nochange = 1; 30019304Speter goto leftmargin; 30119304Speter case C_ZEROSET: /* 0^D */ 30219304Speter if (tp->len > tp->ai + 1) 30319304Speter continue; 30419304Speter 30519304Speter carat_st = C_NOTSET; 30619304Speterleftmargin: (void)gp->scr_ex_adjust(sp, EX_TERM_CE); 30719304Speter tp->ai = tp->len = 0; 30819304Speter break; 30919304Speter case C_NOTSET: /* ^D */ 31019304Speter if (tp->len > tp->ai) 31119304Speter continue; 31219304Speter 31319304Speter if (txt_dent(sp, tp)) 31419304Speter goto err; 31519304Speter break; 31619304Speter default: 31719304Speter abort(); 31819304Speter } 31919304Speter 32019304Speter /* Clear and redisplay the line. */ 32119304Speter (void)gp->scr_ex_adjust(sp, EX_TERM_CE); 32219304Speter txt_prompt(sp, tp, prompt, flags); 32319304Speter break; 32419304Speter default: 32519304Speter /* 32619304Speter * See the TXT_BEAUTIFY comment in vi/v_txt_ev.c. 32719304Speter * 32819304Speter * Silently eliminate any iscntrl() character that was 32919304Speter * not already handled specially, except for <tab> and 33019304Speter * <ff>. 33119304Speter */ 332254225Speterins_ch: if (LF_ISSET(TXT_BEAUTIFY) && ISCNTRL(ev.e_c) && 33319304Speter ev.e_value != K_FORMFEED && ev.e_value != K_TAB) 33419304Speter break; 33519304Speter 33619304Speter tp->lb[tp->len++] = ev.e_c; 33719304Speter break; 33819304Speter } 33919304Speter } 34019304Speter /* NOTREACHED */ 34119304Speter 34219304Speterdone: return (rval); 34319304Speter 34419304Spetererr: 34519304Speteralloc_err: 34619304Speter return (1); 34719304Speter} 34819304Speter 34919304Speter/* 35019304Speter * txt_prompt -- 35119304Speter * Display the ex prompt, line number, ai characters. Characters had 35219304Speter * better be printable by the terminal driver, but that's its problem, 35319304Speter * not ours. 35419304Speter */ 35519304Speterstatic void 356254225Spetertxt_prompt(SCR *sp, TEXT *tp, ARG_CHAR_T prompt, u_int32_t flags) 35719304Speter{ 35819304Speter /* Display the prompt. */ 35919304Speter if (LF_ISSET(TXT_PROMPT)) 360254225Speter (void)ex_printf(sp, "%c", prompt); 36119304Speter 36219304Speter /* Display the line number. */ 36319304Speter if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER)) 364254225Speter (void)ex_printf(sp, "%6lu ", (u_long)tp->lno); 36519304Speter 36619304Speter /* Print out autoindent string. */ 36719304Speter if (LF_ISSET(TXT_AUTOINDENT)) 368254225Speter (void)ex_printf(sp, WVS, (int)tp->ai, tp->lb); 369254225Speter (void)ex_fflush(sp); 37019304Speter} 37119304Speter 37219304Speter/* 37319304Speter * txt_dent -- 37419304Speter * Handle ^D outdents. 37519304Speter * 37619304Speter * Ex version of vi/v_ntext.c:txt_dent(). See that code for the (usual) 37719304Speter * ranting and raving. This is a fair bit simpler as ^T isn't special. 37819304Speter */ 37919304Speterstatic int 380254225Spetertxt_dent(SCR *sp, TEXT *tp) 38119304Speter{ 38219304Speter u_long sw, ts; 38319304Speter size_t cno, off, scno, spaces, tabs; 38419304Speter 38519304Speter ts = O_VAL(sp, O_TABSTOP); 38619304Speter sw = O_VAL(sp, O_SHIFTWIDTH); 38719304Speter 38819304Speter /* Get the current screen column. */ 38919304Speter for (off = scno = 0; off < tp->len; ++off) 39019304Speter if (tp->lb[off] == '\t') 39119304Speter scno += COL_OFF(scno, ts); 39219304Speter else 39319304Speter ++scno; 39419304Speter 39519304Speter /* Get the previous shiftwidth column. */ 396246874Sdim cno = scno--; 397246874Sdim scno -= scno % sw; 39819304Speter 39919304Speter /* 40019304Speter * Since we don't know what comes before the character(s) being 40119304Speter * deleted, we have to resolve the autoindent characters . The 40219304Speter * example is a <tab>, which doesn't take up a full shiftwidth 40319304Speter * number of columns because it's preceded by <space>s. This is 40419304Speter * easy to get if the user sets shiftwidth to a value less than 40519304Speter * tabstop, and then uses ^T to indent, and ^D to outdent. 40619304Speter * 40719304Speter * Count up spaces/tabs needed to get to the target. 40819304Speter */ 40919304Speter for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs) 41019304Speter cno += COL_OFF(cno, ts); 41119304Speter spaces = scno - cno; 41219304Speter 41319304Speter /* Make sure there's enough room. */ 414254225Speter BINC_RETW(sp, tp->lb, tp->lb_len, tabs + spaces + 1); 41519304Speter 41619304Speter /* Adjust the final ai character count. */ 41719304Speter tp->ai = tabs + spaces; 41819304Speter 41919304Speter /* Enter the replacement characters. */ 42019304Speter for (tp->len = 0; tabs > 0; --tabs) 42119304Speter tp->lb[tp->len++] = '\t'; 42219304Speter for (; spaces > 0; --spaces) 42319304Speter tp->lb[tp->len++] = ' '; 42419304Speter return (0); 42519304Speter} 426