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
8 (c) 2015 Matthew Carter
13 #include "vimprobable.h"
15 #include "utilities.h"
18 extern Command commands
[COMMANDSIZE
];
20 extern gboolean complete_case_sensitive
;
21 static GList
*dynamic_searchengines
= NULL
, *dynamic_uri_handlers
= NULL
;
22 static int command_histsize
= 50;
23 int fuzzy_style_match(char *haystack
, char *needle
);
25 void add_modkeys(char key
);
27 static void clear_history(int maxlen
)
29 State
*s
= &client
.state
;
30 GList
*l
= s
->commandhistory
;
31 int len
= g_list_length(s
->commandhistory
);
33 while (l
!= NULL
&& len
> maxlen
) {
34 GList
*next
= l
->next
;
35 s
->commandhistory
= g_list_delete_link(s
->commandhistory
, l
);
41 void save_command_history(char *line
)
43 State
*s
= &client
.state
;
46 while (isspace(*c
) && *c
)
51 if (command_histsize
> 0) {
52 if (command_histsize
<= g_list_length(s
->commandhistory
)) {
53 clear_history(command_histsize
- 1);
55 s
->commandhistory
= g_list_append(s
->commandhistory
, g_strdup(c
));
59 gboolean
clear(const Arg
*arg
)
65 void set_command_history_len(int len
)
67 command_histsize
= len
;
68 clear_history(command_histsize
);
72 process_save_qmark(const char *bm
, WebKitWebView
*webview
)
76 const char *uri
= webkit_web_view_get_uri(webview
);
82 if ( mark
< 1 || mark
> 9 ) {
83 echo_message(Error
, "Invalid quickmark, only 1-9");
86 if ( uri
== NULL
) return FALSE
;
87 for( i
=0; i
< 9; ++i
) strcpy( qmarks
[i
], "");
89 filename
= g_strdup_printf(QUICKMARK_FILE
);
91 /* get current quickmarks */
93 fp
= fopen(filename
, "r");
95 for( i
=0; i
< 10; ++i
) {
101 while (buf
[l
] && l
< 100 && buf
[l
] != '\n') {
110 /* save quickmarks */
111 strcpy( qmarks
[mark
-1], uri
);
112 fp
= fopen(filename
, "w");
113 g_free((gpointer
*)filename
);
114 if (fp
== NULL
) return FALSE
;
115 for( i
=0; i
< 10; ++i
)
116 fprintf(fp
, "%s\n", qmarks
[i
]);
118 echo_message(Error
, "Saved as quickmark %d: %s", mark
, uri
);
127 KeyList
*ptr
, *current
;
132 while ( keys
[i
].key
!= 0 )
134 current
= malloc(sizeof(KeyList
));
135 if (current
== NULL
) {
136 printf("Not enough memory\n");
139 current
->Element
= keys
[i
];
140 current
->next
= NULL
;
141 if (client
.config
.keylistroot
== NULL
)
142 client
.config
.keylistroot
= current
;
151 parse_colour(char *color
) {
155 colorlen
= (int)strlen(color
);
160 /* help the user a bit by making string like
161 #a10 and strings like ffffff full 6digit
162 strings with # in front :)
165 if (color
[0] == '#') {
168 strncpy(goodcolor
, color
, 7);
171 goodcolor
[1] = color
[1];
172 goodcolor
[2] = color
[1];
173 goodcolor
[3] = color
[2];
174 goodcolor
[4] = color
[2];
175 goodcolor
[5] = color
[3];
176 goodcolor
[6] = color
[3];
179 goodcolor
[1] = color
[1];
180 goodcolor
[2] = color
[1];
181 goodcolor
[3] = color
[1];
182 goodcolor
[4] = color
[1];
183 goodcolor
[5] = color
[1];
184 goodcolor
[6] = color
[1];
190 strncpy(&goodcolor
[1], color
, 6);
193 goodcolor
[1] = color
[0];
194 goodcolor
[2] = color
[0];
195 goodcolor
[3] = color
[1];
196 goodcolor
[4] = color
[1];
197 goodcolor
[5] = color
[2];
198 goodcolor
[6] = color
[2];
201 goodcolor
[1] = color
[0];
202 goodcolor
[2] = color
[0];
203 goodcolor
[3] = color
[0];
204 goodcolor
[4] = color
[0];
205 goodcolor
[5] = color
[0];
206 goodcolor
[6] = color
[0];
211 if (strlen (goodcolor
) != 7) {
214 strncpy(color
, goodcolor
, 8);
220 process_line_arg(const Arg
*arg
) {
221 return process_line(arg
->s
);
225 changemapping(Key
*search_key
, int maprecord
, char *cmd
) {
226 KeyList
*current
, *newkey
, *prev
= NULL
;
227 Arg a
= { .s
= cmd
};
230 if (maprecord
< 0 && cmd
== NULL
) {
232 * - maprecord >= 0 && cmd == NULL: mapping to internal symbol
233 * - maprecord < 0 && cmd != NULL: mapping to command line
234 * - maprecord >= 0 && cmd != NULL: cmd will be ignored, treated as mapping to internal symbol
235 * - anything else (gets in here): an error, hence we return FALSE */
239 current
= client
.config
.keylistroot
;
241 while (current
!= NULL
) {
243 current
->Element
.mask
== search_key
->mask
&&
244 current
->Element
.modkey
== search_key
->modkey
&&
245 current
->Element
.key
== search_key
->key
247 if (maprecord
>= 0) {
248 /* mapping to an internal signal */
249 current
->Element
.func
= commands
[maprecord
].func
;
250 current
->Element
.arg
= commands
[maprecord
].arg
;
252 /* mapping to a command line */
253 current
->Element
.func
= process_line_arg
;
254 current
->Element
.arg
= a
;
259 current
= current
->next
;
262 newkey
= malloc(sizeof(KeyList
));
263 if (newkey
== NULL
) {
264 printf("Not enough memory\n");
267 newkey
->Element
.mask
= search_key
->mask
;
268 newkey
->Element
.modkey
= search_key
->modkey
;
269 newkey
->Element
.key
= search_key
->key
;
270 if (maprecord
>= 0) {
271 /* mapping to an internal signal */
272 newkey
->Element
.func
= commands
[maprecord
].func
;
273 newkey
->Element
.arg
= commands
[maprecord
].arg
;
275 /* mapping to a command line */
276 newkey
->Element
.func
= process_line_arg
;
277 newkey
->Element
.arg
= a
;
279 add_modkeys(newkey
->Element
.modkey
);
283 if (client
.config
.keylistroot
== NULL
)
284 client
.config
.keylistroot
= newkey
;
292 void add_modkeys(char key
)
295 len
= strlen(client
.config
.modkeys
);
297 if (client
.config
.modkeys
[k
] == key
) return;
300 client
.config
.modkeys
= realloc(client
.config
.modkeys
, len
+ 2);
301 client
.config
.modkeys
[len
] = key
;
302 client
.config
.modkeys
[len
+1] = '\0';
306 mappings(const Arg
*arg
) {
310 set_error("Missing argument.");
313 strncpy(line
, arg
->s
, 254);
314 if (process_map_line(line
))
317 set_error("Invalid mapping.");
323 get_modkey(char key
) {
326 return GDK_MOD1_MASK
;
328 return GDK_MOD2_MASK
;
330 return GDK_MOD3_MASK
;
332 return GDK_MOD4_MASK
;
334 return GDK_MOD5_MASK
;
341 process_colon(char *alias
, char *cmd
) {
344 /* mapping one colon alias to another colon command */
345 a
= malloc(sizeof(Alias
));
347 fprintf(stderr
, "Memory exhausted while saving new command.\n");
350 a
->alias
= malloc(strlen(alias
)*sizeof(char));
351 memset(a
->alias
, 0, strlen(alias
));
352 strncpy(a
->alias
, (alias
+ 1), strlen(alias
) - 1);
354 client
.config
.colon_aliases
= g_list_prepend(client
.config
.colon_aliases
, a
);
359 process_mapping(char *keystring
, int maprecord
, char *cmd
) {
363 search_key
.modkey
= 0;
366 if (strlen(keystring
) == 1) {
367 search_key
.key
= keystring
[0];
370 if (strlen(keystring
) == 2) {
371 search_key
.modkey
= keystring
[0];
372 search_key
.key
= keystring
[1];
375 /* process stuff like <S-v> for Shift-v or <C-v> for Ctrl-v (strlen == 5),
376 stuff like <S-v>a for Shift-v,a or <C-v>a for Ctrl-v,a (strlen == 6 && keystring[4] == '>')
377 stuff like <M1-v> for Mod1-v (strlen == 6 && keystring[5] == '>')
378 or stuff like <M1-v>a for Mod1-v,a (strlen = 7)
381 ((strlen(keystring
) == 5 || strlen(keystring
) == 6) && keystring
[0] == '<' && keystring
[4] == '>') ||
382 ((strlen(keystring
) == 6 || strlen(keystring
) == 7) && keystring
[0] == '<' && keystring
[5] == '>')
384 switch (toupper(keystring
[1])) {
386 search_key
.mask
= GDK_SHIFT_MASK
;
387 if (strlen(keystring
) == 5) {
388 keystring
[3] = toupper(keystring
[3]);
390 keystring
[3] = tolower(keystring
[3]);
391 keystring
[5] = toupper(keystring
[5]);
395 search_key
.mask
= GDK_CONTROL_MASK
;
398 search_key
.mask
= get_modkey(keystring
[2]);
401 if (!search_key
.mask
)
403 if (strlen(keystring
) == 5) {
404 search_key
.key
= keystring
[3];
405 } else if (strlen(keystring
) == 7) {
406 search_key
.modkey
= keystring
[4];
407 search_key
.key
= keystring
[6];
409 if (search_key
.mask
== 'S' || search_key
.mask
== 'C') {
410 search_key
.modkey
= keystring
[3];
411 search_key
.key
= keystring
[5];
413 search_key
.key
= keystring
[4];
418 /* process stuff like a<S-v> for a,Shift-v or a<C-v> for a,Ctrl-v (strlen == 6)
419 or stuff like a<M1-v> for a,Mod1-v (strlen == 7)
422 (strlen(keystring
) == 6 && keystring
[1] == '<' && keystring
[5] == '>') ||
423 (strlen(keystring
) == 7 && keystring
[1] == '<' && keystring
[6] == '>')
425 switch (toupper(keystring
[2])) {
427 search_key
.mask
= GDK_SHIFT_MASK
;
428 keystring
[4] = toupper(keystring
[4]);
431 search_key
.mask
= GDK_CONTROL_MASK
;
434 search_key
.mask
= get_modkey(keystring
[3]);
437 if (!search_key
.mask
)
439 search_key
.modkey
= keystring
[0];
440 if (strlen(keystring
) == 6) {
441 search_key
.key
= keystring
[4];
443 search_key
.key
= keystring
[5];
446 return (changemapping(&search_key
, maprecord
, cmd
));
450 process_map_line(char *line
) {
456 if (!strlen(my_pair
.what
))
458 while (isspace(*c
) && *c
)
461 if (*c
== ':' || *c
== '=')
465 if (!strlen(my_pair
.value
))
467 listlen
= LENGTH(commands
);
468 for (i
= 0; i
< listlen
; i
++) {
469 /* commands is fixed size */
470 if (commands
[i
].cmd
== NULL
)
472 if (strlen(commands
[i
].cmd
) == strlen(my_pair
.value
) && strncmp(commands
[i
].cmd
, my_pair
.value
, strlen(my_pair
.value
)) == 0) {
473 /* map to an internal symbol */
474 return process_mapping(my_pair
.what
, i
, NULL
);
477 /* if this is reached, the mapping is not for one of the internal symbol - test for command line structure */
478 if (strlen(my_pair
.value
) > 1 && strncmp(my_pair
.value
, ":", 1) == 0) {
479 /* The string begins with a colon, like a command line, but it's not _just_ a colon,
480 * i.e. increasing the pointer by one will not go 'out of bounds'.
481 * We don't actually check that the command line after the = is valid.
482 * This is user responsibility, the worst case is the new mapping simply doing nothing.
483 * Since we will pass the command to the same function which also handles the config file lines,
484 * we have to strip the colon itself (a colon counts as a commented line there - like in vim).
485 * Last, but not least, the second argument being < 0 signifies to the function that this is a
486 * command line mapping, not a mapping to an existing internal symbol. */
487 cmd
= (char *)malloc(sizeof(char) * strlen(my_pair
.value
));
488 memset(cmd
, 0, strlen(my_pair
.value
));
489 strncpy(cmd
, (my_pair
.value
+ 1), strlen(my_pair
.value
) - 1);
490 if (strncmp(my_pair
.what
, ":", 1) == 0) {
491 /* map new colon command */
492 return process_colon(my_pair
.what
, cmd
);
494 /* map key binding */
495 return process_mapping(my_pair
.what
, -1, cmd
);
502 build_taglist(const Arg
*arg
, FILE *f
) {
503 int k
= 0, in_tag
= 0;
504 int t
= 0, marker
= 0;
505 char foundtab
[MAXTAGSIZE
+1];
507 if (!isspace(arg
->s
[k
]) && !in_tag
) {
511 if (isspace(arg
->s
[k
]) && in_tag
) {
514 while (marker
< k
&& t
< MAXTAGSIZE
) foundtab
[t
++] = arg
->s
[marker
++];
516 fprintf(f
, " [%s]", foundtab
);
523 while (marker
< strlen(arg
->s
) && t
< MAXTAGSIZE
) foundtab
[t
++] = arg
->s
[marker
++];
525 fprintf(f
, " [%s]", foundtab
);
531 set_error(const char *error
) {
532 /* it should never happen that set_error is called more than once,
533 * but to avoid any potential memory leaks, we ignore any subsequent
534 * error if the current one has not been shown */
535 if (client
.state
.error_msg
== NULL
) {
536 client
.state
.error_msg
= g_strdup_printf("%s", error
);
541 echo_message(const MessageType type
, const char *format
, ...)
546 va_start(ap
, format
);
548 a
.s
= g_strdup_vprintf(format
, ap
);
555 fuzzy_style_match (char *haystack
, char *needle
)
567 next
= strstr(subs
+ 1 , " ");
569 word
[strlen(subs
) - (next
!= NULL
? strlen(next
) : 0)] = '\0';
570 if (strstr(haystack
, word
+ iter
) == NULL
) {
574 } while ((subs
= strstr(subs
+ 1, " ")) != NULL
);
580 complete_list(const char *searchfor
, const int mode
, Listelement
*elementlist
)
583 const char *filename
;
584 Listelement
*candidatelist
= NULL
, *candidatepointer
= NULL
;
585 char s
[255] = "", readelement
[MAXTAGSIZE
+ 1] = "";
589 /* open in history file */
590 filename
= g_strdup_printf(HISTORY_STORAGE_FILENAME
);
592 /* open in bookmark file (for tags and bookmarks) */
593 filename
= g_strdup_printf(BOOKMARKS_STORAGE_FILENAME
);
595 f
= fopen(filename
, "r");
597 g_free((gpointer
*)filename
);
598 return (elementlist
);
601 while (fgets(s
, 254, f
)) {
603 /* just tags (could be more than one per line) */
605 while (s
[i
] && i
< 254) {
606 while (s
[i
] != '[' && s
[i
])
612 while (s
[i
] != ']' && s
[i
] && t
< MAXTAGSIZE
)
613 readelement
[t
++] = s
[i
++];
614 readelement
[t
] = '\0';
615 candidatelist
= add_list(readelement
, candidatelist
);
619 /* complete string (bookmarks & history) */
620 candidatelist
= add_list(s
, candidatelist
);
622 candidatepointer
= candidatelist
;
623 while (candidatepointer
!= NULL
) {
624 strncpy(s
, candidatepointer
->element
, sizeof(s
));
625 if (!complete_case_sensitive
) {
626 g_utf8_strdown(s
, 255);
628 if (!strlen(searchfor
) || fuzzy_style_match(s
, (char *) searchfor
)) {
629 /* only use string up to the first space */
630 memset(readelement
, 0, MAXTAGSIZE
+ 1);
631 if (strchr(candidatepointer
->element
, ' ') != NULL
) {
632 i
= strcspn(candidatepointer
->element
, " ");
635 strncpy(readelement
, candidatepointer
->element
, i
);
637 strncpy(readelement
, candidatepointer
->element
, MAXTAGSIZE
);
639 /* in the case of URLs without title, remove the line break */
640 if (readelement
[strlen(readelement
) - 1] == '\n') {
641 readelement
[strlen(readelement
) - 1] = '\0';
643 elementlist
= add_list(readelement
, elementlist
);
644 n
= count_list(elementlist
);
646 if (n
>= MAX_LIST_SIZE
)
648 candidatepointer
= candidatepointer
->next
;
650 free_list(candidatelist
);
651 candidatelist
= NULL
;
652 if (n
>= MAX_LIST_SIZE
)
655 g_free((gpointer
)filename
);
656 return (elementlist
);
660 add_list(const char *element
, Listelement
*elementlist
)
663 Listelement
*newelement
, *elementpointer
, *lastelement
;
665 if (elementlist
== NULL
) { /* first element */
666 newelement
= malloc(sizeof(Listelement
));
667 if (newelement
== NULL
)
668 return (elementlist
);
669 strncpy(newelement
->element
, element
, 254);
670 newelement
->next
= NULL
;
673 elementpointer
= elementlist
;
676 /* check if element is already in list */
677 while (elementpointer
!= NULL
) {
678 if (strlen(elementpointer
->element
) == n
&&
679 strncmp(elementpointer
->element
, element
, n
) == 0)
680 return (elementlist
);
681 lastelement
= elementpointer
;
682 elementpointer
= elementpointer
->next
;
686 newelement
= malloc(sizeof(Listelement
));
687 if (newelement
== NULL
)
688 return (elementlist
);
689 lastelement
->next
= newelement
;
690 strncpy(newelement
->element
, element
, 254);
691 newelement
->next
= NULL
;
696 free_list(Listelement
*elementlist
)
698 Listelement
*elementpointer
;
700 while (elementlist
!= NULL
) {
701 elementpointer
= elementlist
->next
;
703 elementlist
= elementpointer
;
708 count_list(Listelement
*elementlist
)
710 Listelement
*elementpointer
= elementlist
;
713 while (elementpointer
!= NULL
) {
715 elementpointer
= elementpointer
->next
;
721 /* split the string at the first occurence of whitespace and return the
722 * position of the second half.
723 * Unlike strtok, the substrings can be empty and the second string is
724 * stripped of trailing and leading whitespace.
725 * Return -1 if `string' contains no whitespace */
726 static int split_string_at_whitespace(char *string
)
728 int index
= strcspn(string
, "\n\t ");
729 if (string
[index
] != '\0') {
731 g_strstrip(string
+index
);
737 /* return TRUE, if the string contains exactly one unescaped %s and no other
738 * printf directives */
739 static gboolean
sanity_check_search_url(const char *string
)
741 int was_percent_char
= 0, percent_s_count
= 0;
743 for (; *string
; string
++) {
746 was_percent_char
= !was_percent_char
;
749 if (was_percent_char
)
751 was_percent_char
= 0;
754 if (was_percent_char
)
756 was_percent_char
= 0;
761 return percent_s_count
== 0 || percent_s_count
== 1;
764 void make_searchengines_list(Searchengine
*searchengines
, int length
)
767 for (i
= 0; i
< length
; i
++, searchengines
++) {
768 dynamic_searchengines
= g_list_prepend(dynamic_searchengines
, searchengines
);
772 /* find a searchengine with a given handle and return its URI or NULL if
774 * The returned string is internal and must not be freed or modified. */
775 const char *find_uri_for_searchengine(const char *handle
)
779 if (dynamic_searchengines
!= NULL
) {
780 for (l
= dynamic_searchengines
; l
; l
= g_list_next(l
)) {
781 Searchengine
*s
= (Searchengine
*)l
->data
;
782 if (!strcmp(s
->handle
, handle
)) {
792 read_rcfile(const char *config
)
794 int t
, linum
= 0, index
;
795 char s
[255], *buffer
;
797 gboolean found_malformed_lines
= FALSE
;
799 URIHandler
*newhandler
;
801 if (access(config
, F_OK
) != 0)
802 return FILE_NOT_FOUND
;
804 fpin
= fopen(config
, "r");
806 return READING_FAILED
;
808 while (fgets(s
, 254, fpin
)) {
811 * ignore lines that begin with #, / and such
817 if (strncmp(s
, "shortcut", 8) == 0 || strncmp(s
, "searchengine", 12) == 0) {
818 if (strncmp(s
, "shortcut", 8) == 0) {
823 while (buffer
[0] == ' ')
825 /* split line at whitespace */
826 index
= split_string_at_whitespace(buffer
);
827 if (index
< 0 || buffer
[0] == '\0' || buffer
[index
] == '\0'
828 || !sanity_check_search_url(buffer
+index
)) {
829 fprintf(stderr
, "searchengines: syntax error on line %d\n", linum
);
830 found_malformed_lines
= TRUE
;
833 new = malloc(sizeof(Searchengine
));
835 fprintf(stderr
, "Memory exhausted while loading search engines.\n");
838 new->handle
= g_strdup(buffer
);
839 new->uri
= g_strdup(buffer
+index
);
840 dynamic_searchengines
= g_list_prepend(dynamic_searchengines
, new);
841 } else if (strncmp(s
, "handler", 7) == 0) {
843 while (buffer
[0] == ' ')
845 /* split line at whitespace */
846 index
= split_string_at_whitespace(buffer
);
847 if (index
< 0 || buffer
[0] == '\0' || buffer
[index
] == '\0'
848 || !sanity_check_search_url(buffer
+index
)) { /* this sanity check is also valid for handler definitions */
849 fprintf(stderr
, "URI handlers: syntax error on line %d\n", linum
);
850 found_malformed_lines
= TRUE
;
853 newhandler
= malloc(sizeof(URIHandler
));
854 if (newhandler
== NULL
) {
855 fprintf(stderr
, "Memory exhausted while loading uri handlers.\n");
858 newhandler
->handle
= g_strdup(buffer
);
859 newhandler
->handler
= g_strdup(buffer
+index
);
860 dynamic_uri_handlers
= g_list_prepend(dynamic_uri_handlers
, newhandler
);
862 if (!process_line(s
))
863 found_malformed_lines
= TRUE
;
867 return found_malformed_lines
? SYNTAX_ERROR
: SUCCESS
;
870 void make_uri_handlers_list(URIHandler
*uri_handlers
, int length
)
873 for (i
= 0; i
< length
; i
++, uri_handlers
++) {
874 dynamic_uri_handlers
= g_list_prepend(dynamic_uri_handlers
, uri_handlers
);
879 /* spawn a child process handling a protocol encoded in URI.
881 On success, pid will contain the pid of the spawned child.
882 If you pass NULL as child_pid, glib will reap the child. */
884 open_handler_pid(char *uri
, GPid
*child_pid
) {
886 char *p
= NULL
, *arg
, arg_temp
[MAX_SETTING_SIZE
], *temp
, temp2
[MAX_SETTING_SIZE
] = "", *temp3
;
889 GSpawnFlags flags
= G_SPAWN_SEARCH_PATH
;
892 flags
|= G_SPAWN_DO_NOT_REAP_CHILD
;
894 p
= strchr(uri
, ':');
896 if (dynamic_uri_handlers
!= NULL
) {
897 for (l
= dynamic_uri_handlers
; l
; l
= g_list_next(l
)) {
898 URIHandler
*s
= (URIHandler
*)l
->data
;
899 if (strlen(uri
) >= strlen(s
->handle
) && strncmp(s
->handle
, uri
, strlen(s
->handle
)) == 0) {
900 if (strlen(s
->handler
) > 0) {
901 arg
= (uri
+ strlen(s
->handle
));
902 strncpy(temp2
, s
->handler
, MAX_SETTING_SIZE
);
903 temp
= strtok(temp2
, " ");
905 while (temp
!= NULL
) {
906 if (strstr(temp
, "%s")) {
908 memset(arg_temp
, 0, MAX_SETTING_SIZE
);
909 while (strncmp(temp3
, "%s", 2) != 0) {
910 strncat(arg_temp
, temp3
, 1);
913 strcat(arg_temp
, arg
);
916 strcat(arg_temp
, temp3
);
921 temp
= strtok(NULL
, " ");
925 g_spawn_async(NULL
, argv
, NULL
, flags
,
926 NULL
, NULL
, child_pid
, NULL
);
938 open_handler(char *uri
) {
939 return open_handler_pid(uri
, NULL
);