/* * hist - interactive readline module * * Copyright (C) 1999-2007,2021 David I. Bell * * Calc is open software; you can redistribute it and/or modify it under * the terms of the version 2.1 of the GNU Lesser General Public License * as published by the Free Software Foundation. * * Calc is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General * Public License for more details. * * A copy of version 2.1 of the GNU Lesser General Public License is * distributed with calc under the filename COPYING-LGPL. You should have * received a copy with calc; if not, write to Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Under source code control: 1993/05/02 20:09:19 * File existed as early as: 1993 * * Share and enjoy! :-) http://www.isthe.com/chongo/tech/comp/calc/ */ /* * Adapted from code written by Stephen Rothwell. * * GNU readline support added by Martin Buck * * Interactive readline module. This is called to read lines of input, * while using emacs-like editing commands within a command stack. * The key bindings for the editing commands are (slightly) configurable. */ #include #include #if !defined(_WIN32) # include #endif #include "have_unistd.h" #if defined(HAVE_UNISTD_H) #include #endif #include "have_stdlib.h" #if defined(HAVE_STDLIB_H) #include #endif #include "calc.h" #include "lib_calc.h" #include "hist.h" #include "have_string.h" #include "have_strdup.h" #if !defined(HAVE_STRDUP) # define strdup(x) calc_strdup((CONST char *)(x)) #endif /* HAVE_STRDUP */ #ifdef HAVE_STRING_H # include #endif #include "have_unused.h" #if !defined(USE_READLINE) E_FUNC FILE *curstream(void); #define STDIN 0 #define SAVE_SIZE 256 /* size of save buffer */ #define MAX_KEYS 60 /* number of key bindings */ #define CONTROL(x) ((char)(((int)(x)) & 0x1f)) STATIC struct { char *prompt; char *buf; char *pos; char *end; char *mark; int bufsize; int linelen; int histcount; /* valid history entries */ int curhist; BOOL virgin_line; /* 1 => never typed chars, 0 => chars typed */ } HS; typedef void (*FUNCPTR)(); typedef struct { char *name; FUNCPTR func; } FUNC; /* declare binding functions */ S_FUNC void flush_input(void); S_FUNC void start_of_line(void); S_FUNC void end_of_line(void); S_FUNC void forward_char(void); S_FUNC void backward_char(void); S_FUNC void forward_word(void); S_FUNC void backward_word(void); S_FUNC void delete_char(void); S_FUNC void forward_kill_char(void); S_FUNC void backward_kill_char(void); S_FUNC void forward_kill_word(void); S_FUNC void kill_line(void); S_FUNC void new_line(void); S_FUNC void save_line(void); S_FUNC void forward_history(void); S_FUNC void backward_history(void); S_FUNC void insert_char(int key); S_FUNC void goto_line(void); S_FUNC void list_history(void); S_FUNC void refresh_line(void); S_FUNC void swap_chars(void); S_FUNC void set_mark(void); S_FUNC void yank(void); S_FUNC void save_region(void); S_FUNC void kill_region(void); S_FUNC void reverse_search(void); S_FUNC void quote_char(void); S_FUNC void uppercase_word(void); S_FUNC void lowercase_word(void); S_FUNC void ignore_char(void); S_FUNC void arrow_key(void); S_FUNC void quit_calc(void); STATIC FUNC funcs[] = { {"ignore-char", ignore_char}, {"flush-input", flush_input}, {"start-of-line", start_of_line}, {"end-of-line", end_of_line}, {"forward-char", forward_char}, {"backward-char", backward_char}, {"forward-word", forward_word}, {"backward-word", backward_word}, {"delete-char", delete_char}, {"forward-kill-char", forward_kill_char}, {"backward-kill-char", backward_kill_char}, {"forward-kill-word", forward_kill_word}, {"uppercase-word", uppercase_word}, {"lowercase-word", lowercase_word}, {"kill-line", kill_line}, {"goto-line", goto_line}, {"new-line", new_line}, {"save-line", save_line}, {"forward-history", forward_history}, {"backward-history", backward_history}, {"insert-char", insert_char}, {"list-history", list_history}, {"refresh-line", refresh_line}, {"swap-chars", swap_chars}, {"set-mark", set_mark}, {"yank", yank}, {"save-region", save_region}, {"kill-region", kill_region}, {"reverse-search", reverse_search}, {"quote-char", quote_char}, {"arrow-key", arrow_key}, {"quit", quit_calc}, {NULL, NULL} }; typedef struct key_ent KEY_ENT; typedef struct key_map KEY_MAP; struct key_ent { FUNCPTR func; KEY_MAP *next; }; struct key_map { char *name; KEY_ENT default_ent; KEY_ENT *map[256]; }; STATIC char base_map_name[] = "base-map"; STATIC char esc_map_name[] = "esc-map"; STATIC KEY_MAP maps[] = { {base_map_name, {NULL, NULL}, {NULL, NULL}}, {esc_map_name, {NULL, NULL}, {NULL, NULL}}, }; typedef struct history_entry { int len; /* length of data */ char *data; /* varying length data */ struct history_entry* prev; struct history_entry* next; } HIST; STATIC int inited; STATIC int canedit; STATIC int key_count; STATIC int save_len; STATIC KEY_MAP *cur_map; STATIC KEY_MAP *base_map; STATIC KEY_ENT key_table[MAX_KEYS]; STATIC HIST* hist_first = NULL; STATIC HIST* hist_last = NULL; STATIC char save_buffer[SAVE_SIZE]; /* declare other static functions */ S_FUNC FUNCPTR find_func(char *name); S_FUNC HIST *get_event(int n); S_FUNC HIST *find_event(char *pat, int len); S_FUNC void read_key(void); S_FUNC void erasechar(void); S_FUNC void newline(void); S_FUNC void backspace(void); S_FUNC void beep(void); S_FUNC void echo_char(int ch); S_FUNC void echo_string(char *str, int len); S_FUNC void savetext(char *str, int len); S_FUNC void memrcpy(char *dest, char *src, int len); S_FUNC int read_bindings(FILE *fp); S_FUNC int in_word(int ch); S_FUNC KEY_MAP *find_map(char *map); S_FUNC void unbind_key(KEY_MAP *map, int key); S_FUNC void raw_bind_key(KEY_MAP *map, int key, FUNCPTR func, KEY_MAP *next_map); S_FUNC KEY_MAP *do_map_line(char *line); S_FUNC void do_default_line(KEY_MAP *map, char *line); S_FUNC void do_bind_line(KEY_MAP *map, char *line); S_FUNC void back_over_char(int ch); S_FUNC void echo_rest_of_line(void); S_FUNC void goto_start_of_line(void); S_FUNC void goto_end_of_line(void); S_FUNC void remove_char(int ch); S_FUNC void decrement_end(int n); S_FUNC void insert_string(char *str, int len); /* * Read a line into the specified buffer. The line ends in a newline, * and is NULL terminated. Returns the number of characters read, or * zero on an end of file or error. The prompt is printed before reading * the line. */ size_t hist_getline(char *prompt, char *buf, size_t len) { /* * initialize if we have not already done so */ if (!inited) (void) hist_init(calcbindings); /* * establish the beginning of a line condition */ HS.prompt = prompt; HS.bufsize = len - 2; HS.buf = buf; HS.pos = buf; HS.end = buf; HS.mark = NULL; HS.linelen = -1; HS.virgin_line = TRUE; /* * prep the I/O */ fputs(prompt, stdout); fflush(stdout); /* * special case: non-interactive editing */ if (!canedit) { if (fgets(buf, len, stdin) == NULL) return 0; return strlen(buf); } /* * get the line */ while (HS.linelen < 0) { /* get the next char */ read_key(); /* chars typed, no longer virgin */ HS.virgin_line = FALSE; } /* * return the line */ return HS.linelen; } /* * Initialize the module by reading in the key bindings from the specified * filename, and then setting the terminal modes for noecho and cbreak mode. * If the supplied filename is NULL, then a default filename will be used. * We will search the CALCPATH for the file. * * Returns zero if successful, or a nonzero error code if unsuccessful. * If this routine fails, hist_getline, hist_saveline, and hist_term can * still be called but all fancy editing is disabled. */ int hist_init(char *filename) { /* * prevent multiple initializations */ if (inited) { if (conf->calc_debug & CALCDBG_TTY) printf("hist_init: inited already set\n"); return HIST_INITED; } /* * setup */ inited = 1; canedit = 0; if (conf->calc_debug & CALCDBG_TTY) printf("hist_init: Set inited, cleared canedit\n"); /* * open the bindings file */ if (filename == NULL) filename = HIST_BINDING_FILE; if (opensearchfile(filename, calcpath, NULL, FALSE) > 0) return HIST_NOFILE; /* * load the bindings */ if (read_bindings(curstream())) return HIST_NOFILE; /* * close the bindings */ closeinput(); /* * setup the calc TTY on STDIN */ if (!calc_tty(STDIN)) { return HIST_NOTTY; } canedit = 1; if (conf->calc_debug & CALCDBG_TTY) printf("hist_init: Set canedit\n"); return HIST_SUCCESS; } /* * Reset the terminal modes just before exiting. */ void hist_term(void) { if (!inited || !canedit) { if (conf->calc_debug & CALCDBG_TTY) { if (!inited) printf("hist_term: inited already cleared\n"); if (!canedit) printf("hist_term: canedit already cleared\n"); } inited = 0; if (conf->calc_debug & CALCDBG_TTY) printf("hist_term: Cleared inited\n"); return; } if (hist_first) { HIST* hp = hist_first; HIST* next; while(hp) { next = hp->next; free(hp->data); free(hp); hp = next; } } hist_first = NULL; hist_last = NULL; /* * restore orignal tty state */ (void) orig_tty(STDIN); } S_FUNC KEY_MAP * find_map(char *map) { unsigned int i; for (i = 0; i < sizeof(maps) / sizeof(maps[0]); i++) { if (strcmp(map, maps[i].name) == 0) return &maps[i]; } return NULL; } S_FUNC void unbind_key(KEY_MAP *map, int key) { map->map[key] = NULL; } S_FUNC void raw_bind_key(KEY_MAP *map, int key, FUNCPTR func, KEY_MAP *next_map) { if (map->map[key] == NULL) { if (key_count >= MAX_KEYS) return; map->map[key] = &key_table[key_count++]; } map->map[key]->func = func; map->map[key]->next = next_map; } S_FUNC KEY_MAP * do_map_line(char *line) { char *cp; char *map_name; cp = line; while (isspace((int)*cp)) cp++; if (*cp == '\0') return NULL; map_name = cp; while ((*cp != '\0') && !isspace((int)*cp)) cp++; *cp = '\0'; return find_map(map_name); } S_FUNC void do_bind_line(KEY_MAP *map, char *line) { char *cp; char key; char *func_name; char *next_name; KEY_MAP *next; FUNCPTR func; if (map == NULL) return; cp = line; key = *cp++; if (*cp == '\0') { unbind_key(map, key); return; } if (key == '^') { if (*cp == '?') { key = 0177; cp++; } else { key = CONTROL(*cp++); } } else if (key == '\\') { key = *cp++; } while (isspace((int)*cp)) cp++; if (*cp == '\0') { unbind_key(map, key); return; } func_name = cp; while ((*cp != '\0') && !isspace((int)*cp)) cp++; if (*cp) { *cp++ = '\0'; while (isspace((int)*cp)) cp++; } func = find_func(func_name); if (func == NULL) { fprintf(stderr, "Unknown function \"%s\"\n", func_name); return; } if (*cp == '\0') { next = map->default_ent.next; if (next == NULL) next = base_map; } else { next_name = cp; while ((*cp != '\0') && !isspace((int)*cp)) cp++; if (*cp) { *cp++ = '\0'; while (isspace((int)*cp)) cp++; } next = find_map(next_name); if (next == NULL) return; } raw_bind_key(map, key, func, next); } S_FUNC void do_default_line(KEY_MAP *map, char *line) { char *cp; char *func_name; char *next_name; KEY_MAP *next; FUNCPTR func; if (map == NULL) return; cp = line; while (isspace((int)*cp)) cp++; if (*cp == '\0') return; func_name = cp; while ((*cp != '\0') && !isspace((int)*cp)) cp++; if (*cp != '\0') { *cp++ = '\0'; while (isspace((int)*cp)) cp++; } func = find_func(func_name); if (func == NULL) return; if (*cp == '\0') { next = map; } else { next_name = cp; while ((*cp != '\0') && !isspace((int)*cp)) cp++; if (*cp != '\0') { *cp++ = '\0'; while (isspace((int)*cp)) cp++; } next = find_map(next_name); if (next == NULL) return; } map->default_ent.func = func; map->default_ent.next = next; } /* * Read bindings from specified open file. * * Returns nonzero on error. */ S_FUNC int read_bindings(FILE *fp) { char *cp; KEY_MAP *input_map; char line[BUFSIZ+1]; base_map = find_map(base_map_name); cur_map = base_map; input_map = base_map; if (fp == NULL) return 1; while (fgets(line, sizeof(line) - 1, fp)) { line[BUFSIZ] = '\0'; cp = line; while (isspace((int)*cp)) cp++; if ((*cp == '\0') || (*cp == '#') || (*cp == '\n')) continue; if (cp[strlen(cp) - 1] == '\n') cp[strlen(cp) - 1] = '\0'; if (memcmp(cp, "map", 3) == 0) input_map = do_map_line(&cp[3]); else if (memcmp(cp, "default", 7) == 0) do_default_line(input_map, &cp[7]); else do_bind_line(input_map, cp); } return 0; } S_FUNC void read_key(void) { KEY_ENT *ent; int key; fflush(stdout); key = fgetc(stdin); if (key == EOF) { HS.linelen = 0; HS.buf[0] = '\0'; return; } ent = cur_map->map[key]; if (ent == NULL) ent = &cur_map->default_ent; if (ent->next) cur_map = ent->next; if (ent->func) /* ignore Saber-C warning #65 - has 1 arg, expecting 0 */ /* ok to ignore in proc read_key */ (*ent->func)(key); else insert_char(key); } /* * Return the Nth history event, indexed from zero. * Earlier history events are lower in number. */ S_FUNC HIST * get_event(int n) { register HIST * hp = hist_first; int i = 0; do { if(!hp) break; if(i == n) return hp; ++i; hp = hp->next; } while(hp); return NULL; } /* * Search the history list for the specified pattern. * Returns the found history, or NULL. */ S_FUNC HIST * find_event(char *pat, int len) { register HIST * hp = hist_first; for(hp = hist_first; hp != NULL; hp = hp->next) { if ((hp->len == len) && (memcmp(hp->data, pat, len) == 0)) return hp; } return NULL; } /* * Insert a line into the end of the history table. * If the line already appears in the table, then it is moved to the end. * If the table is full, then the earliest commands are deleted as necessary. * Warning: the incoming line cannot point into the history table. */ void hist_saveline(char *line, int len) { HIST * hp; if ((len > 0) && (line[len - 1] == '\n')) len--; if (len <= 0) return; /* * See if the line is already present in the history table. * If so, and it is already at the end, then we are all done. * Otherwise delete it since we will reinsert it at the end. */ hp = find_event(line, len); if (hp) { if (hp == hist_last) return; if (hp->prev) hp->prev->next = hp->next; hp->next->prev = hp->prev; hist_last->next = hp; hp->next = NULL; hp->prev = hist_last; hist_last = hp; return; } /* * If there is not enough room left in the history buffer to add * the new command, then repeatedly delete the earliest command * as many times as necessary in order to make enough room. */ if (HS.histcount >= HIST_SIZE) { HIST *new_first = hist_first->next; free(hist_first->data); free(hist_first); new_first->prev = NULL; hist_first = new_first; HS.histcount--; } /* * Add the line to the end of the history table. */ hp = malloc(sizeof(HIST)); if (hp == NULL) { fprintf(stderr, "Out of memory adding line to the history table #0\n"); return; } hp->next = NULL; hp->prev = NULL; hp->len = len; hp->data = malloc(len); if (hp->data == NULL) { fprintf(stderr, "Out of memory adding line to the history table #1\n"); return; } memcpy(hp->data, line, len); HS.curhist = ++HS.histcount; if (!hist_first) hist_first = hp; if (!hist_last) hist_last = hp; else { hist_last->next = hp; hp->prev = hist_last; hist_last = hp; } } /* * Find the function for a specified name. */ S_FUNC FUNCPTR find_func(char *name) { FUNC *fp; for (fp = funcs; fp->name; fp++) { if (strcmp(fp->name, name) == 0) return fp->func; } return NULL; } S_FUNC void arrow_key(void) { switch (fgetc(stdin)) { case 'A': backward_history(); break; case 'B': forward_history(); break; case 'C': forward_char(); break; case 'D': backward_char(); break; } } S_FUNC void back_over_char(int ch) { backspace(); if (!isprint(ch)) backspace(); } S_FUNC void remove_char(int ch) { erasechar(); if (!isprint(ch)) erasechar(); } S_FUNC void echo_rest_of_line(void) { echo_string(HS.pos, HS.end - HS.pos); } S_FUNC void goto_start_of_line(void) { while (HS.pos > HS.buf) back_over_char((int)(*--HS.pos)); } S_FUNC void goto_end_of_line(void) { echo_rest_of_line(); HS.pos = HS.end; } S_FUNC void decrement_end(int n) { HS.end -= n; if (HS.mark && (HS.mark > HS.end)) HS.mark = NULL; } S_FUNC void ignore_char(void) { } S_FUNC void flush_input(void) { echo_rest_of_line(); while (HS.end > HS.buf) remove_char((int)(*--HS.end)); HS.pos = HS.buf; HS.mark = NULL; } S_FUNC void start_of_line(void) { goto_start_of_line(); } S_FUNC void end_of_line(void) { goto_end_of_line(); } S_FUNC void forward_char(void) { if (HS.pos < HS.end) echo_char(*HS.pos++); } S_FUNC void backward_char(void) { if (HS.pos > HS.buf) back_over_char((int)(*--HS.pos)); } S_FUNC void uppercase_word(void) { while ((HS.pos < HS.end) && !in_word((int)(*HS.pos))) echo_char(*HS.pos++); while ((HS.pos < HS.end) && in_word((int)(*HS.pos))) { if ((*HS.pos >= 'a') && (*HS.pos <= 'z')) *HS.pos += 'A' - 'a'; echo_char(*HS.pos++); } } S_FUNC void lowercase_word(void) { while ((HS.pos < HS.end) && !in_word((int)(*HS.pos))) echo_char(*HS.pos++); while ((HS.pos < HS.end) && in_word((int)(*HS.pos))) { if ((*HS.pos >= 'A') && (*HS.pos <= 'Z')) *HS.pos += 'a' - 'A'; echo_char(*HS.pos++); } } S_FUNC void forward_word(void) { while ((HS.pos < HS.end) && !in_word((int)(*HS.pos))) echo_char(*HS.pos++); while ((HS.pos < HS.end) && in_word((int)(*HS.pos))) echo_char(*HS.pos++); } S_FUNC void backward_word(void) { if ((HS.pos > HS.buf) && in_word((int)(*HS.pos))) back_over_char((int)(*--HS.pos)); while ((HS.pos > HS.buf) && !in_word((int)(*HS.pos))) back_over_char((int)(*--HS.pos)); while ((HS.pos > HS.buf) && in_word((int)(*HS.pos))) back_over_char((int)(*--HS.pos)); if ((HS.pos < HS.end) && !in_word((int)(*HS.pos))) echo_char(*HS.pos++); } S_FUNC void forward_kill_char(void) { int rest; char ch; rest = HS.end - HS.pos; if (rest-- <= 0) return; ch = *HS.pos; if (rest > 0) { memcpy(HS.pos, HS.pos + 1, rest); *(HS.end - 1) = ch; } echo_rest_of_line(); remove_char((int)ch); decrement_end(1); while (rest > 0) back_over_char((int)(HS.pos[--rest])); } S_FUNC void delete_char(void) { /* * quit delete_char (usually ^D) is at start of line and we are allowed * * We exit of start of line and config("ctrl_d", "empty") or * if config("ctrl_d", "virgin") and we have never typed on the line. */ if ((HS.end == HS.buf) && (conf->ctrl_d == CTRL_D_EMPTY_EOF || (conf->ctrl_d == CTRL_D_VIRGIN_EOF && HS.virgin_line == TRUE))) { quit_calc(); } /* * normal case: just forward_kill_char */ if (HS.end > HS.buf) forward_kill_char(); } S_FUNC void backward_kill_char(void) { if (HS.pos > HS.buf) { HS.pos--; back_over_char((int)(*HS.pos)); forward_kill_char(); } } S_FUNC void forward_kill_word(void) { char *cp; if (HS.pos >= HS.end) return; echo_rest_of_line(); for (cp = HS.end; cp > HS.pos;) remove_char((int)(*--cp)); cp = HS.pos; while ((cp < HS.end) && !in_word((int)(*cp))) cp++; while ((cp < HS.end) && in_word((int)(*cp))) cp++; savetext(HS.pos, cp - HS.pos); memcpy(HS.pos, cp, HS.end - cp); decrement_end(cp - HS.pos); echo_rest_of_line(); for (cp = HS.end; cp > HS.pos;) back_over_char((int)(*--cp)); } S_FUNC void kill_line(void) { if (HS.end <= HS.pos) return; savetext(HS.pos, HS.end - HS.pos); echo_rest_of_line(); while (HS.end > HS.pos) remove_char((int)(*--HS.end)); decrement_end(0); } /* * This is the function which completes a command line editing session. * The final line length is returned in the HS.linelen variable. * The line is NOT put into the edit history, so that the caller can * decide whether or not this should be done. */ S_FUNC void new_line(void) { int len; newline(); fflush(stdout); HS.mark = NULL; HS.end[0] = '\n'; HS.end[1] = '\0'; len = HS.end - HS.buf + 1; if (len <= 1) { HS.curhist = HS.histcount; HS.linelen = 1; return; } HS.curhist = HS.histcount; HS.pos = HS.buf; HS.end = HS.buf; HS.linelen = len; } S_FUNC void save_line(void) { int len; len = HS.end - HS.buf; if (len > 0) { hist_saveline(HS.buf, len); flush_input(); } HS.curhist = HS.histcount; } S_FUNC void goto_line(void) { int num; char *cp; HIST *hp; num = 0; cp = HS.buf; while ((*cp >= '0') && (*cp <= '9') && (cp < HS.pos)) num = num * 10 + (*cp++ - '0'); if ((num <= 0) || (num > HS.histcount) || (cp != HS.pos)) { beep(); return; } flush_input(); HS.curhist = HS.histcount - num; hp = get_event(HS.curhist); memcpy(HS.buf, hp->data, hp->len); HS.end = &HS.buf[hp->len]; goto_end_of_line(); } S_FUNC void forward_history(void) { HIST *hp; flush_input(); if (++HS.curhist >= HS.histcount) HS.curhist = 0; hp = get_event(HS.curhist); if (hp) { memcpy(HS.buf, hp->data, hp->len); HS.end = &HS.buf[hp->len]; } goto_end_of_line(); } S_FUNC void backward_history(void) { HIST *hp; flush_input(); if (--HS.curhist < 0) HS.curhist = HS.histcount - 1; hp = get_event(HS.curhist); if (hp) { memcpy(HS.buf, hp->data, hp->len); HS.end = &HS.buf[hp->len]; } goto_end_of_line(); } S_FUNC void insert_char(int key) { int len; int rest; len = HS.end - HS.buf; if (len >= HS.bufsize) { beep(); return; } rest = HS.end - HS.pos; if (rest > 0) memrcpy(HS.pos + 1, HS.pos, rest); HS.end++; *HS.pos++ = key; echo_char(key); echo_rest_of_line(); while (rest > 0) back_over_char((int)(HS.pos[--rest])); } S_FUNC void insert_string(char *str, int len) { int rest; int totallen; if (len <= 0) return; totallen = (HS.end - HS.buf) + len; if (totallen > HS.bufsize) { beep(); return; } rest = HS.end - HS.pos; if (rest > 0) memrcpy(HS.pos + len, HS.pos, rest); HS.end += len; memcpy(HS.pos, str, len); HS.pos += len; echo_string(str, len); echo_rest_of_line(); while (rest > 0) back_over_char((int)(HS.pos[--rest])); } S_FUNC void list_history(void) { HIST *hp; int hnum; for (hnum = 0; hnum < HS.histcount; hnum++) { hp = get_event(hnum); printf("\n%3d: ", HS.histcount - hnum); echo_string(hp->data, hp->len); } refresh_line(); } S_FUNC void refresh_line(void) { char *cp; newline(); fputs(HS.prompt, stdout); if (HS.end > HS.buf) { echo_string(HS.buf, HS.end - HS.buf); cp = HS.end; while (cp > HS.pos) back_over_char((int)(*--cp)); } } S_FUNC void swap_chars(void) { char ch1; char ch2; if ((HS.pos <= HS.buf) || (HS.pos >= HS.end)) return; ch1 = *HS.pos--; ch2 = *HS.pos; *HS.pos++ = ch1; *HS.pos = ch2; back_over_char((int)ch2); echo_char(ch1); echo_char(ch2); back_over_char((int)ch2); } S_FUNC void set_mark(void) { HS.mark = HS.pos; } S_FUNC void save_region(void) { int len; if (HS.mark == NULL) return; len = HS.mark - HS.pos; if (len > 0) savetext(HS.pos, len); if (len < 0) savetext(HS.mark, -len); } S_FUNC void kill_region(void) { char *cp; char *left; char *right; if ((HS.mark == NULL) || (HS.mark == HS.pos)) return; echo_rest_of_line(); if (HS.mark < HS.pos) { left = HS.mark; right = HS.pos; HS.pos = HS.mark; } else { left = HS.pos; right = HS.mark; HS.mark = HS.pos; } savetext(left, right - left); for (cp = HS.end; cp > left;) remove_char((int)(*--cp)); if (right < HS.end) memcpy(left, right, HS.end - right); decrement_end(right - left); echo_rest_of_line(); for (cp = HS.end; cp > HS.pos;) back_over_char((int)(*--cp)); } S_FUNC void yank(void) { insert_string(save_buffer, save_len); } S_FUNC void reverse_search(void) { int len; int count; int testhist; HIST *hp; char *save_pos; count = HS.histcount; len = HS.pos - HS.buf; if (len <= 0) count = 0; testhist = HS.curhist; do { if (--count < 0) { beep(); return; } if (--testhist < 0) testhist = HS.histcount - 1; hp = get_event(testhist); } while ((hp == NULL) || (hp->len < len) || memcmp(hp->data, HS.buf, len)); HS.curhist = testhist; save_pos = HS.pos; flush_input(); memcpy(HS.buf, hp->data, hp->len); HS.end = &HS.buf[hp->len]; goto_end_of_line(); while (HS.pos > save_pos) back_over_char((int)(*--HS.pos)); } S_FUNC void quote_char(void) { int ch; ch = fgetc(stdin); if (ch != EOF) insert_char(ch); } /* * Save data in the save buffer. */ S_FUNC void savetext(char *str, int len) { save_len = 0; if (len <= 0) return; if (len > SAVE_SIZE) len = SAVE_SIZE; memcpy(save_buffer, str, len); save_len = len; } /* * Test whether a character is part of a word. */ S_FUNC int in_word(int ch) { return (isalnum(ch) || (ch == '_')); } S_FUNC void erasechar(void) { fputs("\b \b", stdout); } S_FUNC void newline(void) { fputc('\n', stdout); } S_FUNC void backspace(void) { fputc('\b', stdout); } S_FUNC void beep(void) { fputc('\007', stdout); } S_FUNC void echo_char(int ch) { if (isprint(ch)) { putchar(ch); } else { putchar('^'); putchar((ch + '@') & 0x7f); } } S_FUNC void echo_string(char *str, int len) { while (len-- > 0) echo_char(*str++); } S_FUNC void memrcpy(char *dest, char *src, int len) { dest += len - 1; src += len - 1; while (len-- > 0) *dest-- = *src--; } #endif /* !USE_READLINE */ S_FUNC void quit_calc(void) { hist_term(); putchar('\n'); libcalc_call_me_last(); exit(0); } #if defined(USE_READLINE) #define HISTORY_LEN (1024) /* number of entries to save */ #include #include /* * The readline/history libs do most of the dirty work for us, so we can * replace hist_init() and hist_term() with dummies when using readline. * For hist_getline() we have to add a newline that readline removed but * calc expects. For hist_saveline(), we have to undo this. hist_getline() * also has to cope with the different memory management schemes of calc and * readline. */ size_t hist_getline(char *prompt, char *buf, size_t len) { char *line; buf[0] = '\0'; line = readline(prompt); if (!line) { switch (conf->ctrl_d) { case CTRL_D_NEVER_EOF: return 0; case CTRL_D_VIRGIN_EOF: case CTRL_D_EMPTY_EOF: default: quit_calc(); /*NOTREACHED*/ } } strncpy(buf, line, len - 1); buf[len - 2] = '\0'; len = strlen(buf); buf[len] = '\n'; buf[len + 1] = '\0'; free(line); return len + 1; } void hist_term(void) { } S_FUNC void my_stifle_history (void) { /* only save last number of entries */ stifle_history(HISTORY_LEN); if (calc_history) write_history(calc_history); } int hist_init(char UNUSED *filename) { /* used when parsing conditionals in ~/.inputrc */ rl_readline_name = "calc"; /* initialize interactive variables */ using_history(); /* name of history file */ if (calc_history == NULL) { calc_history = tilde_expand("~/.calc_history"); } /* read previous history */ read_history(calc_history); atexit(my_stifle_history); return HIST_SUCCESS; } void hist_saveline(char *line, int len) { STATIC char *prev = NULL; if (len <= 1) return; /* ignore if identical with previous line */ if (prev != NULL && strcmp(prev, line) == 0) return; free(prev); /* fail silently */ prev = strdup(line); line[len - 1] = '\0'; add_history(line); line[len - 1] = '\n'; } #endif /* USE_READLINE */ #if defined(HIST_TEST) /* * Main routine to test history. */ void main(int argc, char **argv) { char *filename; int len; char buf[BUFSIZ+1]; filename = NULL; if (argc > 1) filename = argv[1]; switch (hist_init(filename)) { case HIST_SUCCESS: break; case HIST_NOFILE: fprintf(stderr, "Binding file was not found\n"); break; case HIST_NOTTY: fprintf(stderr, "Cannot set terminal parameters\n"); break; case HIST_INITED: fprintf(stderr, "Hist is already inited\n"); break; default: fprintf(stderr, "Unknown error from hist_init\n"); break; } do { len = hist_getline("HIST> ", buf, sizeof(buf)); hist_saveline(buf, len); } while (len && (buf[0] != 'q')); hist_term(); exit(0); } #endif /* HIST_TEST */