adding DuckDuckGo to default search engines (JS and non-JS variant)
[vimprobable2.git] / utilities.c
blob431147c5bae6dc2eb0b57fcbced61dc844cfc20b
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009-2012 by Hannes Schueller
4 (c) 2009-2010 by Matto Fransen
5 (c) 2010-2011 by Hans-Peter Deifel
6 (c) 2010-2011 by Thomas Adam
7 see LICENSE file
8 */
10 #include "includes.h"
11 #include "vimprobable.h"
12 #include "main.h"
13 #include "utilities.h"
15 extern GList *commandhistory;
16 extern int commandpointer;
17 extern Command commands[COMMANDSIZE];
18 extern KeyList *keylistroot;
19 extern Key keys[];
20 extern char *error_msg;
21 extern gboolean complete_case_sensitive;
22 extern char *config_base;
23 static GList *dynamic_searchengines = NULL;
25 void add_modkeys(char key);
27 void save_command_history(char *line)
29 char *c = line;
31 while (isspace(*c) && *c)
32 c++;
33 if (!strlen(c))
34 return;
36 if (COMMANDHISTSIZE <= g_list_length(commandhistory)) {
37 /* if list is too long - remove items from beginning */
38 commandhistory = g_list_delete_link(commandhistory, g_list_first(commandhistory));
40 commandhistory = g_list_append(commandhistory, g_strdup(c));
43 gboolean
44 process_save_qmark(const char *bm, WebKitWebView *webview)
46 FILE *fp;
47 const char *filename;
48 const char *uri = webkit_web_view_get_uri(webview);
49 char qmarks[10][101];
50 char buf[100];
51 int i, mark, l=0;
52 Arg a;
53 mark = -1;
54 mark = atoi(bm);
55 if ( mark < 1 || mark > 9 )
57 a.i = Error;
58 a.s = g_strdup_printf("Invalid quickmark, only 1-9");
59 echo(&a);
60 g_free(a.s);
61 return TRUE;
63 if ( uri == NULL ) return FALSE;
64 for( i=0; i < 9; ++i ) strcpy( qmarks[i], "");
66 filename = g_strdup_printf(QUICKMARK_FILE);
68 /* get current quickmarks */
70 fp = fopen(filename, "r");
71 if (fp != NULL){
72 for( i=0; i < 10; ++i ) {
73 if (feof(fp)) {
74 break;
76 fgets(buf, 100, fp);
77 l = 0;
78 while (buf[l] && l < 100 && buf[l] != '\n') {
79 qmarks[i][l]=buf[l];
80 l++;
82 qmarks[i][l]='\0';
84 fclose(fp);
87 /* save quickmarks */
88 strcpy( qmarks[mark-1], uri );
89 fp = fopen(filename, "w");
90 g_free((gpointer *)filename);
91 if (fp == NULL) return FALSE;
92 for( i=0; i < 10; ++i )
93 fprintf(fp, "%s\n", qmarks[i]);
94 fclose(fp);
95 a.i = Error;
96 a.s = g_strdup_printf("Saved as quickmark %d: %s", mark, uri);
97 echo(&a);
98 g_free(a.s);
100 return TRUE;
103 void
104 make_keyslist(void)
106 int i;
107 KeyList *ptr, *current;
109 ptr = NULL;
110 current = NULL;
111 i = 0;
112 while ( keys[i].key != 0 )
114 current = malloc(sizeof(KeyList));
115 if (current == NULL) {
116 printf("Not enough memory\n");
117 exit(-1);
119 current->Element = keys[i];
120 current->next = NULL;
121 if (keylistroot == NULL) keylistroot = current;
122 if (ptr != NULL) ptr->next = current;
123 ptr = current;
124 i++;
128 gboolean
129 parse_colour(char *color) {
130 char goodcolor[8];
131 int colorlen;
133 colorlen = (int)strlen(color);
135 goodcolor[0] = '#';
136 goodcolor[7] = '\0';
138 /* help the user a bit by making string like
139 #a10 and strings like ffffff full 6digit
140 strings with # in front :)
143 if (color[0] == '#') {
144 switch (colorlen) {
145 case 7:
146 strncpy(goodcolor, color, 7);
147 break;
148 case 4:
149 goodcolor[1] = color[1];
150 goodcolor[2] = color[1];
151 goodcolor[3] = color[2];
152 goodcolor[4] = color[2];
153 goodcolor[5] = color[3];
154 goodcolor[6] = color[3];
155 break;
156 case 2:
157 goodcolor[1] = color[1];
158 goodcolor[2] = color[1];
159 goodcolor[3] = color[1];
160 goodcolor[4] = color[1];
161 goodcolor[5] = color[1];
162 goodcolor[6] = color[1];
163 break;
165 } else {
166 switch (colorlen) {
167 case 6:
168 strncpy(&goodcolor[1], color, 6);
169 break;
170 case 3:
171 goodcolor[1] = color[0];
172 goodcolor[2] = color[0];
173 goodcolor[3] = color[1];
174 goodcolor[4] = color[1];
175 goodcolor[5] = color[2];
176 goodcolor[6] = color[2];
177 break;
178 case 1:
179 goodcolor[1] = color[0];
180 goodcolor[2] = color[0];
181 goodcolor[3] = color[0];
182 goodcolor[4] = color[0];
183 goodcolor[5] = color[0];
184 goodcolor[6] = color[0];
185 break;
189 if (strlen (goodcolor) != 7) {
190 return FALSE;
191 } else {
192 strncpy(color, goodcolor, 8);
193 return TRUE;
197 gboolean
198 process_line_arg(const Arg *arg) {
199 return process_line(arg->s);
202 gboolean
203 changemapping(Key *search_key, int maprecord, char *cmd) {
204 KeyList *current, *newkey;
205 Arg a = { .s = cmd };
207 /* sanity check */
208 if (maprecord < 0 && cmd == NULL) {
209 /* possible states:
210 * - maprecord >= 0 && cmd == NULL: mapping to internal symbol
211 * - maprecord < 0 && cmd != NULL: mapping to command line
212 * - maprecord >= 0 && cmd != NULL: cmd will be ignored, treated as mapping to internal symbol
213 * - anything else (gets in here): an error, hence we return FALSE */
214 return FALSE;
217 current = keylistroot;
219 if (current)
220 while (current->next != NULL) {
221 if (
222 current->Element.mask == search_key->mask &&
223 current->Element.modkey == search_key->modkey &&
224 current->Element.key == search_key->key
226 if (maprecord >= 0) {
227 /* mapping to an internal signal */
228 current->Element.func = commands[maprecord].func;
229 current->Element.arg = commands[maprecord].arg;
230 } else {
231 /* mapping to a command line */
232 current->Element.func = process_line_arg;
233 current->Element.arg = a;
235 return TRUE;
237 current = current->next;
239 newkey = malloc(sizeof(KeyList));
240 if (newkey == NULL) {
241 printf("Not enough memory\n");
242 exit (-1);
244 newkey->Element.mask = search_key->mask;
245 newkey->Element.modkey = search_key->modkey;
246 newkey->Element.key = search_key->key;
247 if (maprecord >= 0) {
248 /* mapping to an internal signal */
249 newkey->Element.func = commands[maprecord].func;
250 newkey->Element.arg = commands[maprecord].arg;
251 } else {
252 /* mapping to a command line */
253 newkey->Element.func = process_line_arg;
254 newkey->Element.arg = a;
256 add_modkeys(newkey->Element.modkey);
258 newkey->next = NULL;
260 if (keylistroot == NULL) keylistroot = newkey;
262 if (current != NULL) current->next = newkey;
264 return TRUE;
267 void add_modkeys(char key)
269 unsigned int k, len;
270 extern char *modkeys;
271 len = strlen( modkeys );
272 while (k < len )
274 if ( modkeys[k] == key ) return;
275 k++;
277 modkeys = realloc(modkeys, len + 1);
278 modkeys[len++] = key;
279 modkeys[len] = '\0';
282 gboolean
283 mappings(const Arg *arg) {
284 char line[255];
286 if (!arg->s) {
287 set_error("Missing argument.");
288 return FALSE;
290 strncpy(line, arg->s, 254);
291 if (process_map_line(line))
292 return TRUE;
293 else {
294 set_error("Invalid mapping.");
295 return FALSE;
299 int
300 get_modkey(char key) {
301 switch (key) {
302 case '1':
303 return GDK_MOD1_MASK;
304 case '2':
305 return GDK_MOD2_MASK;
306 case '3':
307 return GDK_MOD3_MASK;
308 case '4':
309 return GDK_MOD4_MASK;
310 case '5':
311 return GDK_MOD5_MASK;
312 default:
313 return FALSE;
317 gboolean
318 process_mapping(char *keystring, int maprecord, char *cmd) {
319 Key search_key;
321 search_key.mask = 0;
322 search_key.modkey = 0;
323 search_key.key = 0;
325 if (strlen(keystring) == 1) {
326 search_key.key = keystring[0];
329 if (strlen(keystring) == 2) {
330 search_key.modkey= keystring[0];
331 search_key.key = keystring[1];
334 /* process stuff like <S-v> for Shift-v or <C-v> for Ctrl-v (strlen == 5),
335 stuff like <S-v>a for Shift-v,a or <C-v>a for Ctrl-v,a (strlen == 6 && keystring[4] == '>')
336 stuff like <M1-v> for Mod1-v (strlen == 6 && keystring[5] == '>')
337 or stuff like <M1-v>a for Mod1-v,a (strlen = 7)
339 if (
340 ((strlen(keystring) == 5 || strlen(keystring) == 6) && keystring[0] == '<' && keystring[4] == '>') ||
341 ((strlen(keystring) == 6 || strlen(keystring) == 7) && keystring[0] == '<' && keystring[5] == '>')
343 switch (toupper(keystring[1])) {
344 case 'S':
345 search_key.mask = GDK_SHIFT_MASK;
346 if (strlen(keystring) == 5) {
347 keystring[3] = toupper(keystring[3]);
348 } else {
349 keystring[3] = tolower(keystring[3]);
350 keystring[5] = toupper(keystring[5]);
352 break;
353 case 'C':
354 search_key.mask = GDK_CONTROL_MASK;
355 break;
356 case 'M':
357 search_key.mask = get_modkey(keystring[2]);
358 break;
360 if (!search_key.mask)
361 return FALSE;
362 if (strlen(keystring) == 5) {
363 search_key.key = keystring[3];
364 } else if (strlen(keystring) == 7) {
365 search_key.modkey = keystring[4];
366 search_key.key = keystring[6];
367 } else {
368 if (search_key.mask == 'S' || search_key.mask == 'C') {
369 search_key.modkey = keystring[3];
370 search_key.key = keystring[5];
371 } else {
372 search_key.key = keystring[4];
377 /* process stuff like a<S-v> for a,Shift-v or a<C-v> for a,Ctrl-v (strlen == 6)
378 or stuff like a<M1-v> for a,Mod1-v (strlen == 7)
380 if (
381 (strlen(keystring) == 6 && keystring[1] == '<' && keystring[5] == '>') ||
382 (strlen(keystring) == 7 && keystring[1] == '<' && keystring[6] == '>')
384 switch (toupper(keystring[2])) {
385 case 'S':
386 search_key.mask = GDK_SHIFT_MASK;
387 keystring[4] = toupper(keystring[4]);
388 break;
389 case 'C':
390 search_key.mask = GDK_CONTROL_MASK;
391 break;
392 case 'M':
393 search_key.mask = get_modkey(keystring[3]);
394 break;
396 if (!search_key.mask)
397 return FALSE;
398 search_key.modkey= keystring[0];
399 if (strlen(keystring) == 6) {
400 search_key.key = keystring[4];
401 } else {
402 search_key.key = keystring[5];
405 return (changemapping(&search_key, maprecord, cmd));
408 gboolean
409 process_map_line(char *line) {
410 int listlen, i;
411 char *c, *cmd;
412 my_pair.line = line;
413 c = search_word(0);
415 if (!strlen(my_pair.what))
416 return FALSE;
417 while (isspace(*c) && *c)
418 c++;
420 if (*c == ':' || *c == '=')
421 c++;
422 my_pair.line = c;
423 c = search_word(1);
424 if (!strlen(my_pair.value))
425 return FALSE;
426 listlen = LENGTH(commands);
427 for (i = 0; i < listlen; i++) {
428 /* commands is fixed size */
429 if (commands[i].cmd == NULL)
430 break;
431 if (strlen(commands[i].cmd) == strlen(my_pair.value) && strncmp(commands[i].cmd, my_pair.value, strlen(my_pair.value)) == 0) {
432 /* map to an internal symbol */
433 return process_mapping(my_pair.what, i, NULL);
436 /* if this is reached, the mapping is not for one of the internal symbol - test for command line structure */
437 if (strlen(my_pair.value) > 1 && strncmp(my_pair.value, ":", 1) == 0) {
438 /* The string begins with a colon, like a command line, but it's not _just_ a colon,
439 * i.e. increasing the pointer by one will not go 'out of bounds'.
440 * We don't actually check that the command line after the = is valid.
441 * This is user responsibility, the worst case is the new mapping simply doing nothing.
442 * Since we will pass the command to the same function which also handles the config file lines,
443 * we have to strip the colon itself (a colon counts as a commented line there - like in vim).
444 * Last, but not least, the second argument being < 0 signifies to the function that this is a
445 * command line mapping, not a mapping to an existing internal symbol. */
446 cmd = (char *)malloc(sizeof(char) * strlen(my_pair.value));
447 strncpy(cmd, (my_pair.value + 1), strlen(my_pair.value) - 1);
448 cmd[strlen(cmd)] = '\0';
449 return process_mapping(my_pair.what, -1, cmd);
451 return FALSE;
454 gboolean
455 build_taglist(const Arg *arg, FILE *f) {
456 int k = 0, in_tag = 0;
457 int t = 0, marker = 0;
458 char foundtab[MAXTAGSIZE+1];
459 while (arg->s[k]) {
460 if (!isspace(arg->s[k]) && !in_tag) {
461 in_tag = 1;
462 marker = k;
464 if (isspace(arg->s[k]) && in_tag) {
465 /* found a tag */
466 t = 0;
467 while (marker < k && t < MAXTAGSIZE) foundtab[t++] = arg->s[marker++];
468 foundtab[t] = '\0';
469 fprintf(f, " [%s]", foundtab);
470 in_tag = 0;
472 k++;
474 if (in_tag) {
475 t = 0;
476 while (marker < strlen(arg->s) && t < MAXTAGSIZE) foundtab[t++] = arg->s[marker++];
477 foundtab[t] = '\0';
478 fprintf(f, " [%s]", foundtab );
480 return TRUE;
483 void
484 set_error(const char *error) {
485 /* it should never happen that set_error is called more than once,
486 * but to avoid any potential memory leaks, we ignore any subsequent
487 * error if the current one has not been shown */
488 if (error_msg == NULL) {
489 error_msg = g_strdup_printf("%s", error);
493 void
494 give_feedback(const char *feedback)
496 Arg a = { .i = Info };
498 a.s = g_strdup_printf("%s", feedback);
499 echo(&a);
500 g_free(a.s);
503 Listelement *
504 complete_list(const char *searchfor, const int mode, Listelement *elementlist)
506 FILE *f;
507 const char *filename;
508 Listelement *candidatelist = NULL, *candidatepointer = NULL;
509 char s[255] = "", readelement[MAXTAGSIZE + 1] = "";
510 int i, t, n = 0;
512 if (mode == 2) {
513 /* open in history file */
514 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
515 } else {
516 /* open in bookmark file (for tags and bookmarks) */
517 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
519 f = fopen(filename, "r");
520 if (f == NULL) {
521 g_free((gpointer *)filename);
522 return (elementlist);
525 while (fgets(s, 254, f)) {
526 if (mode == 1) {
527 /* just tags (could be more than one per line) */
528 i = 0;
529 while (s[i] && i < 254) {
530 while (s[i] != '[' && s[i])
531 i++;
532 if (s[i] != '[')
533 continue;
534 i++;
535 t = 0;
536 while (s[i] != ']' && s[i] && t < MAXTAGSIZE)
537 readelement[t++] = s[i++];
538 readelement[t] = '\0';
539 candidatelist = add_list(readelement, candidatelist);
540 i++;
542 } else {
543 /* complete string (bookmarks & history) */
544 candidatelist = add_list(s, candidatelist);
546 candidatepointer = candidatelist;
547 while (candidatepointer != NULL) {
548 strncpy(s, candidatepointer->element, sizeof(s));
549 if (!complete_case_sensitive) {
550 g_strdown(s);
552 if (!strlen(searchfor) || strstr(s, searchfor) != NULL) {
553 /* only use string up to the first space */
554 memset(readelement, 0, MAXTAGSIZE + 1);
555 if (strchr(candidatepointer->element, ' ') != NULL) {
556 i = strcspn(candidatepointer->element, " ");
557 if (i > MAXTAGSIZE)
558 i = MAXTAGSIZE;
559 strncpy(readelement, candidatepointer->element, i);
560 } else {
561 strncpy(readelement, candidatepointer->element, MAXTAGSIZE);
563 /* in the case of URLs without title, remove the line break */
564 if (readelement[strlen(readelement) - 1] == '\n') {
565 readelement[strlen(readelement) - 1] = '\0';
567 elementlist = add_list(readelement, elementlist);
568 n = count_list(elementlist);
570 if (n >= MAX_LIST_SIZE)
571 break;
572 candidatepointer = candidatepointer->next;
574 free_list(candidatelist);
575 candidatelist = NULL;
576 if (n >= MAX_LIST_SIZE)
577 break;
579 g_free((gpointer)filename);
580 return (elementlist);
583 Listelement *
584 add_list(const char *element, Listelement *elementlist)
586 int n, i = 0;
587 Listelement *newelement, *elementpointer, *lastelement;
589 if (elementlist == NULL) { /* first element */
590 newelement = malloc(sizeof(Listelement));
591 if (newelement == NULL)
592 return (elementlist);
593 strncpy(newelement->element, element, 254);
594 newelement->next = NULL;
595 return newelement;
597 elementpointer = elementlist;
598 n = strlen(element);
600 /* check if element is already in list */
601 while (elementpointer != NULL) {
602 if (strlen(elementpointer->element) == n &&
603 strncmp(elementpointer->element, element, n) == 0)
604 return (elementlist);
605 lastelement = elementpointer;
606 elementpointer = elementpointer->next;
607 i++;
609 /* add to list */
610 newelement = malloc(sizeof(Listelement));
611 if (newelement == NULL)
612 return (elementlist);
613 lastelement->next = newelement;
614 strncpy(newelement->element, element, 254);
615 newelement->next = NULL;
616 return elementlist;
619 void
620 free_list(Listelement *elementlist)
622 Listelement *elementpointer;
624 while (elementlist != NULL) {
625 elementpointer = elementlist->next;
626 free(elementlist);
627 elementlist = elementpointer;
632 count_list(Listelement *elementlist)
634 Listelement *elementpointer = elementlist;
635 int n = 0;
637 while (elementpointer != NULL) {
638 n++;
639 elementpointer = elementpointer->next;
642 return n;
645 /* split the string at the first occurence of whitespace and return the
646 * position of the second half.
647 * Unlike strtok, the substrings can be empty and the second string is
648 * stripped of trailing and leading whitespace.
649 * Return -1 if `string' contains no whitespace */
650 static int split_string_at_whitespace(char *string)
652 int index = strcspn(string, "\n\t ");
653 if (string[index] != '\0') {
654 string[index++] = 0;
655 g_strstrip(string+index);
656 return index;
658 return -1;
661 /* return TRUE, if the string contains exactly one unescaped %s and no other
662 * printf directives */
663 static gboolean sanity_check_search_url(const char *string)
665 int was_percent_char = 0, percent_s_count = 0;
667 for (; *string; string++) {
668 switch (*string) {
669 case '%':
670 was_percent_char = !was_percent_char;
671 break;
672 case 's':
673 if (was_percent_char)
674 percent_s_count++;
675 was_percent_char = 0;
676 break;
677 default:
678 if (was_percent_char)
679 return FALSE;
680 was_percent_char = 0;
681 break;
685 return !was_percent_char && percent_s_count == 1;
688 void make_searchengines_list(Searchengine *searchengines, int length)
690 int i;
691 for (i = 0; i < length; i++, searchengines++) {
692 dynamic_searchengines = g_list_prepend(dynamic_searchengines, searchengines);
696 /* find a searchengine with a given handle and return its URI or NULL if
697 * nothing is found.
698 * The returned string is internal and must not be freed or modified. */
699 char *find_uri_for_searchengine(const char *handle)
701 GList *l;
703 if (dynamic_searchengines != NULL) {
704 for (l = dynamic_searchengines; l; l = g_list_next(l)) {
705 Searchengine *s = (Searchengine*)l->data;
706 if (!strcmp(s->handle, handle)) {
707 return s->uri;
712 return NULL;
715 enum ConfigFileError
716 read_rcfile(const char *config)
718 int t, linum = 0, index;
719 char s[255], *buffer;
720 FILE *fpin;
721 gboolean found_malformed_lines = FALSE;
722 Searchengine *new;
724 if (access(config, F_OK) != 0)
725 return FILE_NOT_FOUND;
727 fpin = fopen(config, "r");
728 if (fpin == NULL)
729 return READING_FAILED;
731 while (fgets(s, 254, fpin)) {
732 linum++;
734 * ignore lines that begin with #, / and such
736 if (!isalpha(s[0]))
737 continue;
738 t = strlen(s);
739 s[t - 1] = '\0';
740 if (strncmp(s, "searchengine", 12) == 0) {
741 buffer = (s + 12);
742 while (buffer[0] == ' ')
743 buffer++;
744 /* split line at whitespace */
745 index = split_string_at_whitespace(buffer);
746 if (index < 0 || buffer[0] == '\0' || buffer[index] == '\0'
747 || !sanity_check_search_url(buffer+index)) {
748 fprintf(stderr, "searchengines: syntax error on line %d\n", linum);
749 found_malformed_lines = TRUE;
750 continue;
752 new = malloc(sizeof(Searchengine));
753 if (new == NULL) {
754 fprintf(stderr, "Memory exhausted while loading search engines.\n");
755 exit(EXIT_FAILURE);
757 new->handle = g_strdup(buffer);
758 new->uri = g_strdup(buffer+index);
759 dynamic_searchengines = g_list_prepend(dynamic_searchengines, new);
760 } else {
761 if (!process_line(s))
762 found_malformed_lines = TRUE;
765 fclose(fpin);
766 return found_malformed_lines ? SYNTAX_ERROR : SUCCESS;