/* -*- Mode: C; -*- * ------------------------------------------------------------------------------------------ * Title: Ersatz Readline * Created: 2016-04-20 * Author: Gilbert Baumann * ------------------------------------------------------------------------------------------ * (c) copyright 2016 by Gilbert Baumann */ #include #include #include #include #define MAX_HIST 100 #define HIST_FILE ".rdlinehist" int rdline_max = 255; char *rdline_hist [MAX_HIST]; int rdline_curhist = -1; void rdline_save (void); void rdline_save (void) { char *home, *fn = 0; int i; FILE *f = 0; home = getenv ("HOME"); if (home == 0) goto bad; fn = malloc (strlen (home) + strlen (HIST_FILE) + 2); if (fn == 0) goto bad; strcpy (fn, home); strcat (fn, "/"); strcat (fn, HIST_FILE); f = fopen (fn, "w"); if (f == NULL) goto bad; for (i = 0; i < rdline_curhist; i++) fprintf (f, "%s\n", rdline_hist [i]); bad: if (fn) free (fn); if (f) fclose (f); } unsigned char *rdline (unsigned int (*conin)(void), void (*conout)(int)) { char screen [256]; char line [256]; int ip = 0; /* insertion pointer */ int fp = 0; /* fill pointer */ int cx = 0; /* current column on screen */ int sp = -1; /* search pointer for prev/next */ int sp_save; int hp; /* history pointer */ int max = rdline_max; /* max no. of chars */ int state = 0; /* ESC state */ int activated = 0; /* line activated? */ int c, i; int dir; int n = sizeof (screen); c = 0; /* for GCC */ if (rdline_curhist == -1) { char *home, *fn = 0, *s; FILE *f = 0; rdline_curhist = 0; if (0) atexit (rdline_save); home = getenv ("HOME"); if (home == 0) goto bad; fn = malloc (strlen (home) + strlen (HIST_FILE) + 2); if (fn == 0) goto bad; strcpy (fn, home); strcat (fn, "/"); strcat (fn, HIST_FILE); f = fopen (fn, "r"); if (f == NULL) goto bad; while ((rdline_curhist + 1) < MAX_HIST) { if (fgets (line, sizeof (line), f) == NULL) break; s = strchr (line, '\n'); if (s == NULL) continue; *s = 0; if (!(rdline_hist[rdline_curhist] = strdup (line))) goto bad; rdline_curhist++; } bad: if (fn) free (fn); if (f) fclose (f); } hp = rdline_curhist; memset (screen, ' ', sizeof (screen)); memset (line, ' ', sizeof (line)); if (max >= sizeof (line)) max = sizeof (line) - 1; for (;;) { /* refresh */ for (i = 0; i < n; i++) if (screen [i] != line [i]) { while (cx > i) { conout ('\b'); cx--; } while (cx < i) { conout ((unsigned char)(line [cx])); cx++; } putchar (line [i]); screen [i] = line[i]; cx++; } /* insertion pointer */ while (cx > ip) { conout ('\b'); cx--; } while (cx < ip) { conout ((unsigned char)(line [cx])); cx++; } if (activated) break; sp_save = sp; sp = -1; c = conin (); switch (state) { case 0: /* no escape seen */ switch (c) { case '[' & 0x1F: sp = sp_save; state = 1; break; case '\n': case '\r': activated = 1; ip = fp; break; case 'g' & 0x1F: case 'c' & 0x1F: activated = 2; ip = fp; break; case 'a' & 0x1F: ip = 0; break; backward_char: case 'b' & 0x1F: if (ip > 0) ip--; break; case 'e' & 0x1F: ip = fp; break; forward_char: case 'f' & 0x1F: if (ip < fp) ip++; break; case 'h' & 0x1F: case 0x7F: if (ip == 0) break; ip--; /* FALL THROUGH */ case 'd' & 0x1F: if (ip == fp) break; fp--; memmove (line + ip, line + ip + 1, fp - ip); line [fp] = ' '; break; case 'u' & 0x1F: memset (line, ' ', fp); ip = fp = 0; break; case 'k' & 0x1F: memset (line + ip, ' ', fp - ip); fp = ip; break; case 't' & 0x1F: if (ip == 0) break; if (ip == fp) break; /* ### for now */ c = line [ip-1]; line[ip-1] = line[ip]; line[ip++] = c; break; next_hist: case 'n' & 0x1F: sp = sp_save; if (hp == rdline_curhist) goto bark; dir = +1; goto install_hist; break; prev_hist: case 'p' & 0x1F: sp = sp_save; if (hp == 0) goto bark; if (hp == rdline_curhist) { line [fp] = '\0'; if (rdline_hist [hp]) free (rdline_hist [hp]); rdline_hist [hp] = strdup (line); line [fp] = ' '; } dir = -1; install_hist: /* borrowed from slime: seek command which started what we typed */ { int k; if (sp == -1) sp = fp; k = hp + dir; while ((0 <= k) && (k < rdline_curhist)) { if ((strlen (rdline_hist [k]) >= sp) && (0 == strncmp (rdline_hist [k], line, sp))) { hp = k; goto found; } k = k + dir; } /* fall through */ if (dir == 1) { hp = rdline_curhist; goto found; } goto bark; found: k = strlen (rdline_hist [hp]); memcpy (line, rdline_hist [hp], k); memset (line + k, ' ', sizeof (line) - k); ip = fp = k; } break; default: if (fp >= max) break; if (((c >= ' ') && (c <= '~')) || (c >= 160)) { memmove (line + ip + 1, line + ip, fp - ip); line[ip++] = c; fp++; } break; } break; case 1: /* one escape seen */ state = 0; /* unless otherwise */ switch (c) { case 'o': case 'O': case '[': sp = sp_save; state = 2; break; case 'b': while (ip > 0) { ip--; if ((ip > 1) && !isalnum (line[ip-1]) && isalnum(line[ip])) break; } break; case 'f': while (ip < fp) { ip++; if (ip > 1 && isalnum (line[ip-1]) && !isalnum(line[ip])) break; } break; default: // printf ("\r\nESC %.2x?\r\n", c); cx = 0; /* xxx */ state = 0; break; } break; case 2: /* ESC [ seen */ /* A up, B down, C right, D left */ switch (c) { case 'd': case 'D': state = 0; goto backward_char; case 'c': case 'C': state = 0; goto forward_char; case 'a': case 'A': state = 0; goto prev_hist; case 'b': case 'B': state = 0; goto next_hist; default: // printf ("\r\nESC '[' %.2x?\r\n", c); cx = 0; /* xxx */ state = 0; break; } break; bark: putchar (7); break; } } if (activated == 2) { conout ('^'); conout (c + 0x40); conout ('\r'); conout ('\n'); return 0; } else { conout ('\r'); /* Hmm */ line [fp] = '\0'; if (rdline_hist [rdline_curhist]) free (rdline_hist [rdline_curhist]); rdline_hist [rdline_curhist++] = strdup (line); if (rdline_curhist == MAX_HIST) { free (rdline_hist [0]); memmove (rdline_hist, rdline_hist + 1, sizeof (rdline_hist[0]) * (MAX_HIST - 1)); rdline_curhist--; rdline_hist[rdline_curhist] = 0; } return (unsigned char*)strdup (line); } }