girara
|
00001 /* See LICENSE file for license and copyright information */ 00002 00003 #include <math.h> 00004 #include <string.h> 00005 #include <stdlib.h> 00006 00007 #include "completion.h" 00008 #include "internal.h" 00009 #include "session.h" 00010 #include "settings.h" 00011 #include "datastructures.h" 00012 00013 #if GTK_MAJOR_VERSION == 2 00014 #include "gtk2-compat.h" 00015 #endif 00016 00017 static GtkEventBox* girara_completion_row_create(girara_session_t*, const char*, const char*, bool); 00018 static void girara_completion_row_set_color(girara_session_t*, GtkEventBox*, int); 00019 00020 /* completion */ 00021 struct girara_internal_completion_entry_s 00022 { 00023 bool group; 00024 char* value; 00025 GtkEventBox* widget; 00026 }; 00027 00031 struct girara_completion_element_s 00032 { 00033 char *value; 00034 char *description; 00035 }; 00036 00040 struct girara_completion_group_s 00041 { 00042 char *value; 00043 girara_list_t *elements; 00044 }; 00045 00049 struct girara_completion_s 00050 { 00051 girara_list_t *groups; 00052 }; 00053 00054 typedef struct girara_internal_completion_entry_s girara_internal_completion_entry_t; 00055 00056 static void 00057 completion_element_free(girara_completion_element_t* element) 00058 { 00059 if (element == NULL) { 00060 return; 00061 } 00062 00063 /* free element */ 00064 g_free(element->value); 00065 g_free(element->description); 00066 g_slice_free(girara_completion_element_t, element); 00067 } 00068 00069 static char* 00070 escape(const char* value) 00071 { 00072 if (value == NULL) { 00073 return NULL; 00074 } 00075 00076 GString* str = g_string_new(""); 00077 while (*value != '\0') { 00078 const char c = *value++; 00079 if (strchr("\\ \t\"\'", c) != NULL) { 00080 g_string_append_c(str, '\\'); 00081 } 00082 g_string_append_c(str, c); 00083 } 00084 00085 return g_string_free(str, FALSE); 00086 } 00087 00088 girara_completion_t* 00089 girara_completion_init() 00090 { 00091 girara_completion_t *completion = g_slice_new(girara_completion_t); 00092 completion->groups = girara_list_new2( 00093 (girara_free_function_t) girara_completion_group_free); 00094 00095 return completion; 00096 } 00097 00098 girara_completion_group_t* 00099 girara_completion_group_create(girara_session_t* UNUSED(session), const char* name) 00100 { 00101 girara_completion_group_t* group = g_slice_new(girara_completion_group_t); 00102 00103 group->value = name ? g_strdup(name) : NULL; 00104 group->elements = girara_list_new2( 00105 (girara_free_function_t) completion_element_free); 00106 00107 if (group->elements == NULL) { 00108 g_slice_free(girara_completion_group_t, group); 00109 return NULL; 00110 } 00111 00112 return group; 00113 } 00114 00115 void 00116 girara_completion_add_group(girara_completion_t* completion, girara_completion_group_t* group) 00117 { 00118 g_return_if_fail(completion != NULL); 00119 g_return_if_fail(group != NULL); 00120 00121 girara_list_append(completion->groups, group); 00122 } 00123 00124 void 00125 girara_completion_group_free(girara_completion_group_t* group) 00126 { 00127 if (group == NULL) { 00128 return; 00129 } 00130 00131 g_free(group->value); 00132 girara_list_free(group->elements); 00133 g_slice_free(girara_completion_group_t, group); 00134 } 00135 00136 void 00137 girara_completion_free(girara_completion_t* completion) 00138 { 00139 g_return_if_fail(completion != NULL); 00140 00141 girara_list_free(completion->groups); 00142 /* free completion */ 00143 g_slice_free(girara_completion_t, completion); 00144 } 00145 00146 void 00147 girara_completion_group_add_element(girara_completion_group_t* group, const char* name, const char* description) 00148 { 00149 g_return_if_fail(group != NULL); 00150 g_return_if_fail(name != NULL); 00151 00152 girara_completion_element_t* new_element = g_slice_new(girara_completion_element_t); 00153 00154 new_element->value = g_strdup(name); 00155 new_element->description = description ? g_strdup(description) : NULL; 00156 00157 girara_list_append(group->elements, new_element); 00158 } 00159 00160 bool 00161 girara_isc_completion(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t)) 00162 { 00163 g_return_val_if_fail(session != NULL, false); 00164 00165 /* get current text */ 00166 gchar *input = gtk_editable_get_chars(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1); 00167 if (input == NULL) { 00168 return false; 00169 } 00170 00171 const size_t input_length = strlen(input); 00172 00173 if (input_length == 0 || input[0] != ':') { 00174 g_free(input); 00175 return false; 00176 } 00177 00178 gchar** elements = NULL; 00179 gint n_parameter = 0; 00180 if (input_length > 1) { 00181 if (g_shell_parse_argv(input + 1, &n_parameter, &elements, NULL) == FALSE) { 00182 g_free(input); 00183 return FALSE; 00184 } 00185 } else { 00186 elements = g_malloc0(2 * sizeof(char*)); 00187 elements[0] = g_strdup(""); 00188 } 00189 00190 if (n_parameter == 1 && input[input_length-1] == ' ') { 00191 n_parameter += 1; 00192 } 00193 00194 g_free(input); 00195 00196 /* get current values */ 00197 gchar *current_command = (elements[0] != NULL && elements[0][0] != '\0') ? g_strdup(elements[0]) : NULL; 00198 gchar *current_parameter = (elements[0] != NULL && elements[1] != NULL) ? g_strdup(elements[1]) : NULL; 00199 00200 size_t current_command_length = current_command ? strlen(current_command) : 0; 00201 00202 static GList* entries = NULL; 00203 static GList* entries_current = NULL; 00204 static char *previous_command = NULL; 00205 static char *previous_parameter = NULL; 00206 static bool command_mode = true; 00207 static size_t previous_length = 0; 00208 00209 /* delete old list iff 00210 * the completion should be hidden 00211 * the current command differs from the previous one 00212 * the current parameter differs from the previous one 00213 * no current command is given 00214 */ 00215 if ( (argument->n == GIRARA_HIDE) || 00216 (current_parameter && previous_parameter && strcmp(current_parameter, previous_parameter)) || 00217 (current_command && previous_command && strcmp(current_command, previous_command)) || 00218 input_length != previous_length 00219 ) 00220 { 00221 if (session->gtk.results != NULL) { 00222 /* destroy elements */ 00223 for (GList* element = entries; element; element = g_list_next(element)) { 00224 girara_internal_completion_entry_t* entry = (girara_internal_completion_entry_t*) element->data; 00225 00226 if (entry != NULL) { 00227 gtk_widget_destroy(GTK_WIDGET(entry->widget)); 00228 g_free(entry->value); 00229 g_slice_free(girara_internal_completion_entry_t, entry); 00230 } 00231 } 00232 00233 g_list_free(entries); 00234 entries = NULL; 00235 entries_current = NULL; 00236 00237 /* delete row box */ 00238 gtk_widget_destroy(GTK_WIDGET(session->gtk.results)); 00239 session->gtk.results = NULL; 00240 } 00241 00242 command_mode = true; 00243 00244 if (argument->n == GIRARA_HIDE) { 00245 g_free(previous_command); 00246 previous_command = NULL; 00247 00248 g_free(previous_parameter); 00249 previous_parameter = NULL; 00250 00251 g_strfreev(elements); 00252 00253 g_free(current_command); 00254 g_free(current_parameter); 00255 00256 return false; 00257 } 00258 } 00259 00260 /* create new list iff 00261 * there is no current list 00262 */ 00263 if (session->gtk.results == NULL) { 00264 session->gtk.results = GTK_BOX(gtk_vbox_new(FALSE, 0)); 00265 00266 if (session->gtk.results == NULL) { 00267 g_free(current_command); 00268 g_free(current_parameter); 00269 00270 g_strfreev(elements); 00271 return false; 00272 } 00273 00274 if (n_parameter <= 1) { 00275 /* based on commands */ 00276 command_mode = true; 00277 00278 /* create command rows */ 00279 GIRARA_LIST_FOREACH(session->bindings.commands, girara_command_t*, iter, command) 00280 if (current_command == NULL || 00281 (command->command != NULL && !strncmp(current_command, command->command, current_command_length)) || 00282 (command->abbr != NULL && !strncmp(current_command, command->abbr, current_command_length)) 00283 ) 00284 { 00285 /* create entry */ 00286 girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t); 00287 entry->group = FALSE; 00288 entry->value = g_strdup(command->command); 00289 entry->widget = girara_completion_row_create(session, command->command, command->description, FALSE); 00290 00291 entries = g_list_append(entries, entry); 00292 00293 /* show entry row */ 00294 gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0); 00295 } 00296 GIRARA_LIST_FOREACH_END(session->bindings.commands, girara_command_t*, iter, command); 00297 } 00298 00299 /* based on parameters */ 00300 if (n_parameter > 1 || g_list_length(entries) == 1) { 00301 /* if only one command exists try to run parameter completion */ 00302 if (g_list_length(entries) == 1) { 00303 girara_internal_completion_entry_t* entry = g_list_first(entries)->data; 00304 00305 /* unset command mode */ 00306 command_mode = false; 00307 current_command = entry->value; 00308 current_command_length = strlen(current_command); 00309 00310 /* clear list */ 00311 gtk_widget_destroy(GTK_WIDGET(entry->widget)); 00312 00313 entries = g_list_remove(entries, g_list_first(entries)->data); 00314 g_slice_free(girara_internal_completion_entry_t, entry); 00315 } 00316 00317 /* search matching command */ 00318 girara_command_t* command = NULL; 00319 GIRARA_LIST_FOREACH(session->bindings.commands, girara_command_t*, iter, command_it) 00320 if ( (current_command != NULL && command_it->command != NULL && !strncmp(current_command, command_it->command, current_command_length)) || 00321 (current_command != NULL && command_it->abbr != NULL && !strncmp(current_command, command_it->abbr, current_command_length)) 00322 ) 00323 { 00324 g_free(previous_command); 00325 previous_command = g_strdup(command_it->command); 00326 command = command_it; 00327 break; 00328 } 00329 GIRARA_LIST_FOREACH_END(session->bindings.commands, girara_command_t*, iter, command_it); 00330 00331 if (command == NULL) { 00332 g_free(current_command); 00333 g_free(current_parameter); 00334 00335 g_strfreev(elements); 00336 return false; 00337 } 00338 00339 if (command->completion == NULL) { 00340 girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t); 00341 entry->group = FALSE; 00342 entry->value = g_strdup(command->command); 00343 entry->widget = girara_completion_row_create(session, command->command, command->description, FALSE); 00344 00345 entries = g_list_append(entries, entry); 00346 00347 gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0); 00348 command_mode = true; 00349 } else { 00350 /* generate completion result 00351 * XXX: the last argument should only be current_paramater ... but 00352 * therefore the completion functions would need to handle NULL correctly 00353 * (see cc_open in zathura). */ 00354 girara_completion_t *result = command->completion(session, current_parameter ? current_parameter : ""); 00355 00356 if (result == NULL || result->groups == NULL) { 00357 g_free(current_command); 00358 g_free(current_parameter); 00359 00360 g_strfreev(elements); 00361 return false; 00362 } 00363 00364 GIRARA_LIST_FOREACH(result->groups, girara_completion_group_t*, iter, group) 00365 /* create group entry */ 00366 if (group->value != NULL) { 00367 girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t); 00368 entry->group = TRUE; 00369 entry->value = g_strdup(group->value); 00370 entry->widget = girara_completion_row_create(session, group->value, NULL, TRUE); 00371 00372 entries = g_list_append(entries, entry); 00373 00374 gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0); 00375 } 00376 00377 GIRARA_LIST_FOREACH(group->elements, girara_completion_element_t*, iter2, element) 00378 girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t); 00379 entry->group = FALSE; 00380 entry->value = g_strdup(element->value); 00381 entry->widget = girara_completion_row_create(session, element->value, element->description, FALSE); 00382 00383 entries = g_list_append(entries, entry); 00384 00385 gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0); 00386 00387 GIRARA_LIST_FOREACH_END(group->elements, girara_completion_element_t*, iter2, element); 00388 GIRARA_LIST_FOREACH_END(result->groups, girara_completion_group_t*, iter, group); 00389 girara_completion_free(result); 00390 00391 command_mode = false; 00392 } 00393 } 00394 00395 if (entries != NULL) { 00396 entries_current = (argument->n == GIRARA_NEXT) ? g_list_last(entries) : entries; 00397 gtk_box_pack_start(session->gtk.box, GTK_WIDGET(session->gtk.results), FALSE, FALSE, 0); 00398 gtk_widget_show(GTK_WIDGET(session->gtk.results)); 00399 } 00400 } 00401 00402 /* update entries */ 00403 unsigned int n_elements = g_list_length(entries); 00404 if (entries != NULL && n_elements > 0) { 00405 if (n_elements > 1) { 00406 girara_completion_row_set_color(session, ((girara_internal_completion_entry_t *) entries_current->data)->widget, GIRARA_NORMAL); 00407 00408 bool next_group = FALSE; 00409 00410 for (unsigned int i = 0; i < n_elements; i++) { 00411 if (argument->n == GIRARA_NEXT || argument->n == GIRARA_NEXT_GROUP) { 00412 GList* entry = g_list_next(entries_current); 00413 if (entry == NULL) { 00414 entry = g_list_first(entries); 00415 } 00416 00417 entries_current = entry; 00418 } else if (argument->n == GIRARA_PREVIOUS || argument->n == GIRARA_PREVIOUS_GROUP) { 00419 GList* entry = g_list_previous(entries_current); 00420 if (entry == NULL) { 00421 entry = g_list_last(entries); 00422 } 00423 00424 entries_current = entry; 00425 } 00426 00427 if (((girara_internal_completion_entry_t*) entries_current->data)->group) { 00428 if (command_mode == false && (argument->n == GIRARA_NEXT_GROUP || argument->n == GIRARA_PREVIOUS_GROUP)) { 00429 next_group = TRUE; 00430 } 00431 continue; 00432 } else { 00433 if (command_mode == false && (next_group == 0) && (argument->n == GIRARA_NEXT_GROUP || argument->n == GIRARA_PREVIOUS_GROUP)) { 00434 continue; 00435 } 00436 break; 00437 } 00438 } 00439 00440 girara_completion_row_set_color(session, ((girara_internal_completion_entry_t *) entries_current->data)->widget, GIRARA_HIGHLIGHT); 00441 00442 /* hide other items */ 00443 unsigned int n_completion_items = 15; 00444 girara_setting_get(session, "n-completion-items", &n_completion_items); 00445 unsigned int uh = ceil( n_completion_items / 2); 00446 unsigned int lh = floor(n_completion_items / 2); 00447 00448 unsigned int current_item = g_list_position(entries, entries_current); 00449 00450 GList* tmpentry = entries; 00451 for (unsigned int i = 0; i < n_elements; i++) { 00452 if ( 00453 (i >= (current_item - lh) && (i <= current_item + uh)) || 00454 (i < n_completion_items && current_item < lh) || 00455 (i >= (n_elements - n_completion_items) && (current_item >= (n_elements - uh))) 00456 ) 00457 { 00458 gtk_widget_show(GTK_WIDGET(((girara_internal_completion_entry_t*) tmpentry->data)->widget)); 00459 } else { 00460 gtk_widget_hide(GTK_WIDGET(((girara_internal_completion_entry_t*) tmpentry->data)->widget)); 00461 } 00462 00463 tmpentry = g_list_next(tmpentry); 00464 } 00465 } else { 00466 gtk_widget_hide(GTK_WIDGET(((girara_internal_completion_entry_t*) (g_list_nth(entries, 0))->data)->widget)); 00467 } 00468 00469 /* update text */ 00470 char* temp; 00471 char* escaped_value = escape(((girara_internal_completion_entry_t *) entries_current->data)->value); 00472 if (command_mode == true) { 00473 char* space = (n_elements == 1) ? " " : ""; 00474 temp = g_strconcat(":", escaped_value, space, NULL); 00475 } else { 00476 temp = g_strconcat(":", previous_command, " ", escaped_value, NULL); 00477 } 00478 00479 gtk_entry_set_text(session->gtk.inputbar_entry, temp); 00480 gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1); 00481 g_free(escaped_value); 00482 00483 /* update previous */ 00484 g_free(previous_command); 00485 g_free(previous_parameter); 00486 previous_command = g_strdup((command_mode) ? ((girara_internal_completion_entry_t*) entries_current->data)->value : current_command); 00487 previous_parameter = g_strdup((command_mode) ? current_parameter : ((girara_internal_completion_entry_t*) entries_current->data)->value); 00488 previous_length = strlen(temp); 00489 g_free(temp); 00490 } 00491 00492 g_free(current_command); 00493 g_free(current_parameter); 00494 00495 g_strfreev(elements); 00496 00497 return false; 00498 } 00499 00500 static GtkEventBox* 00501 girara_completion_row_create(girara_session_t* session, const char* command, const char* description, bool group) 00502 { 00503 GtkBox *col = GTK_BOX(gtk_hbox_new(FALSE, 0)); 00504 GtkEventBox *row = GTK_EVENT_BOX(gtk_event_box_new()); 00505 00506 GtkLabel *show_command = GTK_LABEL(gtk_label_new(NULL)); 00507 GtkLabel *show_description = GTK_LABEL(gtk_label_new(NULL)); 00508 00509 gtk_misc_set_alignment(GTK_MISC(show_command), 0.0, 0.0); 00510 gtk_misc_set_alignment(GTK_MISC(show_description), 0.0, 0.0); 00511 00512 if (group == true) { 00513 gtk_misc_set_padding(GTK_MISC(show_command), 2, 4); 00514 gtk_misc_set_padding(GTK_MISC(show_description), 2, 4); 00515 } else { 00516 gtk_misc_set_padding(GTK_MISC(show_command), 1, 1); 00517 gtk_misc_set_padding(GTK_MISC(show_description), 1, 1); 00518 } 00519 00520 gtk_label_set_use_markup(show_command, TRUE); 00521 gtk_label_set_use_markup(show_description, TRUE); 00522 00523 gchar* c = g_markup_printf_escaped(FORMAT_COMMAND, command ? command : ""); 00524 gchar* d = g_markup_printf_escaped(FORMAT_DESCRIPTION, description ? description : ""); 00525 gtk_label_set_markup(show_command, c); 00526 gtk_label_set_markup(show_description, d); 00527 g_free(c); 00528 g_free(d); 00529 00530 if (group == true) { 00531 gtk_widget_override_color(GTK_WIDGET(show_command), GTK_STATE_NORMAL, &(session->style.completion_group_foreground)); 00532 gtk_widget_override_color(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(session->style.completion_group_foreground)); 00533 gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_group_background)); 00534 } else { 00535 gtk_widget_override_color(GTK_WIDGET(show_command), GTK_STATE_NORMAL, &(session->style.completion_foreground)); 00536 gtk_widget_override_color(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(session->style.completion_foreground)); 00537 gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_background)); 00538 } 00539 00540 gtk_widget_override_font(GTK_WIDGET(show_command), session->style.font); 00541 gtk_widget_override_font(GTK_WIDGET(show_description), session->style.font); 00542 00543 gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_command), TRUE, TRUE, 2); 00544 gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_description), FALSE, FALSE, 2); 00545 00546 gtk_container_add(GTK_CONTAINER(row), GTK_WIDGET(col)); 00547 gtk_widget_show_all(GTK_WIDGET(row)); 00548 00549 return row; 00550 } 00551 00552 static void 00553 girara_completion_row_set_color(girara_session_t* session, GtkEventBox* row, int mode) 00554 { 00555 g_return_if_fail(session != NULL); 00556 g_return_if_fail(row != NULL); 00557 00558 GtkBox *col = GTK_BOX(gtk_bin_get_child(GTK_BIN(row))); 00559 GList* items = gtk_container_get_children(GTK_CONTAINER(col)); 00560 GtkLabel *cmd = GTK_LABEL(g_list_nth_data(items, 0)); 00561 GtkLabel *desc = GTK_LABEL(g_list_nth_data(items, 1)); 00562 00563 if (mode == GIRARA_HIGHLIGHT) { 00564 gtk_widget_override_color(GTK_WIDGET(cmd), GTK_STATE_NORMAL, &(session->style.completion_highlight_foreground)); 00565 gtk_widget_override_color(GTK_WIDGET(desc), GTK_STATE_NORMAL, &(session->style.completion_highlight_foreground)); 00566 gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_highlight_background)); 00567 } else { 00568 gtk_widget_override_color(GTK_WIDGET(cmd), GTK_STATE_NORMAL, &(session->style.completion_foreground)); 00569 gtk_widget_override_color(GTK_WIDGET(desc), GTK_STATE_NORMAL, &(session->style.completion_foreground)); 00570 gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_background)); 00571 } 00572 00573 g_list_free(items); 00574 }