/************************************************************************* * TinyFugue - programmable mud client * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys * * TinyFugue (aka "tf") is protected under the terms of the GNU * General Public License. See the file "COPYING" for details. ************************************************************************/ static const char RCSid[] = "$Id: keyboard.c,v 35004.88 2007/01/13 23:12:39 kkeys Exp $"; /************************************************** * Fugue keyboard handling. * Handles all keyboard input and keybindings. **************************************************/ #include "tfconfig.h" #include "port.h" #include "tf.h" #include "util.h" #include "pattern.h" /* for tfio.h */ #include "search.h" #include "tfio.h" #include "macro.h" /* Macro, find_macro(), do_macro()... */ #include "keyboard.h" #include "output.h" /* iput(), idel(), redraw()... */ #include "history.h" /* history_sub() */ #include "expand.h" /* macro_run() */ #include "cmdlist.h" #include "variable.h" /* unsetvar() */ static int literal_next = FALSE; static TrieNode *keynode = NULL; /* current node matched by input */ static int kbnum_internal = 0; int pending_line = FALSE; int pending_input = FALSE; struct timeval keyboard_time; static int dokey_newline(void); static int replace_input(String *line); static void handle_input_string(const char *input, unsigned int len); STATIC_BUFFER(scratch); /* buffer for manipulating text */ STATIC_BUFFER(current_input); /* unprocessed keystrokes */ static TrieNode *keytrie = NULL; /* root of keybinding trie */ AUTO_BUFFER(keybuf); /* input buffer */ int keyboard_pos = 0; /* current position in buffer */ /* * Some dokey operations are implemented internally with names like * DOKEY_FOO; others are implemented as macros in stdlib.tf with names * like /dokey_foo. handle_dokey_command() looks first for an internal * function in efunc_table[], then for a macro, so all operations can be done * with "/dokey foo". Conversely, internally-implemented operations should * have macros in stdlib.tf of the form "/def dokey_foo = /dokey foo", * so all operations can be performed with "/dokey_foo". */ enum { #define gencode(id) DOKEY_##id #include "keylist.h" #undef gencode }; static const char *efunc_table[] = { #define gencode(id) #id #include "keylist.h" #undef gencode }; #define kbnumval (kbnum ? atoi(kbnum->data) : 1) void init_keyboard(void) { gettime(&keyboard_time); } /* Find the macro associated with <key> sequence. */ Macro *find_key(const char *key) { return (Macro *)trie_find(keytrie, (unsigned char*)key); } int bind_key(Macro *spec, const char *key) { int status = intrie(&keytrie, spec, (const unsigned char*)key); if (status < 0) { eprintf("'%S' is %s an existing keybinding.", ascii_to_print(key), (status == TRIE_SUPER) ? "prefixed by" : "a prefix of"); return 0; } return 1; } void unbind_key(const char *key) { untrie(&keytrie, (const unsigned char*)key); keynode = NULL; /* in case it pointed to a node that no longer exists */ } /* returns 0 at EOF, 1 otherwise */ int handle_keyboard_input(int read_flag) { char buf[64]; const char *s; int i, count = 0; static int key_start = 0; static int input_start = 0; static int place = 0; static int eof = 0; /* Solaris select() incorrectly reports the terminal as readable if * the user typed LNEXT or FLUSH, when in fact there is nothing to read. * So we wait for read() to return 0 several times in a row * (hopefully, more times than anyone would realistically type FLUSH in * a row) before deciding it's EOF. */ if (eof < 100 && read_flag) { /* read a block of text */ if ((count = read(STDIN_FILENO, buf, sizeof(buf))) < 0) { /* error or interrupt */ if (errno == EINTR) return 1; die("handle_keyboard_input: read", errno); } else if (count > 0) { /* something was read */ eof = 0; gettime(&keyboard_time); } else { /* nothing was read, and nothing is buffered */ /* Don't close stdin; we might be wrong (solaris bug), and we * don't want the fd to be reused anyway. */ eof++; } } if (count == 0 && place == 0) goto end; for (i = 0; i < count; i++) { if (istrip) buf[i] &= 0x7F; if (buf[i] & 0x80) { if (!literal_next && (meta_esc == META_ON || (!is_print(buf[i]) && meta_esc))) { Stringadd(current_input, '\033'); buf[i] &= 0x7F; } if (!is_print(buf[i])) buf[i] &= 0x7F; } Stringadd(current_input, mapchar(buf[i])); } s = current_input->data; if (!s) /* no good chars; current_input not yet allocated */ goto end; while (place < current_input->len) { if (!keynode) keynode = keytrie; if ((pending_input = pending_line)) break; if (literal_next) { place++; key_start++; literal_next = FALSE; continue; } while (place < current_input->len && keynode && keynode->children) keynode = keynode->u.child[(unsigned char)s[place++]]; if (!keynode) { /* No keybinding match; check for builtins. */ if (s[key_start] == '\n' || s[key_start] == '\r') { handle_input_string(s + input_start, key_start - input_start); place = input_start = ++key_start; dokey_newline(); /* handle_input_line(); */ } else if (s[key_start] == '\b' || s[key_start] == '\177') { handle_input_string(s + input_start, key_start - input_start); place = input_start = ++key_start; do_kbdel(keyboard_pos - kbnumval); reset_kbnum(); } else if (kbnum && is_digit(s[key_start]) && key_start == input_start) { int n; SStringcpy(scratch, kbnum); Stringadd(scratch, s[key_start]); place = input_start = ++key_start; n = kbnumval < 0 ? -kbnumval : kbnumval; if (max_kbnum > 0 && n > max_kbnum) Sprintf(scratch, "%c%d", kbnumval<0 ? '-' : '+', max_kbnum); setstrvar(&special_var[VAR_kbnum], CS(scratch), FALSE); } else { /* No builtin; try a suffix. */ place = ++key_start; } keynode = NULL; } else if (!keynode->children) { /* Total match; process everything up to here and call the macro. */ int kbnumlocal = 0; Macro *macro = (Macro *)keynode->u.datum; handle_input_string(s + input_start, key_start - input_start); key_start = input_start = place; keynode = NULL; /* before do_macro(), for reentrance */ if (kbnum) { kbnum_internal = kbnumlocal = atoi(kbnum->data); reset_kbnum(); } do_macro(macro, NULL, 0, USED_KEY, kbnumlocal); kbnum_internal = 0; } /* else, partial match; just hold on to it for now. */ } /* Process everything up to a possible match. */ handle_input_string(s + input_start, key_start - input_start); /* Shift the window if there's no pending partial match. */ if (key_start >= current_input->len) { Stringtrunc(current_input, 0); place = key_start = 0; } input_start = key_start; if (pending_line && !read_depth) handle_input_line(); end: return eof < 100; } /* Update the input window and keyboard buffer. */ static void handle_input_string(const char *input, unsigned int len) { int putlen = len, extra = 0; if (len == 0) return; if (kbnum) { if (kbnumval > 1) { extra = kbnumval - 1; putlen = len + extra; } reset_kbnum(); } /* if this is a fresh line, input history is already synced; * if user deleted line, input history is already synced; * if called from replace_input, we don't want input history synced. */ if (keybuf->len) sync_input_hist(); if (keyboard_pos == keybuf->len) { /* add to end */ if (extra) { Stringnadd(keybuf, *input, extra); keyboard_pos += extra; } Stringncat(keybuf, input, len); } else if (insert) { /* insert in middle */ Stringcpy(scratch, keybuf->data + keyboard_pos); Stringtrunc(keybuf, keyboard_pos); if (extra) { Stringnadd(keybuf, *input, extra); keyboard_pos += extra; } Stringncat(keybuf, input, len); SStringcat(keybuf, CS(scratch)); } else if (keyboard_pos + len + extra < keybuf->len) { /* overwrite */ while (extra) { keybuf->data[keyboard_pos++] = *input; extra--; } memcpy(keybuf->data + keyboard_pos, input, len); } else { /* write past end */ Stringtrunc(keybuf, keyboard_pos); if (extra) { Stringnadd(keybuf, *input, extra); keyboard_pos += extra; } Stringncat(keybuf, input, len); } keyboard_pos += len; iput(putlen); } struct Value *handle_input_command(String *args, int offset) { handle_input_string(args->data + offset, args->len - offset); return shareval(val_one); } /* * Builtin key functions. */ struct Value *handle_dokey_command(String *args, int offset) { const char **ptr; STATIC_BUFFER(buffer); Macro *macro; int n; /* XXX We use kbnum_internal here, but a macro would use the local %kbnum. * It is possible (though unadvisable) for a macro to change the local * %kbnum before this point, making this code behave differently than * a /dokey_foo macro would. Fetching the actual local %kbnum here would * make the behavior the same, but is a step in the wrong direction; * a better solution would be to make the local %kbnum non-modifiable * by macro code (but variable.c doesn't yet support const). */ n = kbnum_internal ? kbnum_internal : 1; ptr = (const char **)bsearch((void*)(args->data + offset), (void*)efunc_table, sizeof(efunc_table)/sizeof(char*), sizeof(char*), cstrstructcmp); if (!ptr) { SStringocat(Stringcpy(buffer, "dokey_"), CS(args), offset); if ((macro = find_macro(buffer->data))) return newint(do_macro(macro, NULL, 0, USED_NAME, 0)); else eprintf("No editing function %s", args->data + offset); return shareval(val_zero); } switch (ptr - efunc_table) { case DOKEY_CLEAR: return newint(clear_display_screen()); case DOKEY_FLUSH: return newint(screen_end(0)); case DOKEY_LNEXT: return newint(literal_next = TRUE); case DOKEY_NEWLINE: return newint(dokey_newline()); case DOKEY_PAUSE: return newint(pause_screen()); case DOKEY_RECALLB: return newint(replace_input(recall_input(-n,0))); case DOKEY_RECALLBEG: return newint(replace_input(recall_input(-n,2))); case DOKEY_RECALLEND: return newint(replace_input(recall_input(n,2))); case DOKEY_RECALLF: return newint(replace_input(recall_input(n,0))); case DOKEY_REDRAW: return newint(redraw()); case DOKEY_REFRESH: return newint((logical_refresh(), keyboard_pos)); case DOKEY_SEARCHB: return newint(replace_input(recall_input(-n,1))); case DOKEY_SEARCHF: return newint(replace_input(recall_input(n,1))); case DOKEY_SELFLUSH: return newint(selflush()); default: return shareval(val_zero); /* impossible */ } } static int dokey_newline(void) { reset_outcount(NULL); inewline(); /* We might be in the middle of a macro (^M -> /dokey newline) now, * so we can't process the input now, or weird things will happen with * current_command and mecho. So we just set a flag and wait until * later when things are cleaner. */ pending_line = TRUE; return 1; /* return value isn't really used */ } static int replace_input(String *line) { if (!line) { dobell(1); return 0; } if (keybuf->len) { Stringtrunc(keybuf, keyboard_pos = 0); logical_refresh(); } handle_input_string(line->data, line->len); return 1; } int do_kbdel(int place) { if (place >= 0 && place < keyboard_pos) { Stringcpy(scratch, keybuf->data + keyboard_pos); SStringcat(Stringtrunc(keybuf, place), CS(scratch)); idel(place); } else if (place > keyboard_pos && place <= keybuf->len) { Stringcpy(scratch, keybuf->data + place); SStringcat(Stringtrunc(keybuf, keyboard_pos), CS(scratch)); idel(place); } else { dobell(1); } sync_input_hist(); return keyboard_pos; } #define is_inword(c) (is_alnum(c) || (wordpunct && strchr(wordpunct, (c)))) int do_kbword(int start, int dir) { int stop = (dir < 0) ? -1 : keybuf->len; int place = start<0 ? 0 : start>keybuf->len ? keybuf->len : start; place -= (dir < 0); while (place != stop && !is_inword(keybuf->data[place])) place += dir; while (place != stop && is_inword(keybuf->data[place])) place += dir; return place + (dir < 0); } int do_kbmatch(int start) { static const char *braces = "(){}[]"; const char *type; int dir, stop, depth = 0; int place = start<0 ? 0 : start>keybuf->len ? keybuf->len : start; while (1) { if (place >= keybuf->len) return -1; if ((type = strchr(braces, keybuf->data[place]))) break; ++place; } dir = ((type - braces) % 2) ? -1 : 1; stop = (dir < 0) ? -1 : keybuf->len; do { if (keybuf->data[place] == type[0]) depth++; else if (keybuf->data[place] == type[dir]) depth--; if (depth == 0) return place; } while ((place += dir) != stop); return -1; } int handle_input_line(void) { String *line; int result; SStringcpy(scratch, CS(keybuf)); Stringtrunc(keybuf, keyboard_pos = 0); pending_line = FALSE; if (*scratch->data == '^') { if (!(line = history_sub(scratch))) { oputs("% No match."); return 0; } SStringcpy(keybuf, CS(line)); iput(keyboard_pos = keybuf->len); inewline(); Stringtrunc(keybuf, keyboard_pos = 0); } else line = scratch; if (kecho) tfprintf(tferr, "%S%S%A", kprefix, line, getattrvar(VAR_kecho_attr)); gettime(&line->time); record_input(CS(line)); readsafe = 1; result = macro_run(CS(line), 0, NULL, 0, sub, "\bUSER"); readsafe = 0; return result; } #if USE_DMALLOC void free_keyboard(void) { tfclose(tfkeyboard); Stringfree(keybuf); Stringfree(scratch); Stringfree(current_input); } #endif