michael@0: /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Copyright 1992,1993 Simmule Turner and Rich Salz. All rights reserved. michael@0: * michael@0: * This software is not subject to any license of the American Telephone michael@0: * and Telegraph Company or of the Regents of the University of California. michael@0: * michael@0: * Permission is granted to anyone to use this software for any purpose on michael@0: * any computer system, and to alter it and redistribute it freely, subject michael@0: * to the following restrictions: michael@0: * 1. The authors are not responsible for the consequences of use of this michael@0: * software, no matter how awful, even if they arise from flaws in it. michael@0: * 2. The origin of this software must not be misrepresented, either by michael@0: * explicit claim or by omission. Since few users ever read sources, michael@0: * credits must appear in the documentation. michael@0: * 3. Altered versions must be plainly marked as such, and must not be michael@0: * misrepresented as being the original software. Since few users michael@0: * ever read sources, credits must appear in the documentation. michael@0: * 4. This notice may not be removed or altered. michael@0: */ michael@0: michael@0: michael@0: /* michael@0: ** Main editing routines for editline library. michael@0: */ michael@0: #include "editline.h" michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: /* michael@0: ** Manifest constants. michael@0: */ michael@0: #define SCREEN_WIDTH 80 michael@0: #define SCREEN_ROWS 24 michael@0: #define NO_ARG (-1) michael@0: #define DEL 127 michael@0: #define CTL(x) ((x) & 0x1F) michael@0: #define ISCTL(x) ((x) && (x) < ' ') michael@0: #define UNCTL(x) ((x) + 64) michael@0: #define META(x) ((x) | 0x80) michael@0: #define ISMETA(x) ((x) & 0x80) michael@0: #define UNMETA(x) ((x) & 0x7F) michael@0: #if !defined(HIST_SIZE) michael@0: #define HIST_SIZE 20 michael@0: #endif /* !defined(HIST_SIZE) */ michael@0: michael@0: /* michael@0: ** Command status codes. michael@0: */ michael@0: typedef enum _STATUS { michael@0: CSdone, CSeof, CSmove, CSdispatch, CSstay, CSsignal michael@0: } STATUS; michael@0: michael@0: /* michael@0: ** The type of case-changing to perform. michael@0: */ michael@0: typedef enum _CASE { michael@0: TOupper, TOlower michael@0: } CASE; michael@0: michael@0: /* michael@0: ** Key to command mapping. michael@0: */ michael@0: typedef struct _KEYMAP { michael@0: CHAR Key; michael@0: STATUS (*Function)(); michael@0: } KEYMAP; michael@0: michael@0: /* michael@0: ** Command history structure. michael@0: */ michael@0: typedef struct _HISTORY { michael@0: int Size; michael@0: int Pos; michael@0: CHAR *Lines[HIST_SIZE]; michael@0: } HISTORY; michael@0: michael@0: /* michael@0: ** Globals. michael@0: */ michael@0: unsigned rl_eof; michael@0: unsigned rl_erase; michael@0: unsigned rl_intr; michael@0: unsigned rl_kill; michael@0: unsigned rl_quit; michael@0: michael@0: STATIC CHAR NIL[] = ""; michael@0: STATIC CONST CHAR *Input = NIL; michael@0: STATIC CHAR *Line; michael@0: STATIC CONST char *Prompt; michael@0: STATIC CHAR *Yanked; michael@0: STATIC char *Screen; michael@0: STATIC CONST char NEWLINE[]= CRLF; michael@0: STATIC HISTORY H; michael@0: STATIC int Repeat; michael@0: STATIC int End; michael@0: STATIC int Mark; michael@0: STATIC int OldPoint; michael@0: STATIC int Point; michael@0: STATIC int PushBack; michael@0: STATIC int Pushed; michael@0: STATIC int Signal; michael@0: FORWARD CONST KEYMAP Map[32]; michael@0: FORWARD CONST KEYMAP MetaMap[16]; michael@0: STATIC SIZE_T Length; michael@0: STATIC SIZE_T ScreenCount; michael@0: STATIC SIZE_T ScreenSize; michael@0: STATIC char *backspace; michael@0: STATIC int TTYwidth; michael@0: STATIC int TTYrows; michael@0: michael@0: /* Display print 8-bit chars as `M-x' or as the actual 8-bit char? */ michael@0: int rl_meta_chars = 0; michael@0: michael@0: /* michael@0: ** Declarations. michael@0: */ michael@0: STATIC CHAR *editinput(); michael@0: #if defined(USE_TERMCAP) michael@0: #include michael@0: #include michael@0: #include michael@0: #endif /* defined(USE_TERMCAP) */ michael@0: michael@0: /* michael@0: ** TTY input/output functions. michael@0: */ michael@0: michael@0: STATIC void michael@0: TTYflush() michael@0: { michael@0: if (ScreenCount) { michael@0: /* Dummy assignment avoids GCC warning on michael@0: * "attribute warn_unused_result" */ michael@0: ssize_t dummy = write(1, Screen, ScreenCount); michael@0: (void)dummy; michael@0: ScreenCount = 0; michael@0: } michael@0: } michael@0: michael@0: STATIC void michael@0: TTYput(c) michael@0: CHAR c; michael@0: { michael@0: Screen[ScreenCount] = c; michael@0: if (++ScreenCount >= ScreenSize - 1) { michael@0: ScreenSize += SCREEN_INC; michael@0: RENEW(Screen, char, ScreenSize); michael@0: } michael@0: } michael@0: michael@0: STATIC void michael@0: TTYputs(p) michael@0: CONST CHAR *p; michael@0: { michael@0: while (*p) michael@0: TTYput(*p++); michael@0: } michael@0: michael@0: STATIC void michael@0: TTYshow(c) michael@0: CHAR c; michael@0: { michael@0: if (c == DEL) { michael@0: TTYput('^'); michael@0: TTYput('?'); michael@0: } michael@0: else if (ISCTL(c)) { michael@0: TTYput('^'); michael@0: TTYput(UNCTL(c)); michael@0: } michael@0: else if (rl_meta_chars && ISMETA(c)) { michael@0: TTYput('M'); michael@0: TTYput('-'); michael@0: TTYput(UNMETA(c)); michael@0: } michael@0: else michael@0: TTYput(c); michael@0: } michael@0: michael@0: STATIC void michael@0: TTYstring(p) michael@0: CHAR *p; michael@0: { michael@0: while (*p) michael@0: TTYshow(*p++); michael@0: } michael@0: michael@0: STATIC unsigned int michael@0: TTYget() michael@0: { michael@0: CHAR c; michael@0: michael@0: TTYflush(); michael@0: if (Pushed) { michael@0: Pushed = 0; michael@0: return PushBack; michael@0: } michael@0: if (*Input) michael@0: return *Input++; michael@0: return read(0, &c, (SIZE_T)1) == 1 ? c : EOF; michael@0: } michael@0: michael@0: #define TTYback() (backspace ? TTYputs((CHAR *)backspace) : TTYput('\b')) michael@0: michael@0: STATIC void michael@0: TTYbackn(n) michael@0: int n; michael@0: { michael@0: while (--n >= 0) michael@0: TTYback(); michael@0: } michael@0: michael@0: STATIC void michael@0: TTYinfo() michael@0: { michael@0: static int init; michael@0: #if defined(USE_TERMCAP) michael@0: char *term; michael@0: char buff[2048]; michael@0: char *bp, *p; michael@0: #endif /* defined(USE_TERMCAP) */ michael@0: #if defined(TIOCGWINSZ) michael@0: struct winsize W; michael@0: #endif /* defined(TIOCGWINSZ) */ michael@0: michael@0: if (init) { michael@0: #if defined(TIOCGWINSZ) michael@0: /* Perhaps we got resized. */ michael@0: if (ioctl(0, TIOCGWINSZ, &W) >= 0 michael@0: && W.ws_col > 0 && W.ws_row > 0) { michael@0: TTYwidth = (int)W.ws_col; michael@0: TTYrows = (int)W.ws_row; michael@0: } michael@0: #endif /* defined(TIOCGWINSZ) */ michael@0: return; michael@0: } michael@0: init++; michael@0: michael@0: TTYwidth = TTYrows = 0; michael@0: #if defined(USE_TERMCAP) michael@0: bp = &buff[0]; michael@0: if ((term = getenv("TERM")) == NULL) michael@0: term = "dumb"; michael@0: if (tgetent(buff, term) < 0) { michael@0: TTYwidth = SCREEN_WIDTH; michael@0: TTYrows = SCREEN_ROWS; michael@0: return; michael@0: } michael@0: p = tgetstr("le", &bp); michael@0: backspace = p ? strdup(p) : NULL; michael@0: TTYwidth = tgetnum("co"); michael@0: TTYrows = tgetnum("li"); michael@0: #endif /* defined(USE_TERMCAP) */ michael@0: michael@0: #if defined(TIOCGWINSZ) michael@0: if (ioctl(0, TIOCGWINSZ, &W) >= 0) { michael@0: TTYwidth = (int)W.ws_col; michael@0: TTYrows = (int)W.ws_row; michael@0: } michael@0: #endif /* defined(TIOCGWINSZ) */ michael@0: michael@0: if (TTYwidth <= 0 || TTYrows <= 0) { michael@0: TTYwidth = SCREEN_WIDTH; michael@0: TTYrows = SCREEN_ROWS; michael@0: } michael@0: } michael@0: michael@0: michael@0: STATIC void michael@0: reposition() michael@0: { michael@0: int i; michael@0: CHAR *p; michael@0: michael@0: TTYput('\r'); michael@0: TTYputs((CONST CHAR *)Prompt); michael@0: for (i = Point, p = Line; --i >= 0; p++) michael@0: TTYshow(*p); michael@0: } michael@0: michael@0: STATIC void michael@0: left(Change) michael@0: STATUS Change; michael@0: { michael@0: TTYback(); michael@0: if (Point) { michael@0: if (ISCTL(Line[Point - 1])) michael@0: TTYback(); michael@0: else if (rl_meta_chars && ISMETA(Line[Point - 1])) { michael@0: TTYback(); michael@0: TTYback(); michael@0: } michael@0: } michael@0: if (Change == CSmove) michael@0: Point--; michael@0: } michael@0: michael@0: STATIC void michael@0: right(Change) michael@0: STATUS Change; michael@0: { michael@0: TTYshow(Line[Point]); michael@0: if (Change == CSmove) michael@0: Point++; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: ring_bell() michael@0: { michael@0: TTYput('\07'); michael@0: TTYflush(); michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: do_macro(c) michael@0: unsigned int c; michael@0: { michael@0: CHAR name[4]; michael@0: michael@0: name[0] = '_'; michael@0: name[1] = c; michael@0: name[2] = '_'; michael@0: name[3] = '\0'; michael@0: michael@0: if ((Input = (CHAR *)getenv((char *)name)) == NULL) { michael@0: Input = NIL; michael@0: return ring_bell(); michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: do_forward(move) michael@0: STATUS move; michael@0: { michael@0: int i; michael@0: CHAR *p; michael@0: michael@0: i = 0; michael@0: do { michael@0: p = &Line[Point]; michael@0: for ( ; Point < End && (*p == ' ' || !isalnum(*p)); Point++, p++) michael@0: if (move == CSmove) michael@0: right(CSstay); michael@0: michael@0: for (; Point < End && isalnum(*p); Point++, p++) michael@0: if (move == CSmove) michael@0: right(CSstay); michael@0: michael@0: if (Point == End) michael@0: break; michael@0: } while (++i < Repeat); michael@0: michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: do_case(type) michael@0: CASE type; michael@0: { michael@0: int i; michael@0: int end; michael@0: int count; michael@0: CHAR *p; michael@0: michael@0: (void)do_forward(CSstay); michael@0: if (OldPoint != Point) { michael@0: if ((count = Point - OldPoint) < 0) michael@0: count = -count; michael@0: Point = OldPoint; michael@0: if ((end = Point + count) > End) michael@0: end = End; michael@0: for (i = Point, p = &Line[i]; i < end; i++, p++) { michael@0: if (type == TOupper) { michael@0: if (islower(*p)) michael@0: *p = toupper(*p); michael@0: } michael@0: else if (isupper(*p)) michael@0: *p = tolower(*p); michael@0: right(CSmove); michael@0: } michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: case_down_word() michael@0: { michael@0: return do_case(TOlower); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: case_up_word() michael@0: { michael@0: return do_case(TOupper); michael@0: } michael@0: michael@0: STATIC void michael@0: ceol() michael@0: { michael@0: int extras; michael@0: int i; michael@0: CHAR *p; michael@0: michael@0: for (extras = 0, i = Point, p = &Line[i]; i <= End; i++, p++) { michael@0: TTYput(' '); michael@0: if (ISCTL(*p)) { michael@0: TTYput(' '); michael@0: extras++; michael@0: } michael@0: else if (rl_meta_chars && ISMETA(*p)) { michael@0: TTYput(' '); michael@0: TTYput(' '); michael@0: extras += 2; michael@0: } michael@0: } michael@0: michael@0: for (i += extras; i > Point; i--) michael@0: TTYback(); michael@0: } michael@0: michael@0: STATIC void michael@0: clear_line() michael@0: { michael@0: Point = -strlen(Prompt); michael@0: TTYput('\r'); michael@0: ceol(); michael@0: Point = 0; michael@0: End = 0; michael@0: Line[0] = '\0'; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: insert_string(p) michael@0: CHAR *p; michael@0: { michael@0: SIZE_T len; michael@0: int i; michael@0: CHAR *new; michael@0: CHAR *q; michael@0: michael@0: len = strlen((char *)p); michael@0: if (End + len >= Length) { michael@0: if ((new = NEW(CHAR, Length + len + MEM_INC)) == NULL) michael@0: return CSstay; michael@0: if (Length) { michael@0: COPYFROMTO(new, Line, Length); michael@0: DISPOSE(Line); michael@0: } michael@0: Line = new; michael@0: Length += len + MEM_INC; michael@0: } michael@0: michael@0: for (q = &Line[Point], i = End - Point; --i >= 0; ) michael@0: q[len + i] = q[i]; michael@0: COPYFROMTO(&Line[Point], p, len); michael@0: End += len; michael@0: Line[End] = '\0'; michael@0: TTYstring(&Line[Point]); michael@0: Point += len; michael@0: michael@0: return Point == End ? CSstay : CSmove; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: redisplay() michael@0: { michael@0: TTYputs((CONST CHAR *)NEWLINE); michael@0: TTYputs((CONST CHAR *)Prompt); michael@0: TTYstring(Line); michael@0: return CSmove; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: toggle_meta_mode() michael@0: { michael@0: rl_meta_chars = ! rl_meta_chars; michael@0: return redisplay(); michael@0: } michael@0: michael@0: michael@0: STATIC CHAR * michael@0: next_hist() michael@0: { michael@0: return H.Pos >= H.Size - 1 ? NULL : H.Lines[++H.Pos]; michael@0: } michael@0: michael@0: STATIC CHAR * michael@0: prev_hist() michael@0: { michael@0: return H.Pos == 0 ? NULL : H.Lines[--H.Pos]; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: do_insert_hist(p) michael@0: CHAR *p; michael@0: { michael@0: if (p == NULL) michael@0: return ring_bell(); michael@0: Point = 0; michael@0: reposition(); michael@0: ceol(); michael@0: End = 0; michael@0: return insert_string(p); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: do_hist(move) michael@0: CHAR *(*move)(); michael@0: { michael@0: CHAR *p; michael@0: int i; michael@0: michael@0: i = 0; michael@0: do { michael@0: if ((p = (*move)()) == NULL) michael@0: return ring_bell(); michael@0: } while (++i < Repeat); michael@0: return do_insert_hist(p); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: h_next() michael@0: { michael@0: return do_hist(next_hist); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: h_prev() michael@0: { michael@0: return do_hist(prev_hist); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: h_first() michael@0: { michael@0: return do_insert_hist(H.Lines[H.Pos = 0]); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: h_last() michael@0: { michael@0: return do_insert_hist(H.Lines[H.Pos = H.Size - 1]); michael@0: } michael@0: michael@0: /* michael@0: ** Return zero if pat appears as a substring in text. michael@0: */ michael@0: STATIC int michael@0: substrcmp(text, pat, len) michael@0: char *text; michael@0: char *pat; michael@0: int len; michael@0: { michael@0: char c; michael@0: michael@0: if ((c = *pat) == '\0') michael@0: return *text == '\0'; michael@0: for ( ; *text; text++) michael@0: if (*text == c && strncmp(text, pat, len) == 0) michael@0: return 0; michael@0: return 1; michael@0: } michael@0: michael@0: STATIC CHAR * michael@0: search_hist(search, move) michael@0: CHAR *search; michael@0: CHAR *(*move)(); michael@0: { michael@0: static CHAR *old_search; michael@0: int len; michael@0: int pos; michael@0: int (*match)(); michael@0: char *pat; michael@0: michael@0: /* Save or get remembered search pattern. */ michael@0: if (search && *search) { michael@0: if (old_search) michael@0: DISPOSE(old_search); michael@0: old_search = (CHAR *)strdup((char *)search); michael@0: } michael@0: else { michael@0: if (old_search == NULL || *old_search == '\0') michael@0: return NULL; michael@0: search = old_search; michael@0: } michael@0: michael@0: /* Set up pattern-finder. */ michael@0: if (*search == '^') { michael@0: match = strncmp; michael@0: pat = (char *)(search + 1); michael@0: } michael@0: else { michael@0: match = substrcmp; michael@0: pat = (char *)search; michael@0: } michael@0: len = strlen(pat); michael@0: michael@0: for (pos = H.Pos; (*move)() != NULL; ) michael@0: if ((*match)((char *)H.Lines[H.Pos], pat, len) == 0) michael@0: return H.Lines[H.Pos]; michael@0: H.Pos = pos; michael@0: return NULL; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: h_search() michael@0: { michael@0: static int Searching; michael@0: CONST char *old_prompt; michael@0: CHAR *(*move)(); michael@0: CHAR *p; michael@0: michael@0: if (Searching) michael@0: return ring_bell(); michael@0: Searching = 1; michael@0: michael@0: clear_line(); michael@0: old_prompt = Prompt; michael@0: Prompt = "Search: "; michael@0: TTYputs((CONST CHAR *)Prompt); michael@0: move = Repeat == NO_ARG ? prev_hist : next_hist; michael@0: p = editinput(); michael@0: Prompt = old_prompt; michael@0: Searching = 0; michael@0: TTYputs((CONST CHAR *)Prompt); michael@0: if (p == NULL && Signal > 0) { michael@0: Signal = 0; michael@0: clear_line(); michael@0: return redisplay(); michael@0: } michael@0: p = search_hist(p, move); michael@0: clear_line(); michael@0: if (p == NULL) { michael@0: (void)ring_bell(); michael@0: return redisplay(); michael@0: } michael@0: return do_insert_hist(p); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: fd_char() michael@0: { michael@0: int i; michael@0: michael@0: i = 0; michael@0: do { michael@0: if (Point >= End) michael@0: break; michael@0: right(CSmove); michael@0: } while (++i < Repeat); michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC void michael@0: save_yank(begin, i) michael@0: int begin; michael@0: int i; michael@0: { michael@0: if (Yanked) { michael@0: DISPOSE(Yanked); michael@0: Yanked = NULL; michael@0: } michael@0: michael@0: if (i < 1) michael@0: return; michael@0: michael@0: if ((Yanked = NEW(CHAR, (SIZE_T)i + 1)) != NULL) { michael@0: COPYFROMTO(Yanked, &Line[begin], i); michael@0: Yanked[i] = '\0'; michael@0: } michael@0: } michael@0: michael@0: STATIC STATUS michael@0: delete_string(count) michael@0: int count; michael@0: { michael@0: int i; michael@0: CHAR *p; michael@0: michael@0: if (count <= 0 || End == Point) michael@0: return ring_bell(); michael@0: michael@0: if (count == 1 && Point == End - 1) { michael@0: /* Optimize common case of delete at end of line. */ michael@0: End--; michael@0: p = &Line[Point]; michael@0: i = 1; michael@0: TTYput(' '); michael@0: if (ISCTL(*p)) { michael@0: i = 2; michael@0: TTYput(' '); michael@0: } michael@0: else if (rl_meta_chars && ISMETA(*p)) { michael@0: i = 3; michael@0: TTYput(' '); michael@0: TTYput(' '); michael@0: } michael@0: TTYbackn(i); michael@0: *p = '\0'; michael@0: return CSmove; michael@0: } michael@0: if (Point + count > End && (count = End - Point) <= 0) michael@0: return CSstay; michael@0: michael@0: if (count > 1) michael@0: save_yank(Point, count); michael@0: michael@0: for (p = &Line[Point], i = End - (Point + count) + 1; --i >= 0; p++) michael@0: p[0] = p[count]; michael@0: ceol(); michael@0: End -= count; michael@0: TTYstring(&Line[Point]); michael@0: return CSmove; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: bk_char() michael@0: { michael@0: int i; michael@0: michael@0: i = 0; michael@0: do { michael@0: if (Point == 0) michael@0: break; michael@0: left(CSmove); michael@0: } while (++i < Repeat); michael@0: michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: bk_del_char() michael@0: { michael@0: int i; michael@0: michael@0: i = 0; michael@0: do { michael@0: if (Point == 0) michael@0: break; michael@0: left(CSmove); michael@0: } while (++i < Repeat); michael@0: michael@0: return delete_string(i); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: kill_line() michael@0: { michael@0: int i; michael@0: michael@0: if (Repeat != NO_ARG) { michael@0: if (Repeat < Point) { michael@0: i = Point; michael@0: Point = Repeat; michael@0: reposition(); michael@0: (void)delete_string(i - Point); michael@0: } michael@0: else if (Repeat > Point) { michael@0: right(CSmove); michael@0: (void)delete_string(Repeat - Point - 1); michael@0: } michael@0: return CSmove; michael@0: } michael@0: michael@0: save_yank(Point, End - Point); michael@0: Line[Point] = '\0'; michael@0: ceol(); michael@0: End = Point; michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: insert_char(c) michael@0: int c; michael@0: { michael@0: STATUS s; michael@0: CHAR buff[2]; michael@0: CHAR *p; michael@0: CHAR *q; michael@0: int i; michael@0: michael@0: if (Repeat == NO_ARG || Repeat < 2) { michael@0: buff[0] = c; michael@0: buff[1] = '\0'; michael@0: return insert_string(buff); michael@0: } michael@0: michael@0: if ((p = NEW(CHAR, Repeat + 1)) == NULL) michael@0: return CSstay; michael@0: for (i = Repeat, q = p; --i >= 0; ) michael@0: *q++ = c; michael@0: *q = '\0'; michael@0: Repeat = 0; michael@0: s = insert_string(p); michael@0: DISPOSE(p); michael@0: return s; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: meta() michael@0: { michael@0: unsigned int c; michael@0: CONST KEYMAP *kp; michael@0: michael@0: if ((int)(c = TTYget()) == EOF) michael@0: return CSeof; michael@0: #if defined(ANSI_ARROWS) michael@0: /* Also include VT-100 arrows. */ michael@0: if (c == '[' || c == 'O') michael@0: switch (c = TTYget()) { michael@0: default: return ring_bell(); michael@0: case EOF: return CSeof; michael@0: case 'A': return h_prev(); michael@0: case 'B': return h_next(); michael@0: case 'C': return fd_char(); michael@0: case 'D': return bk_char(); michael@0: } michael@0: #endif /* defined(ANSI_ARROWS) */ michael@0: michael@0: if (isdigit(c)) { michael@0: for (Repeat = c - '0'; (int)(c = TTYget()) != EOF && isdigit(c); ) michael@0: Repeat = Repeat * 10 + c - '0'; michael@0: Pushed = 1; michael@0: PushBack = c; michael@0: return CSstay; michael@0: } michael@0: michael@0: if (isupper(c)) michael@0: return do_macro(c); michael@0: for (OldPoint = Point, kp = MetaMap; kp->Function; kp++) michael@0: if (kp->Key == c) michael@0: return (*kp->Function)(); michael@0: michael@0: return ring_bell(); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: emacs(c) michael@0: unsigned int c; michael@0: { michael@0: STATUS s; michael@0: const KEYMAP *kp; michael@0: michael@0: if (rl_meta_chars && ISMETA(c)) { michael@0: Pushed = 1; michael@0: PushBack = UNMETA(c); michael@0: return meta(); michael@0: } michael@0: for (kp = Map; kp->Function; kp++) michael@0: if (kp->Key == c) michael@0: break; michael@0: s = kp->Function ? (*kp->Function)() : insert_char((int)c); michael@0: if (!Pushed) michael@0: /* No pushback means no repeat count; hacky, but true. */ michael@0: Repeat = NO_ARG; michael@0: return s; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: TTYspecial(c) michael@0: unsigned int c; michael@0: { michael@0: if (ISMETA(c)) michael@0: return CSdispatch; michael@0: michael@0: if (c == rl_erase || (int)c == DEL) michael@0: return bk_del_char(); michael@0: if (c == rl_kill) { michael@0: if (Point != 0) { michael@0: Point = 0; michael@0: reposition(); michael@0: } michael@0: Repeat = NO_ARG; michael@0: return kill_line(); michael@0: } michael@0: if (c == rl_eof && Point == 0 && End == 0) michael@0: return CSeof; michael@0: if (c == rl_intr) { michael@0: Signal = SIGINT; michael@0: return CSsignal; michael@0: } michael@0: if (c == rl_quit) { michael@0: Signal = SIGQUIT; michael@0: return CSeof; michael@0: } michael@0: michael@0: return CSdispatch; michael@0: } michael@0: michael@0: STATIC CHAR * michael@0: editinput() michael@0: { michael@0: unsigned int c; michael@0: michael@0: Repeat = NO_ARG; michael@0: OldPoint = Point = Mark = End = 0; michael@0: Line[0] = '\0'; michael@0: michael@0: Signal = -1; michael@0: while ((int)(c = TTYget()) != EOF) michael@0: switch (TTYspecial(c)) { michael@0: case CSdone: michael@0: return Line; michael@0: case CSeof: michael@0: return NULL; michael@0: case CSsignal: michael@0: return (CHAR *)""; michael@0: case CSmove: michael@0: reposition(); michael@0: break; michael@0: case CSdispatch: michael@0: switch (emacs(c)) { michael@0: case CSdone: michael@0: return Line; michael@0: case CSeof: michael@0: return NULL; michael@0: case CSsignal: michael@0: return (CHAR *)""; michael@0: case CSmove: michael@0: reposition(); michael@0: break; michael@0: case CSdispatch: michael@0: case CSstay: michael@0: break; michael@0: } michael@0: break; michael@0: case CSstay: michael@0: break; michael@0: } michael@0: if (strlen((char *)Line)) michael@0: return Line; michael@0: free(Line); michael@0: return NULL; michael@0: } michael@0: michael@0: STATIC void michael@0: hist_add(p) michael@0: CHAR *p; michael@0: { michael@0: int i; michael@0: michael@0: if ((p = (CHAR *)strdup((char *)p)) == NULL) michael@0: return; michael@0: if (H.Size < HIST_SIZE) michael@0: H.Lines[H.Size++] = p; michael@0: else { michael@0: DISPOSE(H.Lines[0]); michael@0: for (i = 0; i < HIST_SIZE - 1; i++) michael@0: H.Lines[i] = H.Lines[i + 1]; michael@0: H.Lines[i] = p; michael@0: } michael@0: H.Pos = H.Size - 1; michael@0: } michael@0: michael@0: /* michael@0: ** For compatibility with FSF readline. michael@0: */ michael@0: /* ARGSUSED0 */ michael@0: void michael@0: rl_reset_terminal(p) michael@0: char *p; michael@0: { michael@0: (void)p; michael@0: } michael@0: michael@0: void michael@0: rl_initialize() michael@0: { michael@0: } michael@0: michael@0: char * michael@0: readline(prompt) michael@0: CONST char *prompt; michael@0: { michael@0: CHAR *line; michael@0: int s; michael@0: michael@0: if (Line == NULL) { michael@0: Length = MEM_INC; michael@0: if ((Line = NEW(CHAR, Length)) == NULL) michael@0: return NULL; michael@0: } michael@0: michael@0: TTYinfo(); michael@0: rl_ttyset(0); michael@0: hist_add(NIL); michael@0: ScreenSize = SCREEN_INC; michael@0: Screen = NEW(char, ScreenSize); michael@0: Prompt = prompt ? prompt : (char *)NIL; michael@0: TTYputs((CONST CHAR *)Prompt); michael@0: if ((line = editinput()) != NULL) { michael@0: line = (CHAR *)strdup((char *)line); michael@0: TTYputs((CONST CHAR *)NEWLINE); michael@0: TTYflush(); michael@0: } michael@0: rl_ttyset(1); michael@0: DISPOSE(Screen); michael@0: DISPOSE(H.Lines[--H.Size]); michael@0: if (Signal > 0) { michael@0: s = Signal; michael@0: Signal = 0; michael@0: (void)kill(getpid(), s); michael@0: } michael@0: return (char *)line; michael@0: } michael@0: michael@0: void michael@0: add_history(p) michael@0: char *p; michael@0: { michael@0: if (p == NULL || *p == '\0') michael@0: return; michael@0: michael@0: #if defined(UNIQUE_HISTORY) michael@0: if (H.Size && strcmp(p, (char *)H.Lines[H.Size - 1]) == 0) michael@0: return; michael@0: #endif /* defined(UNIQUE_HISTORY) */ michael@0: hist_add((CHAR *)p); michael@0: } michael@0: michael@0: michael@0: STATIC STATUS michael@0: beg_line() michael@0: { michael@0: if (Point) { michael@0: Point = 0; michael@0: return CSmove; michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: del_char() michael@0: { michael@0: return delete_string(Repeat == NO_ARG ? 1 : Repeat); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: end_line() michael@0: { michael@0: if (Point != End) { michael@0: Point = End; michael@0: return CSmove; michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: accept_line() michael@0: { michael@0: Line[End] = '\0'; michael@0: return CSdone; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: transpose() michael@0: { michael@0: CHAR c; michael@0: michael@0: if (Point) { michael@0: if (Point == End) michael@0: left(CSmove); michael@0: c = Line[Point - 1]; michael@0: left(CSstay); michael@0: Line[Point - 1] = Line[Point]; michael@0: TTYshow(Line[Point - 1]); michael@0: Line[Point++] = c; michael@0: TTYshow(c); michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: quote() michael@0: { michael@0: unsigned int c; michael@0: michael@0: return (int)(c = TTYget()) == EOF ? CSeof : insert_char((int)c); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: wipe() michael@0: { michael@0: int i; michael@0: michael@0: if (Mark > End) michael@0: return ring_bell(); michael@0: michael@0: if (Point > Mark) { michael@0: i = Point; michael@0: Point = Mark; michael@0: Mark = i; michael@0: reposition(); michael@0: } michael@0: michael@0: return delete_string(Mark - Point); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: mk_set() michael@0: { michael@0: Mark = Point; michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: exchange() michael@0: { michael@0: unsigned int c; michael@0: michael@0: if ((c = TTYget()) != CTL('X')) michael@0: return (int)c == EOF ? CSeof : ring_bell(); michael@0: michael@0: if ((int)(c = Mark) <= End) { michael@0: Mark = Point; michael@0: Point = c; michael@0: return CSmove; michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: yank() michael@0: { michael@0: if (Yanked && *Yanked) michael@0: return insert_string(Yanked); michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: copy_region() michael@0: { michael@0: if (Mark > End) michael@0: return ring_bell(); michael@0: michael@0: if (Point > Mark) michael@0: save_yank(Mark, Point - Mark); michael@0: else michael@0: save_yank(Point, Mark - Point); michael@0: michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: move_to_char() michael@0: { michael@0: unsigned int c; michael@0: int i; michael@0: CHAR *p; michael@0: michael@0: if ((int)(c = TTYget()) == EOF) michael@0: return CSeof; michael@0: for (i = Point + 1, p = &Line[i]; i < End; i++, p++) michael@0: if (*p == c) { michael@0: Point = i; michael@0: return CSmove; michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: fd_word() michael@0: { michael@0: return do_forward(CSmove); michael@0: } michael@0: michael@0: STATIC STATUS michael@0: fd_kill_word() michael@0: { michael@0: int i; michael@0: michael@0: (void)do_forward(CSstay); michael@0: if (OldPoint != Point) { michael@0: i = Point - OldPoint; michael@0: Point = OldPoint; michael@0: return delete_string(i); michael@0: } michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: bk_word() michael@0: { michael@0: int i; michael@0: CHAR *p; michael@0: michael@0: i = 0; michael@0: do { michael@0: for (p = &Line[Point]; p > Line && !isalnum(p[-1]); p--) michael@0: left(CSmove); michael@0: michael@0: for (; p > Line && p[-1] != ' ' && isalnum(p[-1]); p--) michael@0: left(CSmove); michael@0: michael@0: if (Point == 0) michael@0: break; michael@0: } while (++i < Repeat); michael@0: michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: bk_kill_word() michael@0: { michael@0: (void)bk_word(); michael@0: if (OldPoint != Point) michael@0: return delete_string(OldPoint - Point); michael@0: return CSstay; michael@0: } michael@0: michael@0: STATIC int michael@0: argify(line, avp) michael@0: CHAR *line; michael@0: CHAR ***avp; michael@0: { michael@0: CHAR *c; michael@0: CHAR **p; michael@0: CHAR **new; michael@0: int ac; michael@0: int i; michael@0: michael@0: i = MEM_INC; michael@0: if ((*avp = p = NEW(CHAR*, i))== NULL) michael@0: return 0; michael@0: michael@0: for (c = line; isspace(*c); c++) michael@0: continue; michael@0: if (*c == '\n' || *c == '\0') michael@0: return 0; michael@0: michael@0: for (ac = 0, p[ac++] = c; *c && *c != '\n'; ) { michael@0: if (isspace(*c)) { michael@0: *c++ = '\0'; michael@0: if (*c && *c != '\n') { michael@0: if (ac + 1 == i) { michael@0: new = NEW(CHAR*, i + MEM_INC); michael@0: if (new == NULL) { michael@0: p[ac] = NULL; michael@0: return ac; michael@0: } michael@0: COPYFROMTO(new, p, i * sizeof (char **)); michael@0: i += MEM_INC; michael@0: DISPOSE(p); michael@0: *avp = p = new; michael@0: } michael@0: p[ac++] = c; michael@0: } michael@0: } michael@0: else michael@0: c++; michael@0: } michael@0: *c = '\0'; michael@0: p[ac] = NULL; michael@0: return ac; michael@0: } michael@0: michael@0: STATIC STATUS michael@0: last_argument() michael@0: { michael@0: CHAR **av; michael@0: CHAR *p; michael@0: STATUS s; michael@0: int ac; michael@0: michael@0: if (H.Size == 1 || (p = H.Lines[H.Size - 2]) == NULL) michael@0: return ring_bell(); michael@0: michael@0: if ((p = (CHAR *)strdup((char *)p)) == NULL) michael@0: return CSstay; michael@0: ac = argify(p, &av); michael@0: michael@0: if (Repeat != NO_ARG) michael@0: s = Repeat < ac ? insert_string(av[Repeat]) : ring_bell(); michael@0: else michael@0: s = ac ? insert_string(av[ac - 1]) : CSstay; michael@0: michael@0: if (ac) michael@0: DISPOSE(av); michael@0: DISPOSE(p); michael@0: return s; michael@0: } michael@0: michael@0: STATIC CONST KEYMAP Map[32] = { michael@0: { CTL('@'), ring_bell }, michael@0: { CTL('A'), beg_line }, michael@0: { CTL('B'), bk_char }, michael@0: { CTL('D'), del_char }, michael@0: { CTL('E'), end_line }, michael@0: { CTL('F'), fd_char }, michael@0: { CTL('G'), ring_bell }, michael@0: { CTL('H'), bk_del_char }, michael@0: { CTL('J'), accept_line }, michael@0: { CTL('K'), kill_line }, michael@0: { CTL('L'), redisplay }, michael@0: { CTL('M'), accept_line }, michael@0: { CTL('N'), h_next }, michael@0: { CTL('O'), ring_bell }, michael@0: { CTL('P'), h_prev }, michael@0: { CTL('Q'), ring_bell }, michael@0: { CTL('R'), h_search }, michael@0: { CTL('S'), ring_bell }, michael@0: { CTL('T'), transpose }, michael@0: { CTL('U'), ring_bell }, michael@0: { CTL('V'), quote }, michael@0: { CTL('W'), wipe }, michael@0: { CTL('X'), exchange }, michael@0: { CTL('Y'), yank }, michael@0: { CTL('Z'), ring_bell }, michael@0: { CTL('['), meta }, michael@0: { CTL(']'), move_to_char }, michael@0: { CTL('^'), ring_bell }, michael@0: { CTL('_'), ring_bell }, michael@0: { 0, NULL } michael@0: }; michael@0: michael@0: STATIC CONST KEYMAP MetaMap[16]= { michael@0: { CTL('H'), bk_kill_word }, michael@0: { DEL, bk_kill_word }, michael@0: { ' ', mk_set }, michael@0: { '.', last_argument }, michael@0: { '<', h_first }, michael@0: { '>', h_last }, michael@0: { 'b', bk_word }, michael@0: { 'd', fd_kill_word }, michael@0: { 'f', fd_word }, michael@0: { 'l', case_down_word }, michael@0: { 'm', toggle_meta_mode }, michael@0: { 'u', case_up_word }, michael@0: { 'y', yank }, michael@0: { 'w', copy_region }, michael@0: { 0, NULL } michael@0: }; michael@0: