1 /*************************************************************************
2  *  TinyFugue - programmable mud client
3  *  Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
4  *
5  *  TinyFugue (aka "tf") is protected under the terms of the GNU
6  *  General Public License.  See the file "COPYING" for details.
7  ************************************************************************/
8 
9 /**************************************************
10  * Fugue keyboard handling.
11  * Handles all keyboard input and keybindings.
12  **************************************************/
13 
14 #include "tfconfig.h"
15 #include "port.h"
16 #include "tf.h"
17 #include "util.h"
18 #include "pattern.h"	/* for tfio.h */
19 #include "search.h"
20 #include "tfio.h"
21 #include "macro.h"	/* Macro, find_macro(), do_macro()... */
22 #include "keyboard.h"
23 #include "output.h"	/* iput(), idel(), redraw()... */
24 #include "history.h"	/* history_sub() */
25 #include "expand.h"	/* macro_run() */
26 #include "cmdlist.h"
27 #include "variable.h"	/* unsetvar() */
28 
29 static int literal_next = FALSE;
30 static TrieNode *keynode = NULL;	/* current node matched by input */
31 static int kbnum_internal = 0;
32 
33 int pending_line = FALSE;
34 int pending_input = FALSE;
35 struct timeval keyboard_time;
36 
37 static int  dokey_newline(void);
38 static int  replace_input(String *line);
39 static void handle_input_string(const char *input, unsigned int len);
40 
41 
42 STATIC_BUFFER(scratch);                 /* buffer for manipulating text */
43 STATIC_BUFFER(current_input);           /* unprocessed keystrokes */
44 static TrieNode *keytrie = NULL;        /* root of keybinding trie */
45 
46 AUTO_BUFFER(keybuf);                    /* input buffer */
47 int keyboard_pos = 0;                   /* current position in buffer */
48 
49 /*
50  * Some dokey operations are implemented internally with names like
51  * DOKEY_FOO; others are implemented as macros in stdlib.tf with names
52  * like /dokey_foo.  handle_dokey_command() looks first for an internal
53  * function in efunc_table[], then for a macro, so all operations can be done
54  * with "/dokey foo".  Conversely, internally-implemented operations should
55  * have macros in stdlib.tf of the form "/def dokey_foo = /dokey foo",
56  * so all operations can be performed with "/dokey_foo".
57  */
58 enum {
59 #define gencode(id) DOKEY_##id
60 #include "keylist.h"
61 #undef gencode
62 };
63 
64 static const char *efunc_table[] = {
65 #define gencode(id) #id
66 #include "keylist.h"
67 #undef gencode
68 };
69 
70 #define kbnumval	(kbnum ? atoi(kbnum->data) : 1)
71 
72 
init_keyboard(void)73 void init_keyboard(void)
74 {
75     gettime(&keyboard_time);
76 }
77 
78 /* Find the macro associated with <key> sequence. */
find_key(const char * key)79 Macro *find_key(const char *key)
80 {
81     return (Macro *)trie_find(keytrie, (unsigned char*)key);
82 }
83 
bind_key(Macro * spec,const char * key)84 int bind_key(Macro *spec, const char *key)
85 {
86     int status = intrie(&keytrie, spec, (const unsigned char*)key);
87     if (status < 0) {
88         eprintf("'%S' is %s an existing keybinding.",
89             ascii_to_print(key),
90             (status == TRIE_SUPER) ? "prefixed by" : "a prefix of");
91         return 0;
92     }
93     return 1;
94 }
95 
unbind_key(const char * key)96 void unbind_key(const char *key)
97 {
98     untrie(&keytrie, (const unsigned char*)key);
99     keynode = NULL;  /* in case it pointed to a node that no longer exists */
100 }
101 
102 /* returns 0 at EOF, 1 otherwise */
handle_keyboard_input(int read_flag)103 int handle_keyboard_input(int read_flag)
104 {
105     char buf[64];
106     const char *s;
107     int i, count = 0;
108     static int key_start = 0;
109     static int input_start = 0;
110     static int place = 0;
111     static int eof = 0;
112 
113     /* Solaris select() incorrectly reports the terminal as readable if
114      * the user typed LNEXT or FLUSH, when in fact there is nothing to read.
115      * So we wait for read() to return 0 several times in a row
116      * (hopefully, more times than anyone would realistically type FLUSH in
117      * a row) before deciding it's EOF.
118      */
119 
120     if (eof < 100 && read_flag) {
121         /* read a block of text */
122         if ((count = read(STDIN_FILENO, buf, sizeof(buf))) < 0) {
123             /* error or interrupt */
124             if (errno == EINTR) return 1;
125             die("handle_keyboard_input: read", errno);
126         } else if (count > 0) {
127             /* something was read */
128 	    eof = 0;
129             gettime(&keyboard_time);
130         } else {
131             /* nothing was read, and nothing is buffered */
132 	    /* Don't close stdin; we might be wrong (solaris bug), and we
133 	     * don't want the fd to be reused anyway. */
134 	    eof++;
135         }
136     }
137 
138     if (count == 0 && place == 0)
139 	goto end;
140 
141     for (i = 0; i < count; i++) {
142 #if !WIDECHAR
143         if (istrip) buf[i] &= 0x7F;
144         if (buf[i] & 0x80) {
145 	    if (!literal_next &&
146 		(meta_esc == META_ON || (!is_print(buf[i]) && meta_esc)))
147 	    {
148 		Stringadd(current_input, '\033');
149 		buf[i] &= 0x7F;
150 	    }
151 	    if (!is_print(buf[i]))
152 		buf[i] &= 0x7F;
153         }
154 #endif
155         Stringadd(current_input, mapchar(buf[i]));
156     }
157 
158     s = current_input->data;
159     if (!s) /* no good chars; current_input not yet allocated */
160 	goto end;
161     while (place < current_input->len) {
162         if (!keynode) keynode = keytrie;
163         if ((pending_input = pending_line))
164             break;
165         if (literal_next) {
166             place++;
167             key_start++;
168             literal_next = FALSE;
169             continue;
170         }
171         while (place < current_input->len && keynode && keynode->children)
172             keynode = keynode->u.child[(unsigned char)s[place++]];
173         if (!keynode) {
174             /* No keybinding match; check for builtins. */
175             if (s[key_start] == '\n' || s[key_start] == '\r') {
176                 handle_input_string(s + input_start, key_start - input_start);
177                 place = input_start = ++key_start;
178                 dokey_newline();
179                 /* handle_input_line(); */
180             } else if (s[key_start] == '\b' || s[key_start] == '\177') {
181                 handle_input_string(s + input_start, key_start - input_start);
182                 place = input_start = ++key_start;
183                 do_kbdel(keyboard_pos - kbnumval);
184 		reset_kbnum();
185             } else if (kbnum && is_digit(s[key_start]) &&
186 		key_start == input_start)
187 	    {
188 		int n;
189 		SStringcpy(scratch, kbnum);
190 		Stringadd(scratch, s[key_start]);
191 		place = input_start = ++key_start;
192 		n = kbnumval < 0 ? -kbnumval : kbnumval;
193 		if (max_kbnum > 0 && n > max_kbnum)
194 		    Sprintf(scratch, "%c%d", kbnumval<0 ? '-' : '+', max_kbnum);
195 		setstrvar(&special_var[VAR_kbnum], CS(scratch), FALSE);
196             } else {
197                 /* No builtin; try a suffix. */
198                 place = ++key_start;
199             }
200             keynode = NULL;
201         } else if (!keynode->children) {
202             /* Total match; process everything up to here and call the macro. */
203 	    int kbnumlocal = 0;
204             Macro *macro = (Macro *)keynode->u.datum;
205             handle_input_string(s + input_start, key_start - input_start);
206             key_start = input_start = place;
207             keynode = NULL;  /* before do_macro(), for reentrance */
208 	    if (kbnum) {
209 		kbnum_internal = kbnumlocal = atoi(kbnum->data);
210 		reset_kbnum();
211 	    }
212             do_macro(macro, NULL, 0, USED_KEY, kbnumlocal);
213 	    kbnum_internal = 0;
214         } /* else, partial match; just hold on to it for now. */
215     }
216 
217     /* Process everything up to a possible match. */
218     handle_input_string(s + input_start, key_start - input_start);
219 
220     /* Shift the window if there's no pending partial match. */
221     if (key_start >= current_input->len) {
222         Stringtrunc(current_input, 0);
223         place = key_start = 0;
224     }
225     input_start = key_start;
226     if (pending_line && !read_depth)
227         handle_input_line();
228 end:
229     return eof < 100;
230 }
231 
232 /* Update the input window and keyboard buffer. */
handle_input_string(const char * input,unsigned int len)233 static void handle_input_string(const char *input, unsigned int len)
234 {
235     int putlen = len, extra = 0;
236 
237     if (len == 0) return;
238     if (kbnum) {
239 	if (kbnumval > 1) {
240 	    extra = kbnumval - 1;
241 	    putlen = len + extra;
242 	}
243 	reset_kbnum();
244     }
245 
246     /* if this is a fresh line, input history is already synced;
247      * if user deleted line, input history is already synced;
248      * if called from replace_input, we don't want input history synced.
249      */
250     if (keybuf->len) sync_input_hist();
251 
252     if (keyboard_pos == keybuf->len) {                    /* add to end */
253 	if (extra) {
254 	    Stringnadd(keybuf, *input, extra);
255 	    keyboard_pos += extra;
256 	}
257 	Stringncat(keybuf, input, len);
258     } else if (insert) {                                  /* insert in middle */
259         Stringcpy(scratch, keybuf->data + keyboard_pos);
260         Stringtrunc(keybuf, keyboard_pos);
261 	if (extra) {
262 	    Stringnadd(keybuf, *input, extra);
263 	    keyboard_pos += extra;
264 	}
265         Stringncat(keybuf, input, len);
266         SStringcat(keybuf, CS(scratch));
267     } else if (keyboard_pos + len + extra < keybuf->len) {    /* overwrite */
268 	while (extra) {
269 	    keybuf->data[keyboard_pos++] = *input;
270 	    extra--;
271 	}
272 	memcpy(keybuf->data + keyboard_pos, input, len);
273     } else {                                              /* write past end */
274         Stringtrunc(keybuf, keyboard_pos);
275 	if (extra) {
276 	    Stringnadd(keybuf, *input, extra);
277 	    keyboard_pos += extra;
278 	}
279         Stringncat(keybuf, input, len);
280     }
281     keyboard_pos += len;
282     iput(putlen);
283 }
284 
285 
handle_input_command(String * args,int offset)286 struct Value *handle_input_command(String *args, int offset)
287 {
288     handle_input_string(args->data + offset, args->len - offset);
289     return shareval(val_one);
290 }
291 
292 
293 /*
294  *  Builtin key functions.
295  */
296 
handle_dokey_command(String * args,int offset)297 struct Value *handle_dokey_command(String *args, int offset)
298 {
299     const char **ptr;
300     STATIC_BUFFER(buffer);
301     Macro *macro;
302     int n;
303 
304     /* XXX We use kbnum_internal here, but a macro would use the local %kbnum.
305      * It is possible (though unadvisable) for a macro to change the local
306      * %kbnum before this point, making this code behave differently than
307      * a /dokey_foo macro would.  Fetching the actual local %kbnum here would
308      * make the behavior the same, but is a step in the wrong direction;
309      * a better solution would be to make the local %kbnum non-modifiable
310      * by macro code (but variable.c doesn't yet support const). */
311     n = kbnum_internal ? kbnum_internal : 1;
312 
313     ptr = (const char **)bsearch((void*)(args->data + offset),
314         (void*)efunc_table,
315         sizeof(efunc_table)/sizeof(char*), sizeof(char*), cstrstructcmp);
316 
317     if (!ptr) {
318         SStringocat(Stringcpy(buffer, "dokey_"), CS(args), offset);
319         if ((macro = find_macro(buffer->data)))
320             return newint(do_macro(macro, NULL, 0, USED_NAME, 0));
321         else eprintf("No editing function %s", args->data + offset);
322         return shareval(val_zero);
323     }
324 
325     switch (ptr - efunc_table) {
326 
327     case DOKEY_CLEAR:      return newint(clear_display_screen());
328     case DOKEY_FLUSH:      return newint(screen_end(0));
329     case DOKEY_LNEXT:      return newint(literal_next = TRUE);
330     case DOKEY_NEWLINE:    return newint(dokey_newline());
331     case DOKEY_PAUSE:      return newint(pause_screen());
332     case DOKEY_RECALLB:    return newint(replace_input(recall_input(-n,0)));
333     case DOKEY_RECALLBEG:  return newint(replace_input(recall_input(-n,2)));
334     case DOKEY_RECALLEND:  return newint(replace_input(recall_input(n,2)));
335     case DOKEY_RECALLF:    return newint(replace_input(recall_input(n,0)));
336     case DOKEY_REDRAW:     return newint(redraw());
337     case DOKEY_REFRESH:    return newint((logical_refresh(), keyboard_pos));
338     case DOKEY_SEARCHB:    return newint(replace_input(recall_input(-n,1)));
339     case DOKEY_SEARCHF:    return newint(replace_input(recall_input(n,1)));
340     case DOKEY_SELFLUSH:   return newint(selflush());
341     default:               return shareval(val_zero); /* impossible */
342     }
343 }
344 
dokey_newline(void)345 static int dokey_newline(void)
346 {
347     reset_outcount(NULL);
348     inewline();
349     /* We might be in the middle of a macro (^M -> /dokey newline) now,
350      * so we can't process the input now, or weird things will happen with
351      * current_command and mecho.  So we just set a flag and wait until
352      * later when things are cleaner.
353      */
354     pending_line = TRUE;
355     return 1;  /* return value isn't really used */
356 }
357 
replace_input(String * line)358 static int replace_input(String *line)
359 {
360     if (!line) {
361         dobell(1);
362         return 0;
363     }
364     if (keybuf->len) {
365         Stringtrunc(keybuf, keyboard_pos = 0);
366         logical_refresh();
367     }
368     handle_input_string(line->data, line->len);
369     return 1;
370 }
371 
do_kbdel(int place)372 int do_kbdel(int place)
373 {
374     if (place >= 0 && place < keyboard_pos) {
375         Stringcpy(scratch, keybuf->data + keyboard_pos);
376         SStringcat(Stringtrunc(keybuf, place), CS(scratch));
377         idel(place);
378     } else if (place > keyboard_pos && place <= keybuf->len) {
379         Stringcpy(scratch, keybuf->data + place);
380         SStringcat(Stringtrunc(keybuf, keyboard_pos), CS(scratch));
381         idel(place);
382     } else {
383         dobell(1);
384     }
385     sync_input_hist();
386     return keyboard_pos;
387 }
388 
389 #define is_inword(c) (is_alnum(c) || (wordpunct && strchr(wordpunct, (c))))
390 
do_kbword(int start,int dir)391 int do_kbword(int start, int dir)
392 {
393     int stop = (dir < 0) ? -1 : keybuf->len;
394     int place = start<0 ? 0 : start>keybuf->len ? keybuf->len : start;
395     place -= (dir < 0);
396 
397     while (place != stop && !is_inword(keybuf->data[place])) place += dir;
398     while (place != stop && is_inword(keybuf->data[place])) place += dir;
399     return place + (dir < 0);
400 }
401 
do_kbmatch(int start)402 int do_kbmatch(int start)
403 {
404     static const char *braces = "(){}[]";
405     const char *type;
406     int dir, stop, depth = 0;
407     int place = start<0 ? 0 : start>keybuf->len ? keybuf->len : start;
408 
409     while (1) {
410         if (place >= keybuf->len) return -1;
411         if ((type = strchr(braces, keybuf->data[place]))) break;
412         ++place;
413     }
414     dir = ((type - braces) % 2) ? -1 : 1;
415     stop = (dir < 0) ? -1 : keybuf->len;
416     do {
417         if      (keybuf->data[place] == type[0])   depth++;
418         else if (keybuf->data[place] == type[dir]) depth--;
419         if (depth == 0) return place;
420     } while ((place += dir) != stop);
421     return -1;
422 }
423 
handle_input_line(void)424 int handle_input_line(void)
425 {
426     String *line;
427     int result;
428 
429     SStringcpy(scratch, CS(keybuf));
430     Stringtrunc(keybuf, keyboard_pos = 0);
431     pending_line = FALSE;
432 
433     if (*scratch->data == '^') {
434         if (!(line = history_sub(scratch))) {
435             oputs("% No match.");
436             return 0;
437         }
438         SStringcpy(keybuf, CS(line));
439         iput(keyboard_pos = keybuf->len);
440         inewline();
441         Stringtrunc(keybuf, keyboard_pos = 0);
442     } else
443         line = scratch;
444 
445     if (kecho)
446 	tfprintf(tferr, "%S%S%A", kprefix, line, getattrvar(VAR_kecho_attr));
447     gettime(&line->time);
448     record_input(CS(line));
449     readsafe = 1;
450     result = macro_run(CS(line), 0, NULL, 0, sub, "\bUSER");
451     readsafe = 0;
452     return result;
453 }
454 
455 #if USE_DMALLOC
free_keyboard(void)456 void free_keyboard(void)
457 {
458     tfclose(tfkeyboard);
459     Stringfree(keybuf);
460     Stringfree(scratch);
461     Stringfree(current_input);
462 }
463 #endif
464