#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);
}