/* * Copyright (c) 1992, 1993 Simmule Turner and Rich Salz * All rights reserved. * * This software is not subject to any license of the American Telephone * and Telegraph Company or of the Regents of the University of California. * * Permission is granted to anyone to use this software for any purpose on * any computer system, and to alter it and redistribute it freely, subject * to the following restrictions: * 1. The authors are not responsible for the consequences of use of this * software, no matter how awful, even if they arise from flaws in it. * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Since few users ever read sources, * credits must appear in the documentation. * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. Since few users * ever read sources, credits must appear in the documentation. * 4. This notice may not be removed or altered. */ #include #include #include #include #include "editline.h" /* ** Manifest constants. */ #define SCREEN_COLS 80 #define SCREEN_ROWS 24 #define EL_STDIN 0 #define EL_STDOUT 1 #define NO_ARG (-1) #define DEL 127 #define SEPS "\"#$&'()*:;<=>?[\\]^`{|}~\n\t " /* ** The type of case-changing to perform. */ typedef enum { TOupper, TOlower, TOcapitalize } el_case_t; /* ** Key to command mapping. */ typedef struct { int Key; el_status_t (*Function)(void); } el_keymap_t; /* ** Command history structure. */ typedef struct { int Size; int Pos; char **Lines; } el_hist_t; /* User definable callbacks. */ rl_getc_func_t *rl_getc_function = rl_getc; rl_hook_func_t *rl_event_hook; rl_vintfunc_t *rl_prep_term_function = rl_prep_terminal; rl_voidfunc_t *rl_deprep_term_function = rl_deprep_terminal; /* ** Globals. */ int rl_eof; int rl_erase; int rl_intr; int rl_kill; int rl_quit; #ifdef CONFIG_SIGSTOP int rl_susp; #endif int el_hist_size = 15; static el_hist_t H = { .Size = 0, .Pos = 0, .Lines = NULL, }; static char NILSTR[] = ""; static const char *el_input = NILSTR; static char *Yanked; static char *Screen; static char NEWLINE[]= CRLF; static char CLEAR[]= "\ec"; static const char *el_term = "dumb"; static int Repeat; static int old_point; static int el_push_back; static int el_pushed; static int el_intr_pending; static int el_infd = EL_STDIN; static int el_outfd = EL_STDOUT; static el_keymap_t Map[]; static el_keymap_t MetaMap[]; static size_t Length = 0; static size_t ScreenCount; static size_t ScreenSize; static char *backspace = "\b"; static char *old_search = NULL; static int tty_cols = SCREEN_COLS; static int tty_rows = SCREEN_ROWS; static int Searching = 0; static const char *(*search_move)(void); static const char *old_prompt = NULL; static rl_vcpfunc_t *line_handler = NULL; static char *line_up = "\x1b[A"; static char *line_down = "\x1b[B"; int prompt_len = 0; int el_no_echo = 0; /* e.g., under Emacs */ int el_no_hist = 0; int rl_point; int rl_mark; int rl_end; int rl_meta_chars = 0; /* Display 8-bit chars as the actual char(0) or as `M-x'(1)? */ int rl_inhibit_complete = 0; char *rl_line_buffer = NULL; const char *rl_prompt = NULL; const char *rl_readline_name = NULL; /* Set by calling program, for conditional parsing of ~/.inputrc - Not supported yet! */ FILE *rl_instream = NULL; /* The stdio stream from which input is read. Defaults to stdin if NULL */ FILE *rl_outstream = NULL; /* The stdio stream to which output is flushed. Defaults to stdout if NULL */ /* Declarations. */ static char *editinput(int complete); #ifdef CONFIG_USE_TERMCAP extern char *tgetstr(const char *, char **); extern int tgetent(char *, const char *); extern int tgetnum(const char *); #endif /* ** Misc. local helper functions. */ static int is_alpha_num(unsigned char c) { if (isalnum(c)) return 1; if (ISMETA(c)) return 1; if (ISCTL(c)) return 1; return 0; } /* ** TTY input/output functions. */ static void tty_flush(void) { ssize_t res; if (!ScreenCount) return; if (!el_no_echo) { res = write(el_outfd, Screen, ScreenCount); if (res > 0) ScreenCount = 0; } } static void tty_put(const char c) { if (el_no_echo) return; Screen[ScreenCount] = c; if (++ScreenCount >= ScreenSize) { char *ptr; ScreenSize += SCREEN_INC; ptr = realloc(Screen, sizeof(char) * ScreenSize); if (ptr) Screen = ptr; } } static void tty_puts(const char *p) { while (*p) tty_put(*p++); } static void tty_show(unsigned char c) { if (c == DEL) { tty_put('^'); tty_put('?'); } else if (ISCTL(c)) { tty_put('^'); tty_put(UNCTL(c)); } else if (rl_meta_chars && ISMETA(c)) { tty_put('M'); tty_put('-'); tty_put(UNMETA(c)); } else { tty_put(c); } } static void tty_string(char *p) { int i = rl_point + prompt_len + 1; while (*p) { tty_show(*p++); if ((i++) % tty_cols == 0) { tty_put(' '); tty_put('\b'); } } } static void tty_push(int c) { el_pushed = 1; el_push_back = c; } int rl_getc(void) { int r; char c; do { r = read(el_infd, &c, 1); } while (r == -1 && errno == EINTR); return r == 1 ? c : EOF; } static int tty_get(void) { tty_flush(); if (el_pushed) { el_pushed = 0; return el_push_back; } if (*el_input) return *el_input++; return rl_getc_function(); } #define tty_back() tty_puts(backspace) static void tty_backn(int n) { while (--n >= 0) tty_back(); } static void tty_forwardn(int n) { char buf[12]; snprintf(buf, sizeof(buf), "\x1b[%dC", n); tty_puts(buf); } static void tty_info(void) { rl_reset_terminal(NULL); } /* ** Glue routines to rl_ttyset() */ void rl_prep_terminal(int meta_flag) { rl_meta_chars = !meta_flag; rl_ttyset(0); } void rl_deprep_terminal(void) { rl_ttyset(1); } /* ** Print an array of words in columns. */ void el_print_columns(int ac, char **av) { char *p; int i; int j; int k; int len; int skip; int longest; int cols; int colwidth; /* Find longest name, determine column count from that. */ for (longest = 0, i = 0; i < ac; i++) { if ((j = strlen((char *)av[i])) > longest) longest = j; } colwidth = longest + 3; if (colwidth > tty_cols) colwidth = tty_cols; cols = tty_cols / colwidth; tty_puts(NEWLINE); for (skip = ac / cols + 1, i = 0; i < skip; i++) { for (j = i; j < ac; j += skip) { for (p = av[j], len = strlen((char *)p), k = len; --k >= 0; p++) tty_put(*p); if (j + skip < ac) { while (++len < colwidth) tty_put(' '); } } tty_puts(NEWLINE); } } static void reposition(int key) { int len_with_prompt = prompt_len + rl_end; int n = len_with_prompt / tty_cols; /* determine the number of lines */ int i = 0; tty_put('\r'); if (n > 0) { int line; /* determine num of current line */ if (key == CTL('A') || key == CTL('E') || key == rl_kill) line = (prompt_len + old_point) / tty_cols; else line = len_with_prompt / tty_cols; /* move to end of line(s) */ if (key == CTL('E')) { int k; for (k = line; k < n; k++) tty_puts(line_down); /* determine reminder of last line and redraw only it */ i = rl_point - (len_with_prompt % tty_cols); } else { int k; /* CTRL-A, CTRL-U, insert (end, middle), remove (end, middle) */ for (k = line; k > 0; k--) tty_puts(line_up); /* redraw characters until changed data */ tty_puts(rl_prompt); } } else if (n == 0) { tty_puts(rl_prompt); } for (; i < rl_point; i++) { tty_show(rl_line_buffer[i]); /* move to the next line */ if ((i + prompt_len + 1) % tty_cols == 0) tty_put('\n'); } } static void left(el_status_t Change) { if (rl_point) { if ((rl_point + prompt_len) % tty_cols == 0) { tty_puts(line_up); tty_forwardn(tty_cols); } else { tty_back(); } if (ISMETA(rl_line_buffer[rl_point - 1])) { if (rl_meta_chars) { tty_back(); tty_back(); } } else if (ISCTL(rl_line_buffer[rl_point - 1])) { tty_back(); } } if (Change == CSmove) rl_point--; } static void right(el_status_t Change) { if ((rl_point + prompt_len + 1) % tty_cols == 0) tty_put('\n'); else tty_show(rl_line_buffer[rl_point]); if (Change == CSmove) rl_point++; } el_status_t el_ring_bell(void) { tty_put('\07'); tty_flush(); return CSstay; } static el_status_t do_macro(int c) { char name[4]; name[0] = '_'; name[1] = c; name[2] = '_'; name[3] = '\0'; if ((el_input = (char *)getenv((char *)name)) == NULL) { el_input = NILSTR; return el_ring_bell(); } return CSstay; } /* Skip forward to start of next word. If @move is set we also move the cursor. */ static el_status_t do_forward(el_status_t move) { int i; char *p; i = 0; do { p = &rl_line_buffer[rl_point]; /* Skip leading whitespace, like FSF Readline */ for ( ; rl_point < rl_end && (p[0] == ' ' || !is_alpha_num(p[0])); rl_point++, p++) { if (move == CSmove) right(CSstay); } /* Skip to end of word, if inside a word. */ for (; rl_point < rl_end && is_alpha_num(p[0]); rl_point++, p++) { if (move == CSmove) right(CSstay); } /* Skip to next word, or skip leading white space if outside a word. */ for ( ; rl_point < rl_end && (p[0] == ' ' || !is_alpha_num(p[0])); rl_point++, p++) { if (move == CSmove) right(CSstay); } if (rl_point == rl_end) break; } while (++i < Repeat); return CSstay; } static el_status_t do_case(el_case_t type) { int i; int end; int count; char *p; do_forward(CSstay); if (old_point != rl_point) { if ((count = rl_point - old_point) < 0) count = -count; rl_point = old_point; if ((end = rl_point + count) > rl_end) end = rl_end; for (i = rl_point, p = &rl_line_buffer[i]; rl_point < end; p++) { if ((type == TOupper) || (type == TOcapitalize && rl_point == i)) { if (islower(*p)) *p = toupper(*p); } else if (isupper(*p)) { *p = tolower(*p); } right(CSmove); } } return CSstay; } static el_status_t case_down_word(void) { return do_case(TOlower); } static el_status_t case_up_word(void) { return do_case(TOupper); } static el_status_t case_cap_word(void) { return do_case(TOcapitalize); } static void ceol(void) { int extras = 0; int i; char *p; while (rl_point < 0) { tty_put(' '); rl_point++; } for (i = rl_point, p = &rl_line_buffer[i]; i <= rl_end; i++, p++) { if ((i + prompt_len + 1) % tty_cols == 0){ tty_put(' '); tty_put('\n'); } else tty_put(' '); if (ISMETA(*p)) { if (rl_meta_chars) { tty_put(' '); tty_put(' '); extras += 2; } } else if (ISCTL(*p)) { tty_put(' '); extras++; } } for (i += extras; i > rl_point; i--) { if ((i + prompt_len) % tty_cols == 0) { tty_puts(line_up); tty_forwardn(tty_cols); } else { tty_back(); } } } static void clear_line(void) { int n = (rl_point + prompt_len) / tty_cols; rl_point = -(int)strlen(rl_prompt); if (n > 0) { for(int k = 0; k < n; k++) tty_puts(line_up); tty_put('\r'); } else { tty_put('\r'); } ceol(); rl_point = 0; rl_end = 0; rl_line_buffer[0] = '\0'; } static el_status_t insert_string(const char *p) { size_t len; int i; char *line; char *q; len = strlen(p); if (rl_end + len >= Length) { line = malloc(sizeof(char) * (Length + len + MEM_INC)); if (!line) return CSstay; if (Length) { memcpy(line, rl_line_buffer, Length); free(rl_line_buffer); } rl_line_buffer = line; Length += len + MEM_INC; } for (q = &rl_line_buffer[rl_point], i = rl_end - rl_point; --i >= 0; ) q[len + i] = q[i]; memcpy(&rl_line_buffer[rl_point], p, len); rl_end += len; rl_line_buffer[rl_end] = '\0'; tty_string(&rl_line_buffer[rl_point]); rl_point += len; return rl_point == rl_end ? CSstay : CSmove; } int rl_insert_text(const char *text) { int mark = rl_point; insert_string(text); ceol(); return rl_point - mark; } static el_status_t redisplay(int cls) { if (cls) tty_puts(CLEAR); else tty_puts("\r\e[K"); tty_puts(rl_prompt); rl_point = 0; tty_string(rl_line_buffer); rl_point = rl_end; return CSmove; } static el_status_t refresh(void) { return redisplay(1); } int rl_refresh_line(int ignore1 __attribute__((unused)), int ignore2 __attribute__((unused))) { redisplay(0); return 0; } static el_status_t toggle_meta_mode(void) { rl_meta_chars = ! rl_meta_chars; return redisplay(0); } const char *el_next_hist(void) { return H.Pos >= H.Size - 1 ? NULL : H.Lines[++H.Pos]; } const char *el_prev_hist(void) { return H.Pos == 0 ? NULL : H.Lines[--H.Pos]; } static el_status_t do_insert_hist(const char *p) { if (p == NULL) return el_ring_bell(); clear_line(); rl_point = 0; reposition(EOF); rl_end = 0; return insert_string(p); } static el_status_t do_hist(const char *(*move)(void)) { const char *p; int i = 0; do { if ((p = move()) == NULL) return el_ring_bell(); } while (++i < Repeat); return do_insert_hist(p); } static el_status_t h_next(void) { if (el_no_hist) return CSstay; return do_hist(el_next_hist); } static el_status_t h_prev(void) { if (el_no_hist) return CSstay; return do_hist(el_prev_hist); } static el_status_t h_first(void) { return do_insert_hist(H.Lines[H.Pos = 0]); } static el_status_t h_last(void) { return do_insert_hist(H.Lines[H.Pos = H.Size - 1]); } /* ** Return zero if pat appears as a substring in text. */ static int substrcmp(const char *text, const char *pat, size_t len) { char c; if ((c = *pat) == '\0') return *text == '\0'; for ( ; *text; text++) { if (*text == c && strncmp(text, pat, len) == 0) return 0; } return 1; } static const char *search_hist(const char *search, const char *(*move)(void)) { int len; int pos; int (*match)(const char *s1, const char *s2, size_t n); const char *pat; /* Save or get remembered search pattern. */ if (search && *search) { if (old_search) free(old_search); old_search = strdup(search); } else { if (old_search == NULL || *old_search == '\0') return NULL; search = old_search; } /* Set up pattern-finder. */ if (*search == '^') { match = strncmp; pat = search + 1; } else { match = substrcmp; pat = search; } len = strlen(pat); pos = H.Pos; /* Save H.Pos */ while (move()) { if (match(H.Lines[H.Pos], pat, len) == 0) return H.Lines[H.Pos]; } H.Pos = pos; /* Restore H.Pos */ return NULL; } static el_status_t h_search_end(const char *p) { rl_prompt = old_prompt; Searching = 0; if (el_intr_pending > 0) { el_intr_pending = 0; clear_line(); return redisplay(0); } p = search_hist(p, search_move); if (p == NULL) { el_ring_bell(); clear_line(); return redisplay(0); } return do_insert_hist(p); } static el_status_t h_search(void) { if (Searching) return el_ring_bell(); Searching = 1; clear_line(); old_prompt = rl_prompt; rl_prompt = "Search: "; tty_puts(rl_prompt); search_move = Repeat == NO_ARG ? el_prev_hist : el_next_hist; if (line_handler) { editinput(0); return CSstay; } return h_search_end(editinput(1)); } static el_status_t fd_char(void) { int i = 0; do { if (rl_point >= rl_end) break; right(CSmove); } while (++i < Repeat); return CSstay; } static void save_yank(int begin, int i) { if (Yanked) { free(Yanked); Yanked = NULL; } if (i < 1) return; Yanked = malloc(sizeof(char) * (i + 1)); if (Yanked) { memcpy(Yanked, &rl_line_buffer[begin], i); Yanked[i] = '\0'; } } static el_status_t delete_string(int count) { int i; char *p; if (count <= 0 || rl_end == rl_point) return el_ring_bell(); if (count == 1 && rl_point == rl_end - 1) { /* Optimize common case of delete at end of line. */ rl_end--; p = &rl_line_buffer[rl_point]; i = 1; tty_put(' '); if (ISCTL(*p)) { i = 2; tty_put(' '); } else if (rl_meta_chars && ISMETA(*p)) { i = 3; tty_put(' '); tty_put(' '); } tty_backn(i); *p = '\0'; return CSmove; } if (rl_point + count > rl_end && (count = rl_end - rl_point) <= 0) return CSstay; if (count > 1) save_yank(rl_point, count); for (p = &rl_line_buffer[rl_point], i = rl_end - (rl_point + count) + 1; --i >= 0; p++) p[0] = p[count]; ceol(); rl_end -= count; tty_string(&rl_line_buffer[rl_point]); return CSmove; } static el_status_t bk_char(void) { int i = 0; do { if (rl_point == 0) break; left(CSmove); } while (++i < Repeat); return CSstay; } static el_status_t bk_del_char(void) { int i = 0; do { if (rl_point == 0) break; left(CSmove); } while (++i < Repeat); return delete_string(i); } static el_status_t kill_line(void) { int i; if (Repeat != NO_ARG) { if (Repeat < rl_point) { i = rl_point; rl_point = Repeat; reposition(EOF); delete_string(i - rl_point); } else if (Repeat > rl_point) { right(CSmove); delete_string(Repeat - rl_point - 1); } return CSmove; } save_yank(rl_point, rl_end - rl_point); rl_line_buffer[rl_point] = '\0'; ceol(); rl_end = rl_point; return CSstay; } static el_status_t insert_char(int c) { el_status_t s; char buff[2]; char *p; char *q; int i; if (Repeat == NO_ARG || Repeat < 2) { buff[0] = c; buff[1] = '\0'; return insert_string(buff); } p = malloc(sizeof(char) * (Repeat + 1)); if (!p) return CSstay; for (i = Repeat, q = p; --i >= 0; ) *q++ = c; *q = '\0'; Repeat = 0; s = insert_string(p); free(p); return s; } static el_status_t beg_line(void) { if (rl_point) { rl_point = 0; return CSmove; } return CSstay; } static el_status_t end_line(void) { if (rl_point != rl_end) { rl_point = rl_end; return CSmove; } return CSstay; } static el_status_t del_char(void) { return delete_string(Repeat == NO_ARG ? CSeof : Repeat); } el_status_t el_del_char(void) { return del_char(); } static el_status_t fd_word(void) { return do_forward(CSmove); } static el_status_t bk_word(void) { int i; char *p; i = 0; do { for (p = &rl_line_buffer[rl_point]; p > rl_line_buffer && !is_alpha_num(p[-1]); p--) left(CSmove); for (; p > rl_line_buffer && !isblank(p[-1]) && is_alpha_num(p[-1]); p--) left(CSmove); if (rl_point == 0) break; } while (++i < Repeat); return CSstay; } static el_status_t meta(void) { int c; el_keymap_t *kp; if ((c = tty_get()) == EOF) return CSeof; #ifdef CONFIG_ANSI_ARROWS /* Also include VT-100 arrows. */ if (c == '[' || c == 'O') { switch (tty_get()) { case EOF: return CSeof; case '1': { char seq[4] = { 0 }; for (c = 0; c < 3; c++) seq[c] = tty_get(); if (!strncmp(seq, ";5C", 3)) return fd_word(); /* Ctrl+Right */ if (!strncmp(seq, ";5D", 3)) return bk_word(); /* Ctrl+Left */ break; } case '2': tty_get(); return CSstay; /* Insert */ case '3': tty_get(); return del_char(); /* Delete */ case '5': tty_get(); return CSstay; /* PgUp */ case '6': tty_get(); return CSstay; /* PgDn */ case 'A': return h_prev(); /* Up */ case 'B': return h_next(); /* Down */ case 'C': return fd_char(); /* Left */ case 'D': return bk_char(); /* Right */ case 'F': return end_line(); /* End */ case 'H': return beg_line(); /* Home */ default: /* Fall through */ break; } return el_ring_bell(); } #endif /* CONFIG_ANSI_ARROWS */ if (isdigit(c)) { for (Repeat = c - '0'; (c = tty_get()) != EOF && isdigit(c); ) Repeat = Repeat * 10 + c - '0'; tty_push(c); return CSstay; } if (isupper(c)) return do_macro(c); for (kp = MetaMap; kp->Function; kp++) { if (kp->Key == c) return kp->Function(); } return el_ring_bell(); } static el_status_t emacs(int c) { el_status_t s; el_keymap_t *kp; /* Save point before interpreting input character 'c'. */ old_point = rl_point; if (rl_meta_chars && ISMETA(c)) { tty_push(UNMETA(c)); return meta(); } for (kp = Map; kp->Function; kp++) { if (kp->Key == c) break; } if (kp->Function) { s = kp->Function(); if (s == CSdispatch) /* If Function is inhibited. */ s = insert_char(c); } else { s = insert_char(c); } if (!el_pushed) { /* No pushback means no repeat count; hacky, but true. */ Repeat = NO_ARG; } return s; } static el_status_t tty_special(int c) { #ifdef CONFIG_SIGINT if (c == rl_intr) { el_intr_pending = SIGINT; return CSsignal; } #endif if (c == rl_quit) { el_intr_pending = SIGQUIT; return CSeof; } #ifdef CONFIG_SIGSTOP if (c == rl_susp) { el_intr_pending = SIGTSTP; return CSsignal; } #endif if (rl_meta_chars && ISMETA(c)) return CSdispatch; if (c == rl_erase || c == DEL) return bk_del_char(); if (c == rl_kill) { if (rl_point != 0) { old_point = rl_point; rl_point = 0; reposition(c); } Repeat = NO_ARG; return kill_line(); } #ifdef CONFIG_EOF if (c == rl_eof && rl_point == 0 && rl_end == 0) return CSeof; #endif return CSdispatch; } static char *editinput(int complete) { int c; do { c = tty_get(); if (c == EOF) break; switch (tty_special(c)) { case CSdone: return rl_line_buffer; case CSeof: return NULL; case CSsignal: return (char *)""; case CSmove: reposition(c); break; case CSdispatch: switch (emacs(c)) { case CSdone: return rl_line_buffer; case CSeof: return NULL; case CSsignal: return (char *)""; case CSmove: reposition(c); break; case CSdispatch: case CSstay: break; } break; case CSstay: break; } } while (complete); return NULL; } static void hist_alloc(void) { if (!H.Lines) H.Lines = calloc(el_hist_size, sizeof(char *)); } static void hist_add(const char *p) { int i; char *s; #ifdef CONFIG_UNIQUE_HISTORY if (H.Size && strcmp(p, H.Lines[H.Size - 1]) == 0) return; #endif s = strdup(p); if (s == NULL) return; if (H.Size < el_hist_size) { H.Lines[H.Size++] = s; } else { free(H.Lines[0]); for (i = 0; i < el_hist_size - 1; i++) H.Lines[i] = H.Lines[i + 1]; H.Lines[i] = s; } H.Pos = H.Size - 1; } static char *read_redirected(void) { int size = MEM_INC; char *p; char *line; char *end; p = line = malloc(sizeof(char) * size); if (!p) return NULL; end = p + size; while (1) { if (p == end) { int oldpos = end - line; size += MEM_INC; p = realloc(line, sizeof(char) * size); if (!p) { free(line); return NULL; } line = p; end = p + size; p += oldpos; /* Continue where we left off... */ } if (read(el_infd, p, 1) <= 0) { /* Ignore "incomplete" lines at EOF, just like we do for a tty. */ free(line); return NULL; } if (*p == '\n') break; p++; } *p = '\0'; return line; } /* For compatibility with FSF readline. */ void rl_reset_terminal(const char *terminal_name) { #ifdef CONFIG_USE_TERMCAP char buf[1024]; char *bp; #endif #ifdef TIOCGWINSZ struct winsize W; #endif if (terminal_name) { el_term = terminal_name; } else if ((el_term = getenv("TERM")) == NULL) { el_term = "dumb"; } /* Initialize to faulty values to trigger fallback if nothing else works. */ tty_cols = tty_rows = -1; #ifdef CONFIG_USE_TERMCAP bp = buf; if (-1 != tgetent(buf, el_term)) { if ((backspace = tgetstr("le", &bp)) != NULL) backspace = strdup(backspace); tty_cols = tgetnum("co"); tty_rows = tgetnum("li"); } /* Make sure to check width & rows and fallback to TIOCGWINSZ if available. */ #endif if (tty_cols <= 0 || tty_rows <= 0) { #ifdef TIOCGWINSZ if (ioctl(el_outfd, TIOCGWINSZ, &W) >= 0 && W.ws_col > 0 && W.ws_row > 0) { tty_cols = (int)W.ws_col; tty_rows = (int)W.ws_row; return; } #endif tty_cols = SCREEN_COLS; tty_rows = SCREEN_ROWS; } } void rl_initialize(void) { if (!rl_prompt) rl_prompt = "? "; hist_alloc(); /* Setup I/O descriptors */ if (!rl_instream) el_infd = EL_STDIN; else el_infd = fileno(rl_instream); if (el_infd < 0) el_infd = EL_STDIN; if (!rl_outstream) el_outfd = EL_STDOUT; else el_outfd = fileno(rl_outstream); if (el_outfd < 0) el_outfd = EL_STDOUT; } void rl_uninitialize(void) { int i; /* Uninitialize the history */ if (H.Lines) { for (i = 0; i < el_hist_size; i++) { if (H.Lines[i]) free(H.Lines[i]); H.Lines[i] = NULL; } free(H.Lines); H.Lines = NULL; } H.Size = 0; H.Pos = 0; if (old_search) free(old_search); old_search = NULL; /* Uninitialize the line buffer */ if (rl_line_buffer) free(rl_line_buffer); rl_line_buffer = NULL; Length = 0; } static const char *rl_saved_prompt = NULL; void rl_save_prompt(void) { rl_saved_prompt = rl_prompt; } void rl_restore_prompt(void) { if (rl_saved_prompt) rl_prompt = rl_saved_prompt; } void rl_set_prompt(const char *prompt) { rl_prompt = prompt; } void rl_clear_message(void) { /* Nothing to do atm. */ } void rl_forced_update_display() { redisplay(0); tty_flush(); } static int el_prep(const char *prompt) { rl_initialize(); if (!rl_line_buffer) { Length = MEM_INC; rl_line_buffer = malloc(sizeof(char) * Length); if (!rl_line_buffer) return -1; } tty_info(); rl_prep_term_function(!rl_meta_chars); hist_add(NILSTR); ScreenSize = SCREEN_INC; Screen = malloc(sizeof(char) * ScreenSize); if (!Screen) return -1; rl_prompt = prompt ? prompt : NILSTR; prompt_len = strlen(rl_prompt); if (el_no_echo) { int old = el_no_echo; el_no_echo = 0; tty_puts(rl_prompt); tty_flush(); el_no_echo = old; } else { tty_puts(rl_prompt); } Repeat = NO_ARG; old_point = rl_point = rl_mark = rl_end = 0; rl_line_buffer[0] = '\0'; el_intr_pending = -1; return 0; } static char *el_deprep(char *line) { if (line) { line = strdup(line); tty_puts(NEWLINE); tty_flush(); } rl_deprep_term_function(); if (Screen) { free(Screen); Screen = NULL; } free(H.Lines[--H.Size]); H.Lines[H.Size] = NULL; /* Add to history, unless no-echo or no-history mode ... */ if (!el_no_echo && !el_no_hist) { if (line != NULL && *line != '\0') hist_add(line); } if (el_intr_pending > 0) { int signo = el_intr_pending; el_intr_pending = 0; kill(getpid(), signo); } return line; } void rl_callback_handler_install(const char *prompt, rl_vcpfunc_t *lhandler) { if (!lhandler) return; line_handler = lhandler; /* * Any error from el_prep() is handled by the lhandler callbck as * soon as the user calls rl_callback_read_char(). */ el_prep(prompt); tty_flush(); } /* * Reads one character at a time, when a complete line has been received * the lhandler from rl_callback_handler_install() is called with the * line as argument. * * If the callback returns the terminal is prepped for reading a new * line. * * If any error occurs, either in the _install() phase, or while reading * one character, this function restores the terminal and calls lhandler * with a NULL argument. */ void rl_callback_read_char(void) { char *line; if (!line_handler) { errno = EINVAL; return; } /* * Check if rl_callback_handler_install() failed * This is the only point where we can tell user */ if (!Screen || !rl_line_buffer) { errno = ENOMEM; line_handler(el_deprep(NULL)); return; } line = editinput(0); if (line) { char *l; if (Searching) { h_search_end(line); tty_flush(); return; } l = el_deprep(line); line_handler(l); if (el_prep(rl_prompt)) line_handler(NULL); } tty_flush(); } void rl_callback_handler_remove(void) { if (!line_handler) return; el_deprep(NULL); line_handler = NULL; } char *readline(const char *prompt) { /* Unless called by the user already. */ rl_initialize(); if (!isatty(el_infd)) { tty_flush(); return read_redirected(); } if (el_prep(prompt)) return NULL; return el_deprep(editinput(1)); } /* * Even though readline() itself adds history automatically, the user * can also add lines. This is for compatibility with GNU Readline. */ void add_history(const char *p) { if (p == NULL || *p == '\0') return; hist_add(p); } int read_history(const char *filename) { FILE *fp; char buf[SCREEN_INC]; hist_alloc(); fp = fopen(filename, "r"); if (!fp) return EOF; H.Size = 0; while (H.Size < el_hist_size) { if (!fgets(buf, SCREEN_INC, fp)) break; buf[strlen(buf) - 1] = 0; /* Remove '\n' */ add_history(buf); } return fclose(fp); } int write_history(const char *filename) { FILE *fp; int i = 0; hist_alloc(); fp = fopen(filename, "w"); if (!fp) return EOF; while (i < H.Size) fprintf(fp, "%s\n", H.Lines[i++]); return fclose(fp); } /* ** Move back to the beginning of the current word and return an ** allocated copy of it. */ char *el_find_word(void) { char *p, *q; char *word; size_t len; p = &rl_line_buffer[rl_point]; while (p > rl_line_buffer) { p--; if (p > rl_line_buffer && p[-1] == '\\') { p--; } else { if (strchr(SEPS, (char) *p) != NULL) { p++; break; } } } len = rl_point - (p - rl_line_buffer) + 1; word = malloc(sizeof(char) * len); if (!word) return NULL; q = word; while (p < &rl_line_buffer[rl_point]) { if (*p == '\\') { if (++p == &rl_line_buffer[rl_point]) break; } *q++ = *p++; } *q = '\0'; return word; } static el_status_t c_possible(void) { char **av; char *word; int ac; word = el_find_word(); ac = rl_list_possib(word, &av); if (word) free(word); if (ac) { el_print_columns(ac, av); while (--ac >= 0) free(av[ac]); free(av); return CSmove; } return el_ring_bell(); } static el_status_t c_complete(void) { char *p, *q; char *word, *string; size_t len; int unique; el_status_t s = CSdone; if (rl_inhibit_complete) return CSdispatch; word = el_find_word(); p = rl_complete(word, &unique); if (word) free(word); if (p) { len = strlen(p); word = p; string = q = malloc(sizeof(char) * (2 * len + 1)); if (!string) { free(word); return CSstay; } while (*p) { if ((*p < ' ' || strchr(SEPS, *p) != NULL) && (!unique || p[1] != 0)) { *q++ = '\\'; } *q++ = *p++; } *q = '\0'; free(word); if (len > 0) { s = insert_string(string); #ifdef CONFIG_TERMINAL_BELL if (!unique) el_ring_bell(); #endif } free(string); if (len > 0) return s; } return c_possible(); } static el_status_t accept_line(void) { rl_line_buffer[rl_end] = '\0'; return CSdone; } #ifdef SYSTEM_IS_WIN32 static el_status_t end_of_input(void) { rl_line_buffer[rl_end] = '\0'; return CSeof; } #endif static el_status_t transpose(void) { char c; if (rl_point) { if (rl_point == rl_end) left(CSmove); c = rl_line_buffer[rl_point - 1]; left(CSstay); rl_line_buffer[rl_point - 1] = rl_line_buffer[rl_point]; tty_show(rl_line_buffer[rl_point - 1]); rl_line_buffer[rl_point++] = c; tty_show(c); } return CSstay; } static el_status_t quote(void) { int c; return (c = tty_get()) == EOF ? CSeof : insert_char((int)c); } static el_status_t mk_set(void) { rl_mark = rl_point; return CSstay; } static el_status_t exchange(void) { int c; if ((c = tty_get()) != CTL('X')) return c == EOF ? CSeof : el_ring_bell(); if ((c = rl_mark) <= rl_end) { rl_mark = rl_point; rl_point = c; return CSmove; } return CSstay; } static el_status_t yank(void) { if (Yanked && *Yanked) return insert_string(Yanked); return CSstay; } static el_status_t copy_region(void) { if (rl_mark > rl_end) return el_ring_bell(); if (rl_point > rl_mark) save_yank(rl_mark, rl_point - rl_mark); else save_yank(rl_point, rl_mark - rl_point); return CSstay; } static el_status_t move_to_char(void) { int i, c; char *p; if ((c = tty_get()) == EOF) return CSeof; for (i = rl_point + 1, p = &rl_line_buffer[i]; i < rl_end; i++, p++) { if (*p == c) { rl_point = i; return CSmove; } } return CSstay; } static el_status_t fd_kill_word(void) { int i; do_forward(CSstay); if (old_point != rl_point) { i = rl_point - old_point - 1; rl_point = old_point; return delete_string(i); } return CSstay; } static el_status_t bk_kill_word(void) { bk_word(); if (old_point != rl_point) return delete_string(old_point - rl_point); return CSstay; } static int argify(char *line, char ***avp) { char *c; char **p; char **arg; int ac; int i; i = MEM_INC; *avp = p = malloc(sizeof(char *) * i); if (!p) return 0; for (c = line; isspace(*c); c++) continue; if (*c == '\n' || *c == '\0') return 0; for (ac = 0, p[ac++] = c; *c && *c != '\n'; ) { if (!isspace(*c)) { c++; continue; } *c++ = '\0'; if (*c && *c != '\n') { if (ac + 1 == i) { arg = malloc(sizeof(char *) * (i + MEM_INC)); if (!arg) { p[ac] = NULL; return ac; } memcpy(arg, p, i * sizeof(char *)); i += MEM_INC; free(p); *avp = p = arg; } p[ac++] = c; } } *c = '\0'; p[ac] = NULL; return ac; } static el_status_t last_argument(void) { char **av = NULL; char *p; el_status_t s; int ac; if (H.Size == 1 || (p = (char *)H.Lines[H.Size - 2]) == NULL) return el_ring_bell(); p = strdup(p); if (!p) return CSstay; ac = argify(p, &av); if (Repeat != NO_ARG) s = Repeat < ac ? insert_string(av[Repeat]) : el_ring_bell(); else s = ac ? insert_string(av[ac - 1]) : CSstay; if (av) free(av); free(p); return s; } static el_keymap_t Map[64] = { { CTL('@'), mk_set }, { CTL('A'), beg_line }, { CTL('B'), bk_char }, { CTL('D'), del_char }, { CTL('E'), end_line }, { CTL('F'), fd_char }, { CTL('G'), el_ring_bell }, { CTL('H'), bk_del_char }, { CTL('I'), c_complete }, { CTL('J'), accept_line }, { CTL('K'), kill_line }, { CTL('L'), refresh }, { CTL('M'), accept_line }, { CTL('N'), h_next }, { CTL('O'), el_ring_bell }, { CTL('P'), h_prev }, { CTL('Q'), el_ring_bell }, { CTL('R'), h_search }, { CTL('S'), el_ring_bell }, { CTL('T'), transpose }, { CTL('U'), el_ring_bell }, { CTL('V'), quote }, { CTL('W'), bk_kill_word }, { CTL('X'), exchange }, { CTL('Y'), yank }, #ifdef SYSTEM_IS_WIN32 { CTL('Z'), end_of_input }, #else { CTL('Z'), el_ring_bell }, #endif { CTL('['), meta }, { CTL(']'), move_to_char }, { CTL('^'), el_ring_bell }, { CTL('_'), el_ring_bell }, { 0, NULL } }; static el_keymap_t MetaMap[64]= { { CTL('H'), bk_kill_word }, { DEL, bk_kill_word }, { ' ', mk_set }, { '.', last_argument }, { '<', h_first }, { '>', h_last }, { '?', c_possible }, { 'b', bk_word }, { 'c', case_cap_word }, { 'd', fd_kill_word }, { 'f', fd_word }, { 'l', case_down_word }, { 'm', toggle_meta_mode }, { 'u', case_up_word }, { 'y', yank }, { 'w', copy_region }, { 0, NULL } }; static size_t find_key_in_map(int key, el_keymap_t map[], size_t mapsz) { size_t i; for (i = 0; i < mapsz && map[i].Function; i++) { if (map[i].Key == key) return i; } if (i < mapsz) return i; return mapsz; } static el_status_t el_bind_key_in_map(int key, el_keymap_func_t function, el_keymap_t map[], size_t mapsz) { size_t creat, pos = find_key_in_map(key, map, mapsz); /* Must check that pos is not the next to last array position, * otherwise we will write out-of-bounds to terminate the list. */ if (pos + 1 >= mapsz) { errno = ENOMEM; return CSeof; } /* Add at end, create new? */ creat = map[pos].Function == NULL; /* A new key so have to add it to end */ map[pos].Key = key; map[pos].Function = function; /* Terminate list */ if (creat) { map[pos + 1].Key = 0; map[pos + 1].Function = NULL; } return CSdone; } el_status_t el_bind_key(int key, el_keymap_func_t function) { return el_bind_key_in_map(key, function, Map, NELEMS(Map)); } el_status_t el_bind_key_in_metamap(int key, el_keymap_func_t function) { return el_bind_key_in_map(key, function, MetaMap, NELEMS(MetaMap)); } rl_getc_func_t *rl_set_getc_func(rl_getc_func_t *func) { rl_getc_func_t *old = rl_getc_function; rl_getc_function = func; return old; } /** * Local Variables: * c-file-style: "k&r" * c-basic-offset: 4 * End: */