#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ncurses.h> #include <assert.h> #include <ctype.h> #include "trie.h" // to do - flushinp() to cancel type-ahead when there is an ecce error reported // (and then wait for the user to see it. (Enter?) (Issue ^G too?) // current serious bug: entering an ecce command of all spaces (or // any equivalent ignorable noise characters) causes a lock-up. probably // looping looking for more data and getting an EOF it doesn't handle // entering a number to repeat the last command unfortunately repeats // the last immediate-mode command too, because things like cursor-right // are implemented as callbacks to ecce commands. Fix by recording // the last entered ecce command, and re-issuing it on a number. // need to take care re 'hidden' ecce commands contained in keystroke // macro expansions. char *expand[256] = { #include "expand.h" }; static int default_highlighting = TRUE; void inverted_screen(void) { default_highlighting = !default_highlighting; } void highlight(void) { if (!default_highlighting) standend(); else standout(); } void normal(void) { if (default_highlighting) standend(); else standout(); } typedef struct virtual_screen_line { char *sp; // pointer to next byte to be displayed on screen char *st; // partial text of multi-char expansion from // previous line. } virtual_screen_line; void getsize(char *fname, int *MAX_BUF_BYTES, int *MAX_DISPLAY_LINES) { FILE *tmp = fopen(fname, "rw"); *MAX_BUF_BYTES = -1; *MAX_DISPLAY_LINES = -1; if (tmp == NULL) return; fseek(tmp, 0L, SEEK_END); *MAX_BUF_BYTES = (int)ftell(tmp); fclose(tmp); *MAX_BUF_BYTES = (*MAX_BUF_BYTES + 1024*256) * 3; *MAX_DISPLAY_LINES = *MAX_BUF_BYTES / 8; /* approx, worst case eg for dict files */ } // virtual screen variables virtual_screen_line *l; // ECCE variables char *a; char *fbeg, *lbeg, *pp, *fp, *lend, *fend; extern char ecce_err[256]; extern void ecinner(char *command); void ecinnerf(char *format, int num) // HACK { char command[256]; sprintf(command, format, num); ecinner(command); } // display (sliding cursor window) variables int FIRST_DISPLAY_LINE = 0; int LAST_DISPLAY_LINE = 23; static int preferred_display_line = 0; int DISPLAY_LEFTMOST_COLUMN = 0; int DISPLAY_RIGHTMOST_COLUMN = 79; void allocatebuffer(int MAX_BUF_BYTES, int MAX_DISPLAY_LINES) { a = malloc(MAX_BUF_BYTES); l = malloc(sizeof(virtual_screen_line)*MAX_DISPLAY_LINES); } int lpend = 0; // temp hacks... int filesize = 0; void loadfile(char *fname, int MAX_BUF_BYTES) { FILE *tmp = fopen(fname, "r"); int c; a[0] = '\n'; lbeg = pp = fbeg = &a[1]; fp = fend = &a[MAX_BUF_BYTES-1]; *fend = '\n'; for (;;) { c = fgetc(tmp); if (c == EOF) break; *pp++ = c; filesize += 1; } fclose(tmp); while (pp != fbeg) { *--fp = *--pp; } lend = fp; while (*lend != '\n') lend += 1; } void generate_screen_line_start_pointers(char *pp, int *this_line, int *this_col) { // at the moment this is only used for finding the screen coords of the // cursor, but the main reason for it is for scrolling upwards. Eventually // it won't be invoked more than it needs to be. This is just a placeholder. int c, col; char *p; p = fbeg; lpend = 0; l[lpend].sp = p; l[lpend].st = ""; col = 0; for (;;) { if (p == pp) { p = fp; *this_col = col; *this_line = lpend; } if (p == fend) break; c = (*p++)&255; if (c == '\t') { int spaces = ((col+8)&(~7))-col; // at least 1 if (col+spaces > DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) { // wrap char *eight = " "; int wrapped_part = (col+spaces)-(DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN); // 1..8 int this_part = (DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN)-col; col += this_part; l[lpend+1].st = &eight[8-(wrapped_part-1)]; } else col += spaces; } else if (c != '\n') { // STILL DOES IT??? if (col+strlen(expand[c]) > DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) { // wrap int extra = col+strlen(expand[c]) - (DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN); col = DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN; l[lpend+1].st = expand[c]+strlen(expand[c])-extra; } else { col += 1; /* or add width of expansion of c */ l[lpend+1].st = ""; // may be able to fold this with the case above } } if (c == '\n') { // AHA! Oops - should be in the display routine, not the scanner!!! // NEED TO PAD WITH SPACES TO EOL IF DOING ICL7502-STYLE HIGHLIGHTING l[++lpend].sp = p; // safety coding: if (l[lpend].st == NULL) l[lpend].st = ""; l[lpend+1].st = ""; // if we run out of lines, do a realloc() ... col = 0; } else if (col == DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) { l[++lpend].sp = p; // safety coding: if (l[lpend].st == NULL) l[lpend].st = ""; l[lpend+1].st = ""; // if we run out of lines, do a realloc() ... col = 0; } } } void update_display(char *pp, int pref_display_line) { int c, x, y, line, this_virtual_screen_line; int first_virtual_screen_line, cursor_col = 0; if (pref_display_line > LAST_DISPLAY_LINE) pref_display_line = LAST_DISPLAY_LINE; if (pref_display_line < FIRST_DISPLAY_LINE) pref_display_line = FIRST_DISPLAY_LINE; generate_screen_line_start_pointers(pp, &this_virtual_screen_line, &cursor_col); // SIDE-EFFECT: sets lpend first_virtual_screen_line = this_virtual_screen_line-pref_display_line+FIRST_DISPLAY_LINE; if (first_virtual_screen_line < 0) first_virtual_screen_line = 0; if (first_virtual_screen_line > lpend) first_virtual_screen_line = lpend; if (first_virtual_screen_line+LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE > lpend) { first_virtual_screen_line = lpend - (LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE); pref_display_line = LAST_DISPLAY_LINE; } if (first_virtual_screen_line < 0) first_virtual_screen_line = 0; if (this_virtual_screen_line-first_virtual_screen_line < LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE) { // requested line too far down page, so move it up pref_display_line = first_virtual_screen_line+FIRST_DISPLAY_LINE; // tie to top } for (y = first_virtual_screen_line; y <= first_virtual_screen_line+LAST_DISPLAY_LINE-FIRST_DISPLAY_LINE; y++) { int eol = FALSE; char *p; int c, col = 0; move(y-first_virtual_screen_line+FIRST_DISPLAY_LINE, DISPLAY_LEFTMOST_COLUMN); // Output wrapped part of .st string first, if present ... p = l[y].st; if (p == NULL) break; // coding error while (*p != '\0') { addch(*p); p += 1; col += 1; } p = l[y].sp; if (p == NULL) break; // coding error for (;;) { if (p == pp) { p = fp; cursor_col = DISPLAY_LEFTMOST_COLUMN+col; pref_display_line = y-first_virtual_screen_line; highlight(); } if (p == fend) { normal(); clrtobot(); break; } c = (*p++)&255; if (c == '\t') { int spaces = ((col+8)&(~7))-col; // at least 1 if (col+spaces > DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) { // wrap int wrapped_part = (col+spaces)-(DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN); // 1..8 int this_part = (DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN)-col; col += this_part; // DOESN'T INVERSE! highlight(); clrtoeol(); normal(); while (this_part > 0) { addch(' '); this_part -= 1; } break; } else { col += spaces; // DOESN'T INVERSE! highlight(); clrtoeol(); normal(); while (spaces > 0) { addch(' '); spaces -= 1; } // move(y, DISPLAY_LEFTMOST_COLUMN+col); // is this now redundant? } } else if (c != '\n') { char *exp = expand[c]; while (*exp != '\0') { addch(*exp++); col += 1; if (col == DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN) break; } } if (c == '\n' || (col == DISPLAY_RIGHTMOST_COLUMN-DISPLAY_LEFTMOST_COLUMN)) { clrtoeol(); normal(); col = 0; // output wrapped string here? break; } } } move(pref_display_line, cursor_col); } int execute(int command) { static char command_line[128]; static int savedmac[128]; // sort out size limitations later static int *savedmacp = savedmac; static int nextc = 0; static int ecce_mode = FALSE; static int learning_mode = FALSE; static int update_required = (0==0); static int correction = 0; static int target_col = -1; // nasty modeful hack :-( if (command == ERR) { // *should* repaint the screen any time the keyboard is idle and // the display size has changed. Unfortunately it doesn't until // the next keypress. Not sure why. But better to leave this in // slightly broken than omit resizing altogether, which is severely // broken ... if ((!ecce_mode) && ((LAST_DISPLAY_LINE != LINES-1) || (DISPLAY_RIGHTMOST_COLUMN != COLS))) { LAST_DISPLAY_LINE = LINES-1; DISPLAY_RIGHTMOST_COLUMN = COLS; // update_display(pp, preferred_display_line); // refresh(); // return; } usleep(10000); // makes the difference between a load-average of 1 and tiny! // without this, we get ERR calls as fast as the system can generate them } else { if (learning_mode) { if (command != ('E'&31)) { *savedmacp++ = command; *savedmacp = ERR; // record keystrokes before all other processing } } if ((command == ('L'&31)) && (!ecce_mode)) { if (learning_mode) { if (savedmacp != savedmac) savedmacp -= 1; *savedmacp = ERR; // remove final ^L // get key sequence and assign learned commands to key sequence. // key sequence must be unique path through trie, not a prefix, otherwise can't assign. learning_mode = FALSE; inverted_screen(); if (savedmacp == savedmac) { // ^L^L - treat as a single ^L? clear(); update_required = (0==0); } } else { // Start learning mode! learning_mode = TRUE; inverted_screen(); savedmacp = savedmac; *savedmacp = ERR; } return; } else if (command == ('E'&31)) { // TEMPORARY PROOF OF CONCEPT: Execute stored macro learning_mode = FALSE; // quick hack to avoid recursion problems savedmacp = savedmac; while (*savedmacp != ERR) { execute(*savedmacp++); } return; } else if ((command == '\r') && (!ecce_mode)) { // don't special-case enter until in ecce mode } else { if ((!ecce_mode) && ((' ' <= command) && (command <= '~'))) { // although we want redefine characters to take precedence, // simply moving this test to the end does not work... ecce_mode = TRUE; nextc = 0; if (preferred_display_line == LAST_DISPLAY_LINE) preferred_display_line -= (correction = 1); LAST_DISPLAY_LINE -= 1; update_required = (0==0); } if (ecce_mode && (command == '\r')) { command_line[nextc] = '\0'; ecce_mode = FALSE; preferred_display_line += correction; correction = 0; LAST_DISPLAY_LINE += 1; nextc = 0; // execute the command here ecinner(command_line); command_line[nextc] = '\0'; target_col = -1; update_display(pp, preferred_display_line); if (*ecce_err != '\0') {int x=DISPLAY_LEFTMOST_COLUMN; char *s; move(LAST_DISPLAY_LINE, 0); s=ecce_err; highlight(); for (;;) { if (*s == '\0') break; if (*s == '\n') break; // move(LAST_DISPLAY_LINE+1,x++); addch(*s++); } while (x++ < DISPLAY_RIGHTMOST_COLUMN) addch(' '); normal(); *ecce_err = '\0'; refresh(); } return(TRUE); } else if (ecce_mode) { int x, len; command_line[nextc++] = command; command_line[nextc] = '\0'; if (nextc == 127) nextc = 126; // truncate if (update_required) { update_required = (0!=0); update_display(pp, preferred_display_line); } move(LAST_DISPLAY_LINE+1, DISPLAY_LEFTMOST_COLUMN); highlight(); len = strlen(command_line); // NEED TO OUTPUT PROMPT. need generic routine for // handling input line. Must be ready for "F!" so that // it prompts for the find/replace strings, for when we // bind commands like f// to a single key... for (x = 0; x < len; x++) { addch(command_line[x]); } normal(); for (x = DISPLAY_LEFTMOST_COLUMN+len; x < DISPLAY_RIGHTMOST_COLUMN; x++) { addch(' '); } move(LAST_DISPLAY_LINE+1, DISPLAY_LEFTMOST_COLUMN+strlen(command_line)); // Plus strlen(prompt) when added... refresh(); return(TRUE); } } } if (command == '\e' && getch() == ERR) { return(FALSE); // to add: HOME, END (start/end of file) } else if (command == KEY_DOWN || command == '\r') { int line, before, after; preferred_display_line += 1; if (preferred_display_line > LAST_DISPLAY_LINE) preferred_display_line = LAST_DISPLAY_LINE; generate_screen_line_start_pointers(pp, &line, &before); if (target_col < 0) target_col = before; { ecinner("m"); for (;;) { generate_screen_line_start_pointers(pp, &line, &after); if (after >= target_col) break; ecinner("r"); if (*ecce_err != '\0') break; } } update_required = (0==0); } else if (command == KEY_NPAGE || command == ' ' /* "more" clone? */) { preferred_display_line += LAST_DISPLAY_LINE; if (preferred_display_line > LAST_DISPLAY_LINE) preferred_display_line = LAST_DISPLAY_LINE; ecinnerf("m%0d", LAST_DISPLAY_LINE); update_required = (0==0); } else if (command == KEY_UP) { int line, before, after; preferred_display_line -= 1; if (preferred_display_line < FIRST_DISPLAY_LINE) preferred_display_line = FIRST_DISPLAY_LINE; generate_screen_line_start_pointers(pp, &line, &before); if (target_col < 0) target_col = before; { ecinner("m-r0"); for (;;) { generate_screen_line_start_pointers(pp, &line, &after); if (after <= target_col) break; ecinner("l"); } } update_required = (0==0); } else if (command == KEY_HOME) { ecinner("m-0"); update_required = (0==0); } else if (command == KEY_END) { ecinner("m0"); update_required = (0==0); } else if (command == 0x14A) { ecinner("e"); update_required = (0==0); } else if (command == 0x14B) { ecinner("i/ /l"); update_required = (0==0); } else if (command == 0x107) { ecinner("e-"); update_required = (0==0); } else if (command == KEY_PPAGE) { preferred_display_line -= LAST_DISPLAY_LINE; if (preferred_display_line < FIRST_DISPLAY_LINE) preferred_display_line = FIRST_DISPLAY_LINE; ecinnerf("m-%0d", LAST_DISPLAY_LINE); update_required = (0==0); } else if (command == KEY_LEFT) { if (pp == lbeg && pp != fbeg) { ecinner("m-r0"); // see below ... ecinner("(l,m-r0)"); preferred_display_line -= 1; if (preferred_display_line < FIRST_DISPLAY_LINE) preferred_display_line = FIRST_DISPLAY_LINE; } else ecinner("l"); target_col = -1; update_required = (0==0); } else if (command == KEY_RIGHT) { if (fp == lend) { ecinner("m"); // use just ecinner("(r,m)") if ecinner is allowed to update the display info preferred_display_line += 1; if (preferred_display_line > LAST_DISPLAY_LINE) preferred_display_line = LAST_DISPLAY_LINE; } else ecinner("r"); target_col = -1; update_required = (0==0); } else if (command == ' ') { // 'next' - re-execute last ecce command ecinner("1"); target_col = -1; update_required = (0==0); } else if (command == ('L'&31)) { // refresh clear(); update_required = (0==0); } else if (command == ERR) { // update only when there is no more type-ahead if (update_required) { update_display(pp, preferred_display_line); refresh(); } update_required = (0!=0); } else { // Debug info: unknown key? move(0, DISPLAY_RIGHTMOST_COLUMN-9); {char *hex = "0123456789ABCDEF"; highlight(); addch(hex[(command>>28)&15]); addch(hex[(command>>24)&15]); addch(hex[(command>>20)&15]); addch(hex[(command>>16)&15]); addch(hex[(command>>12)&15]); addch(hex[(command>>8)&15]); addch(hex[(command>>4)&15]); addch(hex[command&15]); normal(); } } return(TRUE); } int main(int argc, char **argv) { int MAX_BUF_BYTES, MAX_DISPLAY_LINES; int i, this_line; int command; char *p; if (argc == 111) { char *s; fprintf(stderr, "syntax: %s filename\n", (s=strrchr(argv[0], '/')) == NULL ? argv[0] : ++s); exit(1); } getsize(argc == 1 ? "tabtest.txt" : argv[1], &MAX_BUF_BYTES, &MAX_DISPLAY_LINES); if (MAX_BUF_BYTES < 0) { char *s; fprintf(stderr, "%s: cannot edit %s\n", (s=strrchr(argv[0], '/')) == NULL ? argv[0] : ++s, argc == 1 ? "tabtest.txt" : argv[1]); exit(1); } initscr(); cbreak(); noecho(); nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); nodelay(stdscr, TRUE); LAST_DISPLAY_LINE = LINES-1; DISPLAY_RIGHTMOST_COLUMN = COLS; allocatebuffer(MAX_BUF_BYTES, MAX_DISPLAY_LINES); loadfile(argc == 1 ? "tabtest.txt" : argv[1], MAX_BUF_BYTES); init_globals(); ecinner("%e"); for (;;) { command = getch(); if (!execute(command)) break; } free_buffers(); move(0, 0); clear(); refresh(); endwin(); exit(0); }