1 /* $Id: func.c,v 1.27 2003/09/26 17:59:51 ukai Exp $ */
2 /*
3  * w3m func.c
4  */
5 
6 #include <stdio.h>
7 
8 #include "fm.h"
9 #include "func.h"
10 #include "myctype.h"
11 #include "regex.h"
12 
13 #include "funcname.c"
14 #include "functable.c"
15 
16 #define KEYDATA_HASH_SIZE 16
17 static Hash_iv *keyData = NULL;
18 static char keymap_initialized = FALSE;
19 static struct stat sys_current_keymap_file;
20 static struct stat current_keymap_file;
21 
22 void
setKeymap(char * p,int lineno,int verbose)23 setKeymap(char *p, int lineno, int verbose)
24 {
25     unsigned char *map = NULL;
26     char *s, *emsg;
27     int c, f;
28 
29     s = getQWord(&p);
30     c = getKey(s);
31     if (c < 0) {		/* error */
32 	if (lineno > 0)
33 	    /* FIXME: gettextize? */
34 	    emsg = Sprintf("line %d: unknown key '%s'", lineno, s)->ptr;
35 	else
36 	    /* FIXME: gettextize? */
37 	    emsg = Sprintf("defkey: unknown key '%s'", s)->ptr;
38 	record_err_message(emsg);
39 	if (verbose)
40 	    disp_message_nsec(emsg, FALSE, 1, TRUE, FALSE);
41 	return;
42     }
43     s = getWord(&p);
44     f = getFuncList(s);
45     if (f < 0) {
46 	if (lineno > 0)
47 	    /* FIXME: gettextize? */
48 	    emsg = Sprintf("line %d: invalid command '%s'", lineno, s)->ptr;
49 	else
50 	    /* FIXME: gettextize? */
51 	    emsg = Sprintf("defkey: invalid command '%s'", s)->ptr;
52 	record_err_message(emsg);
53 	if (verbose)
54 	    disp_message_nsec(emsg, FALSE, 1, TRUE, FALSE);
55 	return;
56     }
57     if (c & K_MULTI) {
58 	unsigned char **mmap = NULL;
59 	int i, j, m = MULTI_KEY(c);
60 
61 	if (m & K_ESCD)
62 	    map = EscDKeymap;
63 	else if (m & K_ESCB)
64 	    map = EscBKeymap;
65 	else if (m & K_ESC)
66 	    map = EscKeymap;
67 	else
68 	    map = GlobalKeymap;
69 	if (map[m & 0x7F] == FUNCNAME_multimap)
70 	    mmap = (unsigned char **)getKeyData(m);
71 	else
72 	    map[m & 0x7F] = FUNCNAME_multimap;
73 	if (!mmap) {
74 	    mmap = New_N(unsigned char *, 4);
75 	    for (i = 0; i < 4; i++) {
76 		mmap[i] = New_N(unsigned char, 128);
77 		for (j = 0; j < 128; j++)
78 		    mmap[i][j] = FUNCNAME_nulcmd;
79 	    }
80 	    mmap[0][ESC_CODE] = FUNCNAME_escmap;
81 	    mmap[1]['['] = FUNCNAME_escbmap;
82 	    mmap[1]['O'] = FUNCNAME_escbmap;
83 	}
84 	if (keyData == NULL)
85 	    keyData = newHash_iv(KEYDATA_HASH_SIZE);
86 	putHash_iv(keyData, m, (void *)mmap);
87 	if (c & K_ESCD)
88 	    map = mmap[3];
89 	else if (c & K_ESCB)
90 	    map = mmap[2];
91 	else if (c & K_ESC)
92 	    map = mmap[1];
93 	else
94 	    map = mmap[0];
95     }
96     else {
97 	if (c & K_ESCD)
98 	    map = EscDKeymap;
99 	else if (c & K_ESCB)
100 	    map = EscBKeymap;
101 	else if (c & K_ESC)
102 	    map = EscKeymap;
103 	else
104 	    map = GlobalKeymap;
105     }
106     map[c & 0x7F] = f;
107     s = getQWord(&p);
108     if (*s) {
109 	if (keyData == NULL)
110 	    keyData = newHash_iv(KEYDATA_HASH_SIZE);
111 	putHash_iv(keyData, c, (void *)s);
112     }
113     else if (getKeyData(c))
114 	putHash_iv(keyData, c, NULL);
115 }
116 
117 static void
interpret_keymap(FILE * kf,struct stat * current,int force)118 interpret_keymap(FILE * kf, struct stat *current, int force)
119 {
120     int fd;
121     struct stat kstat;
122     Str line;
123     char *p, *s, *emsg;
124     int lineno;
125 #ifdef USE_M17N
126     wc_ces charset = SystemCharset;
127 #endif
128     int verbose = 1;
129     extern int str_to_bool(char *value, int old);
130 
131     if ((fd = fileno(kf)) < 0 || fstat(fd, &kstat) ||
132 	(!force &&
133 	 kstat.st_mtime == current->st_mtime &&
134 	 kstat.st_dev == current->st_dev &&
135 	 kstat.st_ino == current->st_ino && kstat.st_size == current->st_size))
136 	return;
137     *current = kstat;
138 
139     lineno = 0;
140     while (!feof(kf)) {
141 	line = Strfgets(kf);
142 	lineno++;
143 	Strchop(line);
144 	Strremovefirstspaces(line);
145 	if (line->length == 0)
146 	    continue;
147 #ifdef USE_M17N
148 	line = wc_Str_conv(line, charset, InnerCharset);
149 #endif
150 	p = line->ptr;
151 	s = getWord(&p);
152 	if (*s == '#')		/* comment */
153 	    continue;
154 	if (!strcmp(s, "keymap")) ;
155 #ifdef USE_M17N
156 	else if (!strcmp(s, "charset") || !strcmp(s, "encoding")) {
157 	    s = getQWord(&p);
158 	    if (*s)
159 		charset = wc_guess_charset(s, charset);
160 	    continue;
161 	}
162 #endif
163 	else if (!strcmp(s, "verbose")) {
164 	    s = getWord(&p);
165 	    if (*s)
166 		verbose = str_to_bool(s, verbose);
167 	    continue;
168 	}
169 	else {			/* error */
170 	    emsg = Sprintf("line %d: syntax error '%s'", lineno, s)->ptr;
171 	    record_err_message(emsg);
172 	    if (verbose)
173 		disp_message_nsec(emsg, FALSE, 1, TRUE, FALSE);
174 	    continue;
175 	}
176 	setKeymap(p, lineno, verbose);
177     }
178 }
179 
180 void
initKeymap(int force)181 initKeymap(int force)
182 {
183     FILE *kf;
184 
185     if ((kf = fopen(confFile(KEYMAP_FILE), "rt")) != NULL) {
186 	interpret_keymap(kf, &sys_current_keymap_file,
187 			 force || !keymap_initialized);
188 	fclose(kf);
189     }
190     if ((kf = fopen(rcFile(keymap_file), "rt")) != NULL) {
191 	interpret_keymap(kf, &current_keymap_file,
192 			 force || !keymap_initialized);
193 	fclose(kf);
194     }
195     keymap_initialized = TRUE;
196 }
197 
198 int
getFuncList(char * id)199 getFuncList(char *id)
200 {
201     return getHash_si(&functable, id, -1);
202 }
203 
204 char *
getKeyData(int key)205 getKeyData(int key)
206 {
207     if (keyData == NULL)
208 	return NULL;
209     return (char *)getHash_iv(keyData, key, NULL);
210 }
211 
212 static int
getKey2(char ** str)213 getKey2(char **str)
214 {
215     char *s = *str;
216     int c, esc = 0, ctrl = 0;
217 
218     if (s == NULL || *s == '\0')
219 	return -1;
220 
221     if (strcasecmp(s, "UP") == 0) {	/* ^[[A */
222 	*str = s + 2;
223 	return K_ESCB | 'A';
224     }
225     else if (strcasecmp(s, "DOWN") == 0) {	/* ^[[B */
226 	*str = s + 4;
227 	return K_ESCB | 'B';
228     }
229     else if (strcasecmp(s, "RIGHT") == 0) {	/* ^[[C */
230 	*str = s + 5;
231 	return K_ESCB | 'C';
232     }
233     else if (strcasecmp(s, "LEFT") == 0) {	/* ^[[D */
234 	*str = s + 4;
235 	return K_ESCB | 'D';
236     }
237 
238     if (strncasecmp(s, "ESC-", 4) == 0 || strncasecmp(s, "ESC ", 4) == 0) {	/* ^[ */
239 	s += 4;
240 	esc = K_ESC;
241     }
242     else if (strncasecmp(s, "M-", 2) == 0 || strncasecmp(s, "\\E", 2) == 0) {	/* ^[ */
243 	s += 2;
244 	esc = K_ESC;
245     }
246     else if (*s == ESC_CODE) {	/* ^[ */
247 	s++;
248 	esc = K_ESC;
249     }
250     if (strncasecmp(s, "C-", 2) == 0) {	/* ^, ^[^ */
251 	s += 2;
252 	ctrl = 1;
253     }
254     else if (*s == '^' && *(s + 1)) {	/* ^, ^[^ */
255 	s++;
256 	ctrl = 1;
257     }
258     if (!esc && ctrl && *s == '[') {	/* ^[ */
259 	s++;
260 	ctrl = 0;
261 	esc = K_ESC;
262     }
263     if (esc && !ctrl) {
264 	if (*s == '[' || *s == 'O') {	/* ^[[, ^[O */
265 	    s++;
266 	    esc = K_ESCB;
267 	}
268 	if (strncasecmp(s, "C-", 2) == 0) {	/* ^[^, ^[[^ */
269 	    s += 2;
270 	    ctrl = 1;
271 	}
272 	else if (*s == '^' && *(s + 1)) {	/* ^[^, ^[[^ */
273 	    s++;
274 	    ctrl = 1;
275 	}
276     }
277 
278     if (ctrl) {
279 	*str = s + 1;
280 	if (*s >= '@' && *s <= '_')	/* ^@ .. ^_ */
281 	    return esc | (*s - '@');
282 	else if (*s >= 'a' && *s <= 'z')	/* ^a .. ^z */
283 	    return esc | (*s - 'a' + 1);
284 	else if (*s == '?')	/* ^? */
285 	    return esc | DEL_CODE;
286 	else
287 	    return -1;
288     }
289 
290     if (esc == K_ESCB && IS_DIGIT(*s)) {
291 	c = (int)(*s - '0');
292 	s++;
293 	if (IS_DIGIT(*s)) {
294 	    c = c * 10 + (int)(*s - '0');
295 	    s++;
296 	}
297 	*str = s + 1;
298 	if (*s == '~')
299 	    return K_ESCD | c;
300 	else
301 	    return -1;
302     }
303 
304     if (strncasecmp(s, "SPC", 3) == 0) {	/* ' ' */
305 	*str = s + 3;
306 	return esc | ' ';
307     }
308     else if (strncasecmp(s, "TAB", 3) == 0) {	/* ^i */
309 	*str = s + 3;
310 	return esc | '\t';
311     }
312     else if (strncasecmp(s, "DEL", 3) == 0) {	/* ^? */
313 	*str = s + 3;
314 	return esc | DEL_CODE;
315     }
316 
317     if (*s == '\\' && *(s + 1) != '\0') {
318 	s++;
319 	*str = s + 1;
320 	switch (*s) {
321 	case 'a':		/* ^g */
322 	    return esc | CTRL_G;
323 	case 'b':		/* ^h */
324 	    return esc | CTRL_H;
325 	case 't':		/* ^i */
326 	    return esc | CTRL_I;
327 	case 'n':		/* ^j */
328 	    return esc | CTRL_J;
329 	case 'r':		/* ^m */
330 	    return esc | CTRL_M;
331 	case 'e':		/* ^[ */
332 	    return esc | ESC_CODE;
333 	case '^':		/* ^ */
334 	    return esc | '^';
335 	case '\\':		/* \ */
336 	    return esc | '\\';
337 	default:
338 	    return -1;
339 	}
340     }
341     *str = s + 1;
342     if (IS_ASCII(*s))		/* Ascii */
343 	return esc | *s;
344     else
345 	return -1;
346 }
347 
348 int
getKey(char * s)349 getKey(char *s)
350 {
351     int c, c2;
352 
353     c = getKey2(&s);
354     if (c < 0)
355 	return -1;
356     if (*s == ' ' || *s == '-')
357 	s++;
358     if (*s) {
359 	c2 = getKey2(&s);
360 	if (c2 < 0)
361 	    return -1;
362 	c = K_MULTI | (c << 16) | c2;
363     }
364     return c;
365 }
366 
367 char *
getWord(char ** str)368 getWord(char **str)
369 {
370     char *p, *s;
371 
372     p = *str;
373     SKIP_BLANKS(p);
374     for (s = p; *p && !IS_SPACE(*p) && *p != ';'; p++) ;
375     *str = p;
376     return Strnew_charp_n(s, p - s)->ptr;
377 }
378 
379 char *
getQWord(char ** str)380 getQWord(char **str)
381 {
382     Str tmp = Strnew();
383     char *p;
384     int in_q = 0, in_dq = 0, esc = 0;
385 
386     p = *str;
387     SKIP_BLANKS(p);
388     for (; *p; p++) {
389 	if (esc) {
390 	    if (in_q) {
391 		if (*p != '\\' && *p != '\'')	/* '..\\..', '..\'..' */
392 		    Strcat_char(tmp, '\\');
393 	    }
394 	    else if (in_dq) {
395 		if (*p != '\\' && *p != '"')	/* "..\\..", "..\".." */
396 		    Strcat_char(tmp, '\\');
397 	    }
398 	    else {
399 		if (*p != '\\' && *p != '\'' &&	/* ..\\.., ..\'.. */
400 		    *p != '"' && !IS_SPACE(*p))	/* ..\".., ..\.. */
401 		    Strcat_char(tmp, '\\');
402 	    }
403 	    Strcat_char(tmp, *p);
404 	    esc = 0;
405 	}
406 	else if (*p == '\\') {
407 	    esc = 1;
408 	}
409 	else if (in_q) {
410 	    if (*p == '\'')
411 		in_q = 0;
412 	    else
413 		Strcat_char(tmp, *p);
414 	}
415 	else if (in_dq) {
416 	    if (*p == '"')
417 		in_dq = 0;
418 	    else
419 		Strcat_char(tmp, *p);
420 	}
421 	else if (*p == '\'') {
422 	    in_q = 1;
423 	}
424 	else if (*p == '"') {
425 	    in_dq = 1;
426 	}
427 	else if (IS_SPACE(*p) || *p == ';') {
428 	    break;
429 	}
430 	else {
431 	    Strcat_char(tmp, *p);
432 	}
433     }
434     *str = p;
435     return tmp->ptr;
436 }
437 
438 /* This extracts /regex/i or m@regex@i from the given string.
439  * Then advances *str to the end of regex.
440  * If the input does not seems to be a regex, this falls back to getQWord().
441  *
442  * Returns a word (no matter whether regex or not) in the give string.
443  * If regex_ret is non-NULL, compiles the regex and stores there.
444  *
445  * XXX: Actually this is unrelated to func.c.
446  */
447 char *
getRegexWord(const char ** str,Regex ** regex_ret)448 getRegexWord(const char **str, Regex **regex_ret)
449 {
450     char *word = NULL;
451     const char *p, *headp, *bodyp, *tailp;
452     char delimiter;
453     int esc;
454     int igncase = 0;
455 
456     p = *str;
457     SKIP_BLANKS(p);
458     headp = p;
459 
460     /* Get the opening delimiter */
461     if (p[0] == 'm' && IS_PRINT(p[1]) && !IS_ALNUM(p[1]) && p[1] != '\\') {
462 	delimiter = p[1];
463 	p += 2;
464     }
465     else if (p[0] == '/') {
466 	delimiter = '/';
467 	p += 1;
468     }
469     else {
470 	goto not_regex;
471     }
472     bodyp = p;
473 
474     /* Scan the end of the expression */
475     for (esc = 0; *p; ++p) {
476 	if (esc) {
477 	    esc = 0;
478 	} else {
479 	    if (*p == delimiter)
480 		break;
481 	    else if (*p == '\\')
482 		esc = 1;
483 	}
484     }
485     if (!*p && *headp == '/')
486 	goto not_regex;
487     tailp = p;
488 
489     /* Check the modifiers */
490     if (*p == delimiter) {
491 	while (*++p && !IS_SPACE(*p)) {
492 	    switch (*p) {
493 	    case 'i':
494 		igncase = 1;
495 		break;
496 	    }
497 	    /* ignore unknown modifiers */
498 	}
499     }
500 
501     /* Save the expression */
502     word = allocStr(headp, p - headp);
503 
504     /* Compile */
505     if (regex_ret) {
506 	if (*tailp == delimiter)
507 	    word[tailp - headp] = 0;
508 	*regex_ret = newRegex(word + (bodyp - headp), igncase, NULL, NULL);
509 	if (*tailp == delimiter)
510 	    word[tailp - headp] = delimiter;
511     }
512     goto last;
513 
514 not_regex:
515     p = headp;
516     word = getQWord((char **)&p);
517     if (regex_ret)
518 	*regex_ret = NULL;
519 
520 last:
521     *str = p;
522     return word;
523 }
524 
525 #ifdef USE_MOUSE
526 static MouseAction default_mouse_action = {
527     NULL,
528     "<=UpDn",
529     0, 6, FALSE, 0, 0,
530     {{movMs, NULL}, {backBf, NULL}, {menuMs, NULL}},	/* default */
531     {{NULL, NULL}, {NULL, NULL}, {NULL, NULL}},	/* anchor */
532     {{followA, NULL}, {NULL, NULL}, {NULL, NULL}},	/* active */
533     {{tabMs, NULL}, {closeTMs, NULL}, {NULL, NULL}},	/* tab */
534     {NULL, NULL, NULL},		/* menu */
535     {NULL, NULL, NULL}		/* lastline */
536 };
537 static MouseActionMap default_lastline_action[6] = {
538     {backBf, NULL},
539     {backBf, NULL},
540     {pgBack, NULL},
541     {pgBack, NULL},
542     {pgFore, NULL},
543     {pgFore, NULL}
544 };
545 
546 static void
setMouseAction0(char ** str,int * width,MouseActionMap ** map,char * p)547 setMouseAction0(char **str, int *width, MouseActionMap ** map, char *p)
548 {
549     char *s;
550     int b, w, x;
551 
552     s = getQWord(&p);
553     if (!*s) {
554 	*str = NULL;
555 	width = 0;
556 	for (b = 0; b < 3; b++)
557 	    map[b] = NULL;
558 	return;
559     }
560     w = *width;
561     *str = s;
562     *width = get_strwidth(s);
563     if (*width >= LIMIT_MOUSE_MENU)
564 	*width = LIMIT_MOUSE_MENU;
565     if (*width <= w)
566 	return;
567     for (b = 0; b < 3; b++) {
568 	if (!map[b])
569 	    continue;
570 	map[b] = New_Reuse(MouseActionMap, map[b], *width);
571 	for (x = w + 1; x < *width; x++) {
572 	    map[b][x].func = NULL;
573 	    map[b][x].data = NULL;
574 	}
575     }
576 }
577 
578 static void
setMouseAction1(MouseActionMap ** map,int width,char * p)579 setMouseAction1(MouseActionMap ** map, int width, char *p)
580 {
581     char *s;
582     int x, x2, f;
583 
584     if (!*map) {
585 	*map = New_N(MouseActionMap, width);
586 	for (x = 0; x < width; x++) {
587 	    (*map)[x].func = NULL;
588 	    (*map)[x].data = NULL;
589 	}
590     }
591     s = getWord(&p);
592     x = atoi(s);
593     if (!(IS_DIGIT(*s) && x >= 0 && x < width))
594 	return;			/* error */
595     s = getWord(&p);
596     x2 = atoi(s);
597     if (!(IS_DIGIT(*s) && x2 >= 0 && x2 < width))
598 	return;			/* error */
599     s = getWord(&p);
600     f = getFuncList(s);
601     s = getQWord(&p);
602     if (!*s)
603 	s = NULL;
604     for (; x <= x2; x++) {
605 	(*map)[x].func = (f >= 0) ? w3mFuncList[f].func : NULL;
606 	(*map)[x].data = s;
607     }
608 }
609 
610 static void
setMouseAction2(MouseActionMap * map,char * p)611 setMouseAction2(MouseActionMap * map, char *p)
612 {
613     char *s;
614     int f;
615 
616     s = getWord(&p);
617     f = getFuncList(s);
618     s = getQWord(&p);
619     if (!*s)
620 	s = NULL;
621     map->func = (f >= 0) ? w3mFuncList[f].func : NULL;
622     map->data = s;
623 }
624 
625 static void
interpret_mouse_action(FILE * mf)626 interpret_mouse_action(FILE * mf)
627 {
628     Str line;
629     char *p, *s;
630     int b;
631 
632     while (!feof(mf)) {
633 	line = Strfgets(mf);
634 	Strchop(line);
635 	Strremovefirstspaces(line);
636 	if (line->length == 0)
637 	    continue;
638 	p = conv_from_system(line->ptr);
639 	s = getWord(&p);
640 	if (*s == '#')		/* comment */
641 	    continue;
642 	if (!strcmp(s, "menu")) {
643 	    setMouseAction0(&mouse_action.menu_str, &mouse_action.menu_width,
644 			    mouse_action.menu_map, p);
645 	    continue;
646 	}
647 	else if (!strcmp(s, "lastline")) {
648 	    setMouseAction0(&mouse_action.lastline_str,
649 			    &mouse_action.lastline_width,
650 			    mouse_action.lastline_map, p);
651 	    continue;
652 	}
653 	if (strcmp(s, "button"))
654 	    continue;		/* error */
655 	s = getWord(&p);
656 	b = atoi(s) - 1;
657 	if (!(b >= 0 && b <= 2))
658 	    continue;		/* error */
659 	SKIP_BLANKS(p);
660 	if (IS_DIGIT(*p))
661 	    s = "menu";
662 	else
663 	    s = getWord(&p);
664 	if (!strcasecmp(s, "menu")) {
665 	    if (!mouse_action.menu_str)
666 		continue;
667 	    setMouseAction1(&mouse_action.menu_map[b], mouse_action.menu_width,
668 			    p);
669 	}
670 	else if (!strcasecmp(s, "lastline")) {
671 	    if (!mouse_action.lastline_str)
672 		continue;
673 	    setMouseAction1(&mouse_action.lastline_map[b],
674 			    mouse_action.lastline_width, p);
675 	}
676 	else if (!strcasecmp(s, "default"))
677 	    setMouseAction2(&mouse_action.default_map[b], p);
678 	else if (!strcasecmp(s, "anchor"))
679 	    setMouseAction2(&mouse_action.anchor_map[b], p);
680 	else if (!strcasecmp(s, "active"))
681 	    setMouseAction2(&mouse_action.active_map[b], p);
682 	else if (!strcasecmp(s, "tab"))
683 	    setMouseAction2(&mouse_action.tab_map[b], p);
684     }
685 }
686 
687 void
initMouseAction(void)688 initMouseAction(void)
689 {
690     FILE *mf;
691 
692     bcopy((void *)&default_mouse_action, (void *)&mouse_action,
693 	  sizeof(default_mouse_action));
694     mouse_action.lastline_map[0] = New_N(MouseActionMap, 6);
695     bcopy((void *)&default_lastline_action,
696 	  (void *)mouse_action.lastline_map[0],
697 	  sizeof(default_lastline_action));
698     {
699 #ifdef USE_M17N
700 	int w = 0;
701 	char **symbol = get_symbol(DisplayCharset, &w);
702 #else
703 	char **symbol = get_symbol();
704 #endif
705 	mouse_action.lastline_str =
706 	    Strnew_charp(symbol[N_GRAPH_SYMBOL + 13])->ptr;
707     }
708 
709     if ((mf = fopen(confFile(MOUSE_FILE), "rt")) != NULL) {
710 	interpret_mouse_action(mf);
711 	fclose(mf);
712     }
713     if ((mf = fopen(rcFile(MOUSE_FILE), "rt")) != NULL) {
714 	interpret_mouse_action(mf);
715 	fclose(mf);
716     }
717 }
718 #endif
719