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