Use GList for command history.
[vimprobable2.git] / utilities.c
blobfa0db4479079f23cc679f3a2787c1b2a8307b57b
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009-2011 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 gboolean read_rcfile(const char *config)
29 int t;
30 char s[255];
31 FILE *fpin;
32 gboolean returnval = TRUE;
34 if ((fpin = fopen(config, "r")) == NULL)
35 return FALSE;
36 while (fgets(s, 254, fpin)) {
38 * ignore lines that begin with #, / and such
40 if (!isalpha(s[0]))
41 continue;
42 t = strlen(s);
43 s[t - 1] = '\0';
44 if (!process_line(s))
45 returnval = FALSE;
47 fclose(fpin);
48 return returnval;
51 void save_command_history(char *line)
53 char *c = line;
55 while (isspace(*c) && *c)
56 c++;
57 if (!strlen(c))
58 return;
60 if (COMMANDHISTSIZE <= g_list_length(commandhistory)) {
61 /* if list is too long - remove items from beginning */
62 commandhistory = g_list_delete_link(commandhistory, g_list_first(commandhistory));
64 commandhistory = g_list_append(commandhistory, g_strdup(c));
67 gboolean
68 process_save_qmark(const char *bm, WebKitWebView *webview)
70 FILE *fp;
71 const char *filename;
72 const char *uri = webkit_web_view_get_uri(webview);
73 char qmarks[10][101];
74 char buf[100];
75 int i, mark, l=0;
76 Arg a;
77 mark = -1;
78 mark = atoi(bm);
79 if ( mark < 1 || mark > 9 )
81 a.i = Error;
82 a.s = g_strdup_printf("Invalid quickmark, only 1-9");
83 echo(&a);
84 g_free(a.s);
85 return TRUE;
87 if ( uri == NULL ) return FALSE;
88 for( i=0; i < 9; ++i ) strcpy( qmarks[i], "");
90 filename = g_strdup_printf(QUICKMARK_FILE);
92 /* get current quickmarks */
94 fp = fopen(filename, "r");
95 if (fp != NULL){
96 for( i=0; i < 10; ++i ) {
97 if (feof(fp)) {
98 break;
100 fgets(buf, 100, fp);
101 l = 0;
102 while (buf[l] && l < 100 && buf[l] != '\n') {
103 qmarks[i][l]=buf[l];
104 l++;
106 qmarks[i][l]='\0';
108 fclose(fp);
111 /* save quickmarks */
112 strcpy( qmarks[mark-1], uri );
113 fp = fopen(filename, "w");
114 g_free((gpointer *)filename);
115 if (fp == NULL) return FALSE;
116 for( i=0; i < 10; ++i )
117 fprintf(fp, "%s\n", qmarks[i]);
118 fclose(fp);
119 a.i = Error;
120 a.s = g_strdup_printf("Saved as quickmark %d: %s", mark, uri);
121 echo(&a);
122 g_free(a.s);
124 return TRUE;
127 void
128 make_keyslist(void)
130 int i;
131 KeyList *ptr, *current;
133 ptr = NULL;
134 current = NULL;
135 i = 0;
136 while ( keys[i].key != 0 )
138 current = malloc(sizeof(KeyList));
139 if (current == NULL) {
140 printf("Not enough memory\n");
141 exit(-1);
143 current->Element = keys[i];
144 current->next = NULL;
145 if (keylistroot == NULL) keylistroot = current;
146 if (ptr != NULL) ptr->next = current;
147 ptr = current;
148 i++;
152 gboolean
153 parse_colour(char *color) {
154 char goodcolor[8];
155 int colorlen;
157 colorlen = (int)strlen(color);
159 goodcolor[0] = '#';
160 goodcolor[7] = '\0';
162 /* help the user a bit by making string like
163 #a10 and strings like ffffff full 6digit
164 strings with # in front :)
167 if (color[0] == '#') {
168 switch (colorlen) {
169 case 7:
170 strncpy(goodcolor, color, 7);
171 break;
172 case 4:
173 goodcolor[1] = color[1];
174 goodcolor[2] = color[1];
175 goodcolor[3] = color[2];
176 goodcolor[4] = color[2];
177 goodcolor[5] = color[3];
178 goodcolor[6] = color[3];
179 break;
180 case 2:
181 goodcolor[1] = color[1];
182 goodcolor[2] = color[1];
183 goodcolor[3] = color[1];
184 goodcolor[4] = color[1];
185 goodcolor[5] = color[1];
186 goodcolor[6] = color[1];
187 break;
189 } else {
190 switch (colorlen) {
191 case 6:
192 strncpy(&goodcolor[1], color, 6);
193 break;
194 case 3:
195 goodcolor[1] = color[0];
196 goodcolor[2] = color[0];
197 goodcolor[3] = color[1];
198 goodcolor[4] = color[1];
199 goodcolor[5] = color[2];
200 goodcolor[6] = color[2];
201 break;
202 case 1:
203 goodcolor[1] = color[0];
204 goodcolor[2] = color[0];
205 goodcolor[3] = color[0];
206 goodcolor[4] = color[0];
207 goodcolor[5] = color[0];
208 goodcolor[6] = color[0];
209 break;
213 if (strlen (goodcolor) != 7) {
214 return FALSE;
215 } else {
216 strncpy(color, goodcolor, 8);
217 return TRUE;
221 gboolean
222 process_line_arg(const Arg *arg) {
223 return process_line(arg->s);
226 gboolean
227 changemapping(Key *search_key, int maprecord, char *cmd) {
228 KeyList *current, *newkey;
229 Arg a = { .s = cmd };
231 /* sanity check */
232 if (maprecord < 0 && cmd == NULL) {
233 /* possible states:
234 * - maprecord >= 0 && cmd == NULL: mapping to internal symbol
235 * - maprecord < 0 && cmd != NULL: mapping to command line
236 * - maprecord >= 0 && cmd != NULL: cmd will be ignored, treated as mapping to internal symbol
237 * - anything else (gets in here): an error, hence we return FALSE */
238 return FALSE;
241 current = keylistroot;
243 if (current)
244 while (current->next != NULL) {
245 if (
246 current->Element.mask == search_key->mask &&
247 current->Element.modkey == search_key->modkey &&
248 current->Element.key == search_key->key
250 if (maprecord >= 0) {
251 /* mapping to an internal signal */
252 current->Element.func = commands[maprecord].func;
253 current->Element.arg = commands[maprecord].arg;
254 } else {
255 /* mapping to a command line */
256 current->Element.func = process_line_arg;
257 current->Element.arg = a;
259 return TRUE;
261 current = current->next;
263 newkey = malloc(sizeof(KeyList));
264 if (newkey == NULL) {
265 printf("Not enough memory\n");
266 exit (-1);
268 newkey->Element.mask = search_key->mask;
269 newkey->Element.modkey = search_key->modkey;
270 newkey->Element.key = search_key->key;
271 if (maprecord >= 0) {
272 /* mapping to an internal signal */
273 newkey->Element.func = commands[maprecord].func;
274 newkey->Element.arg = commands[maprecord].arg;
275 } else {
276 /* mapping to a command line */
277 newkey->Element.func = process_line_arg;
278 newkey->Element.arg = a;
280 add_modkeys(newkey->Element.modkey);
282 newkey->next = NULL;
284 if (keylistroot == NULL) keylistroot = newkey;
286 if (current != NULL) current->next = newkey;
288 return TRUE;
291 void add_modkeys(char key)
293 unsigned int k, len;
294 extern char *modkeys;
295 len = strlen( modkeys );
296 while (k < len )
298 if ( modkeys[k] == key ) return;
299 k++;
301 modkeys = realloc(modkeys, len + 1);
302 modkeys[len++] = key;
303 modkeys[len] = '\0';
306 gboolean
307 mappings(const Arg *arg) {
308 char line[255];
310 if (!arg->s) {
311 set_error("Missing argument.");
312 return FALSE;
314 strncpy(line, arg->s, 254);
315 if (process_map_line(line))
316 return TRUE;
317 else {
318 set_error("Invalid mapping.");
319 return FALSE;
323 int
324 get_modkey(char key) {
325 switch (key) {
326 case '1':
327 return GDK_MOD1_MASK;
328 case '2':
329 return GDK_MOD2_MASK;
330 case '3':
331 return GDK_MOD3_MASK;
332 case '4':
333 return GDK_MOD4_MASK;
334 case '5':
335 return GDK_MOD5_MASK;
336 default:
337 return FALSE;
341 gboolean
342 process_mapping(char *keystring, int maprecord, char *cmd) {
343 Key search_key;
345 search_key.mask = 0;
346 search_key.modkey = 0;
347 search_key.key = 0;
349 if (strlen(keystring) == 1) {
350 search_key.key = keystring[0];
353 if (strlen(keystring) == 2) {
354 search_key.modkey= keystring[0];
355 search_key.key = keystring[1];
358 /* process stuff like <S-v> for Shift-v or <C-v> for Ctrl-v (strlen == 5),
359 stuff like <S-v>a for Shift-v,a or <C-v>a for Ctrl-v,a (strlen == 6 && keystring[4] == '>')
360 stuff like <M1-v> for Mod1-v (strlen == 6 && keystring[5] == '>')
361 or stuff like <M1-v>a for Mod1-v,a (strlen = 7)
363 if (
364 ((strlen(keystring) == 5 || strlen(keystring) == 6) && keystring[0] == '<' && keystring[4] == '>') ||
365 ((strlen(keystring) == 6 || strlen(keystring) == 7) && keystring[0] == '<' && keystring[5] == '>')
367 switch (toupper(keystring[1])) {
368 case 'S':
369 search_key.mask = GDK_SHIFT_MASK;
370 if (strlen(keystring) == 5) {
371 keystring[3] = toupper(keystring[3]);
372 } else {
373 keystring[3] = tolower(keystring[3]);
374 keystring[5] = toupper(keystring[5]);
376 break;
377 case 'C':
378 search_key.mask = GDK_CONTROL_MASK;
379 break;
380 case 'M':
381 search_key.mask = get_modkey(keystring[2]);
382 break;
384 if (!search_key.mask)
385 return FALSE;
386 if (strlen(keystring) == 5) {
387 search_key.key = keystring[3];
388 } else if (strlen(keystring) == 7) {
389 search_key.modkey = keystring[4];
390 search_key.key = keystring[6];
391 } else {
392 if (search_key.mask == 'S' || search_key.mask == 'C') {
393 search_key.modkey = keystring[3];
394 search_key.key = keystring[5];
395 } else {
396 search_key.key = keystring[4];
401 /* process stuff like a<S-v> for a,Shift-v or a<C-v> for a,Ctrl-v (strlen == 6)
402 or stuff like a<M1-v> for a,Mod1-v (strlen == 7)
404 if (
405 (strlen(keystring) == 6 && keystring[1] == '<' && keystring[5] == '>') ||
406 (strlen(keystring) == 7 && keystring[1] == '<' && keystring[6] == '>')
408 switch (toupper(keystring[2])) {
409 case 'S':
410 search_key.mask = GDK_SHIFT_MASK;
411 keystring[4] = toupper(keystring[4]);
412 break;
413 case 'C':
414 search_key.mask = GDK_CONTROL_MASK;
415 break;
416 case 'M':
417 search_key.mask = get_modkey(keystring[3]);
418 break;
420 if (!search_key.mask)
421 return FALSE;
422 search_key.modkey= keystring[0];
423 if (strlen(keystring) == 6) {
424 search_key.key = keystring[4];
425 } else {
426 search_key.key = keystring[5];
429 return (changemapping(&search_key, maprecord, cmd));
432 gboolean
433 process_map_line(char *line) {
434 int listlen, i;
435 char *c, *cmd;
436 my_pair.line = line;
437 c = search_word(0);
439 if (!strlen(my_pair.what))
440 return FALSE;
441 while (isspace(*c) && *c)
442 c++;
444 if (*c == ':' || *c == '=')
445 c++;
446 my_pair.line = c;
447 c = search_word(1);
448 if (!strlen(my_pair.value))
449 return FALSE;
450 listlen = LENGTH(commands);
451 for (i = 0; i < listlen; i++) {
452 /* commands is fixed size */
453 if (commands[i].cmd == NULL)
454 break;
455 if (strlen(commands[i].cmd) == strlen(my_pair.value) && strncmp(commands[i].cmd, my_pair.value, strlen(my_pair.value)) == 0) {
456 /* map to an internal symbol */
457 return process_mapping(my_pair.what, i, NULL);
460 /* if this is reached, the mapping is not for one of the internal symbol - test for command line structure */
461 if (strlen(my_pair.value) > 1 && strncmp(my_pair.value, ":", 1) == 0) {
462 /* The string begins with a colon, like a command line, but it's not _just_ a colon,
463 * i.e. increasing the pointer by one will not go 'out of bounds'.
464 * We don't actually check that the command line after the = is valid.
465 * This is user responsibility, the worst case is the new mapping simply doing nothing.
466 * Since we will pass the command to the same function which also handles the config file lines,
467 * we have to strip the colon itself (a colon counts as a commented line there - like in vim).
468 * Last, but not least, the second argument being < 0 signifies to the function that this is a
469 * command line mapping, not a mapping to an existing internal symbol. */
470 cmd = (char *)malloc(sizeof(char) * strlen(my_pair.value));
471 strncpy(cmd, (my_pair.value + 1), strlen(my_pair.value) - 1);
472 cmd[strlen(cmd)] = '\0';
473 return process_mapping(my_pair.what, -1, cmd);
475 return FALSE;
478 gboolean
479 build_taglist(const Arg *arg, FILE *f) {
480 int k = 0, in_tag = 0;
481 int t = 0, marker = 0;
482 char foundtab[MAXTAGSIZE+1];
483 while (arg->s[k]) {
484 if (!isspace(arg->s[k]) && !in_tag) {
485 in_tag = 1;
486 marker = k;
488 if (isspace(arg->s[k]) && in_tag) {
489 /* found a tag */
490 t = 0;
491 while (marker < k && t < MAXTAGSIZE) foundtab[t++] = arg->s[marker++];
492 foundtab[t] = '\0';
493 fprintf(f, " [%s]", foundtab);
494 in_tag = 0;
496 k++;
498 if (in_tag) {
499 t = 0;
500 while (marker < strlen(arg->s) && t < MAXTAGSIZE) foundtab[t++] = arg->s[marker++];
501 foundtab[t] = '\0';
502 fprintf(f, " [%s]", foundtab );
504 return TRUE;
507 void
508 set_error(const char *error) {
509 /* it should never happen that set_error is called more than once,
510 * but to avoid any potential memory leaks, we ignore any subsequent
511 * error if the current one has not been shown */
512 if (error_msg == NULL) {
513 error_msg = g_strdup_printf("%s", error);
517 void
518 give_feedback(const char *feedback)
520 Arg a = { .i = Info };
522 a.s = g_strdup_printf("%s", feedback);
523 echo(&a);
524 g_free(a.s);
527 Listelement *
528 complete_list(const char *searchfor, const int mode, Listelement *elementlist)
530 FILE *f;
531 const char *filename;
532 Listelement *candidatelist = NULL, *candidatepointer = NULL;
533 char s[255] = "", readelement[MAXTAGSIZE + 1] = "";
534 int i, t, n = 0;
536 if (mode == 2) {
537 /* open in history file */
538 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
539 } else {
540 /* open in bookmark file (for tags and bookmarks) */
541 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
543 f = fopen(filename, "r");
544 if (f == NULL) {
545 g_free((gpointer *)filename);
546 return (elementlist);
549 while (fgets(s, 254, f)) {
550 if (mode == 1) {
551 /* just tags (could be more than one per line) */
552 i = 0;
553 while (s[i] && i < 254) {
554 while (s[i] != '[' && s[i])
555 i++;
556 if (s[i] != '[')
557 continue;
558 i++;
559 t = 0;
560 while (s[i] != ']' && s[i] && t < MAXTAGSIZE)
561 readelement[t++] = s[i++];
562 readelement[t] = '\0';
563 candidatelist = add_list(readelement, candidatelist);
564 i++;
566 } else {
567 /* complete string (bookmarks & history) */
568 candidatelist = add_list(s, candidatelist);
570 candidatepointer = candidatelist;
571 while (candidatepointer != NULL) {
572 strncpy(s, candidatepointer->element, sizeof(s));
573 if (!complete_case_sensitive) {
574 g_strdown(s);
576 if (!strlen(searchfor) || strstr(s, searchfor) != NULL) {
577 /* only use string up to the first space */
578 memset(readelement, 0, MAXTAGSIZE + 1);
579 if (strchr(candidatepointer->element, ' ') != NULL) {
580 i = strcspn(candidatepointer->element, " ");
581 if (i > MAXTAGSIZE)
582 i = MAXTAGSIZE;
583 strncpy(readelement, candidatepointer->element, i);
584 } else {
585 strncpy(readelement, candidatepointer->element, MAXTAGSIZE);
587 /* in the case of URLs without title, remove the line break */
588 if (readelement[strlen(readelement) - 1] == '\n') {
589 readelement[strlen(readelement) - 1] = '\0';
591 elementlist = add_list(readelement, elementlist);
592 n = count_list(elementlist);
594 if (n >= MAX_LIST_SIZE)
595 break;
596 candidatepointer = candidatepointer->next;
598 free_list(candidatelist);
599 candidatelist = NULL;
600 if (n >= MAX_LIST_SIZE)
601 break;
603 g_free((gpointer)filename);
604 return (elementlist);
607 Listelement *
608 add_list(const char *element, Listelement *elementlist)
610 int n, i = 0;
611 Listelement *newelement, *elementpointer, *lastelement;
613 if (elementlist == NULL) { /* first element */
614 newelement = malloc(sizeof(Listelement));
615 if (newelement == NULL)
616 return (elementlist);
617 strncpy(newelement->element, element, 254);
618 newelement->next = NULL;
619 return newelement;
621 elementpointer = elementlist;
622 n = strlen(element);
624 /* check if element is already in list */
625 while (elementpointer != NULL) {
626 if (strlen(elementpointer->element) == n &&
627 strncmp(elementpointer->element, element, n) == 0)
628 return (elementlist);
629 lastelement = elementpointer;
630 elementpointer = elementpointer->next;
631 i++;
633 /* add to list */
634 newelement = malloc(sizeof(Listelement));
635 if (newelement == NULL)
636 return (elementlist);
637 lastelement->next = newelement;
638 strncpy(newelement->element, element, 254);
639 newelement->next = NULL;
640 return elementlist;
643 void
644 free_list(Listelement *elementlist)
646 Listelement *elementpointer;
648 while (elementlist != NULL) {
649 elementpointer = elementlist->next;
650 free(elementlist);
651 elementlist = elementpointer;
656 count_list(Listelement *elementlist)
658 Listelement *elementpointer = elementlist;
659 int n = 0;
661 while (elementpointer != NULL) {
662 n++;
663 elementpointer = elementpointer->next;
666 return n;
669 /* split the string at the first occurence of whitespace and return the
670 * position of the second half.
671 * Unlike strtok, the substrings can be empty and the second string is
672 * stripped of trailing and leading whitespace.
673 * Return -1 if `string' contains no whitespace */
674 static int split_string_at_whitespace(char *string)
676 int index = strcspn(string, "\n\t ");
677 if (string[index] != '\0') {
678 string[index++] = 0;
679 g_strstrip(string+index);
680 return index;
682 return -1;
685 /* return TRUE, if the string contains exactly one unescaped %s and no other
686 * printf directives */
687 static gboolean sanity_check_search_url(const char *string)
689 int was_percent_char = 0, percent_s_count = 0;
691 for (; *string; string++) {
692 switch (*string) {
693 case '%':
694 was_percent_char = !was_percent_char;
695 break;
696 case 's':
697 if (was_percent_char)
698 percent_s_count++;
699 was_percent_char = 0;
700 break;
701 default:
702 if (was_percent_char)
703 return FALSE;
704 was_percent_char = 0;
705 break;
709 return !was_percent_char && percent_s_count == 1;
712 enum ConfigFileError
713 read_searchengines(const char *filename)
715 FILE *file;
716 char buffer[BUFFERSIZE], c;
717 int linum = 0, index;
718 gboolean found_malformed_lines = FALSE;
719 Searchengine *new;
721 if (access(filename, F_OK) != 0)
722 return FILE_NOT_FOUND;
724 file = fopen(filename, "r");
725 if (file == NULL)
726 return READING_FAILED;
728 while (fgets(buffer, BUFFERSIZE, file)) {
729 linum++;
731 /* skip empty lines */
732 if (!strcmp(buffer, "\n")) continue;
734 /* skip too long lines */
735 if (buffer[strlen(buffer)-1] != '\n') {
736 c = getc(file);
737 if (c != EOF) { /* this is not the last line */
738 while ((c=getc(file)) != EOF && c != '\n');
739 fprintf(stderr, "searchengines: syntax error on line %d\n", linum);
740 found_malformed_lines = TRUE;
741 continue;
745 /* split line at whitespace */
746 index = split_string_at_whitespace(buffer);
748 if (index < 0 || buffer[0] == '\0' || buffer[index] == '\0'
749 || !sanity_check_search_url(buffer+index)) {
750 fprintf(stderr, "searchengines: syntax error on line %d\n", linum);
751 found_malformed_lines = TRUE;
752 continue;
755 new = malloc(sizeof(Searchengine));
756 if (new == NULL) {
757 fprintf(stderr, "Memory exhausted while loading search engines.\n");
758 exit(EXIT_FAILURE);
761 new->handle = g_strdup(buffer);
762 new->uri = g_strdup(buffer+index);
764 dynamic_searchengines = g_list_prepend(dynamic_searchengines, new);
767 if (ferror(file)) {
768 fclose(file);
769 return READING_FAILED;
772 fclose(file);
774 return found_malformed_lines ? SYNTAX_ERROR : SUCCESS;
777 void make_searchengines_list(Searchengine *searchengines, int length)
779 int i;
780 for (i = 0; i < length; i++, searchengines++) {
781 dynamic_searchengines = g_list_prepend(dynamic_searchengines, searchengines);
785 /* find a searchengine with a given handle and return its URI or NULL if
786 * nothing is found.
787 * The returned string is internal and must not be freed or modified. */
788 char *find_uri_for_searchengine(const char *handle)
790 GList *l;
792 if (dynamic_searchengines != NULL) {
793 for (l = dynamic_searchengines; l; l = g_list_next(l)) {
794 Searchengine *s = (Searchengine*)l->data;
795 if (!strcmp(s->handle, handle)) {
796 return s->uri;
801 return NULL;