girara
completion.c
Go to the documentation of this file.
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 #if GTK_MAJOR_VERSION == 2
00265     session->gtk.results = GTK_BOX(gtk_vbox_new(FALSE, 0));
00266 #else
00267     session->gtk.results = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
00268 #endif
00269 
00270     if (session->gtk.results == NULL) {
00271       g_free(current_command);
00272       g_free(current_parameter);
00273 
00274       g_strfreev(elements);
00275       return false;
00276     }
00277 
00278     if (n_parameter <= 1) {
00279     /* based on commands */
00280       command_mode = true;
00281 
00282       /* create command rows */
00283       GIRARA_LIST_FOREACH(session->bindings.commands, girara_command_t*, iter, command)
00284         if (current_command == NULL ||
00285             (command->command != NULL && !strncmp(current_command, command->command, current_command_length)) ||
00286             (command->abbr != NULL && !strncmp(current_command, command->abbr,    current_command_length))
00287           )
00288         {
00289           /* create entry */
00290           girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00291           entry->group  = FALSE;
00292           entry->value  = g_strdup(command->command);
00293           entry->widget = girara_completion_row_create(session, command->command, command->description, FALSE);
00294 
00295           entries = g_list_append(entries, entry);
00296 
00297           /* show entry row */
00298           gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00299         }
00300       GIRARA_LIST_FOREACH_END(session->bindings.commands, girara_command_t*, iter, command);
00301     }
00302 
00303     /* based on parameters */
00304     if (n_parameter > 1 || g_list_length(entries) == 1) {
00305       /* if only one command exists try to run parameter completion */
00306       if (g_list_length(entries) == 1) {
00307         girara_internal_completion_entry_t* entry = g_list_first(entries)->data;
00308 
00309         /* unset command mode */
00310         command_mode           = false;
00311         current_command        = entry->value;
00312         current_command_length = strlen(current_command);
00313 
00314         /* clear list */
00315         gtk_widget_destroy(GTK_WIDGET(entry->widget));
00316 
00317         entries = g_list_remove(entries, g_list_first(entries)->data);
00318         g_slice_free(girara_internal_completion_entry_t, entry);
00319       }
00320 
00321       /* search matching command */
00322       girara_command_t* command = NULL;
00323       GIRARA_LIST_FOREACH(session->bindings.commands, girara_command_t*, iter, command_it)
00324         if ( (current_command != NULL && command_it->command != NULL && !strncmp(current_command, command_it->command, current_command_length)) ||
00325              (current_command != NULL && command_it->abbr != NULL    && !strncmp(current_command, command_it->abbr,    current_command_length))
00326           )
00327         {
00328           g_free(previous_command);
00329           previous_command = g_strdup(command_it->command);
00330           command = command_it;
00331           break;
00332         }
00333       GIRARA_LIST_FOREACH_END(session->bindings.commands, girara_command_t*, iter, command_it);
00334 
00335       if (command == NULL) {
00336         g_free(current_command);
00337         g_free(current_parameter);
00338 
00339         g_strfreev(elements);
00340         return false;
00341       }
00342 
00343       if (command->completion == NULL) {
00344           girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00345           entry->group  = FALSE;
00346           entry->value  = g_strdup(command->command);
00347           entry->widget = girara_completion_row_create(session, command->command, command->description, FALSE);
00348 
00349           entries = g_list_append(entries, entry);
00350 
00351           gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00352           command_mode = true;
00353       } else {
00354         /* generate completion result
00355          * XXX: the last argument should only be current_paramater ... but
00356          * therefore the completion functions would need to handle NULL correctly
00357          * (see cc_open in zathura). */
00358         girara_completion_t *result = command->completion(session, current_parameter ? current_parameter : "");
00359 
00360         if (result == NULL || result->groups == NULL) {
00361           g_free(current_command);
00362           g_free(current_parameter);
00363 
00364           g_strfreev(elements);
00365           return false;
00366         }
00367 
00368         GIRARA_LIST_FOREACH(result->groups, girara_completion_group_t*, iter, group)
00369           /* create group entry */
00370           if (group->value != NULL) {
00371             girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00372             entry->group  = TRUE;
00373             entry->value  = g_strdup(group->value);
00374             entry->widget = girara_completion_row_create(session, group->value, NULL, TRUE);
00375 
00376             entries = g_list_append(entries, entry);
00377 
00378             gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00379           }
00380 
00381           GIRARA_LIST_FOREACH(group->elements, girara_completion_element_t*, iter2, element)
00382             girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00383             entry->group  = FALSE;
00384             entry->value  = g_strdup(element->value);
00385             entry->widget = girara_completion_row_create(session, element->value, element->description, FALSE);
00386 
00387             entries = g_list_append(entries, entry);
00388 
00389             gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00390 
00391           GIRARA_LIST_FOREACH_END(group->elements, girara_completion_element_t*, iter2, element);
00392         GIRARA_LIST_FOREACH_END(result->groups, girara_completion_group_t*, iter, group);
00393         girara_completion_free(result);
00394 
00395         command_mode = false;
00396       }
00397     }
00398 
00399     if (entries != NULL) {
00400       entries_current = (argument->n == GIRARA_NEXT) ? g_list_last(entries) : entries;
00401       gtk_box_pack_start(session->gtk.box, GTK_WIDGET(session->gtk.results), FALSE, FALSE, 0);
00402       gtk_widget_show(GTK_WIDGET(session->gtk.results));
00403     }
00404   }
00405 
00406   /* update entries */
00407   unsigned int n_elements = g_list_length(entries);
00408   if (entries != NULL && n_elements > 0) {
00409     if (n_elements > 1) {
00410       girara_completion_row_set_color(session, ((girara_internal_completion_entry_t *) entries_current->data)->widget, GIRARA_NORMAL);
00411 
00412       bool next_group = FALSE;
00413 
00414       for (unsigned int i = 0; i < n_elements; i++) {
00415         if (argument->n == GIRARA_NEXT || argument->n == GIRARA_NEXT_GROUP) {
00416           GList* entry = g_list_next(entries_current);
00417           if (entry == NULL) {
00418             entry = g_list_first(entries);
00419           }
00420 
00421           entries_current = entry;
00422         } else if (argument->n == GIRARA_PREVIOUS || argument->n == GIRARA_PREVIOUS_GROUP) {
00423           GList* entry = g_list_previous(entries_current);
00424           if (entry == NULL) {
00425             entry = g_list_last(entries);
00426           }
00427 
00428           entries_current = entry;
00429         }
00430 
00431         if (((girara_internal_completion_entry_t*) entries_current->data)->group) {
00432           if (command_mode == false && (argument->n == GIRARA_NEXT_GROUP || argument->n == GIRARA_PREVIOUS_GROUP)) {
00433             next_group = TRUE;
00434           }
00435           continue;
00436         } else {
00437           if (command_mode == false && (next_group == 0) && (argument->n == GIRARA_NEXT_GROUP || argument->n == GIRARA_PREVIOUS_GROUP)) {
00438             continue;
00439           }
00440           break;
00441         }
00442       }
00443 
00444       girara_completion_row_set_color(session, ((girara_internal_completion_entry_t *) entries_current->data)->widget, GIRARA_HIGHLIGHT);
00445 
00446       /* hide other items */
00447       unsigned int n_completion_items = 15;
00448       girara_setting_get(session, "n-completion-items", &n_completion_items);
00449       unsigned int uh = ceil( n_completion_items / 2);
00450       unsigned int lh = floor(n_completion_items / 2);
00451 
00452       unsigned int current_item = g_list_position(entries, entries_current);
00453 
00454       GList* tmpentry = entries;
00455       for (unsigned int i = 0; i < n_elements; i++) {
00456         if (
00457             (i >= (current_item - lh) && (i <= current_item + uh)) ||
00458             (i < n_completion_items && current_item < lh) ||
00459             (i >= (n_elements - n_completion_items) && (current_item >= (n_elements - uh)))
00460           )
00461         {
00462           gtk_widget_show(GTK_WIDGET(((girara_internal_completion_entry_t*) tmpentry->data)->widget));
00463         } else {
00464           gtk_widget_hide(GTK_WIDGET(((girara_internal_completion_entry_t*) tmpentry->data)->widget));
00465         }
00466 
00467         tmpentry = g_list_next(tmpentry);
00468       }
00469     } else {
00470       gtk_widget_hide(GTK_WIDGET(((girara_internal_completion_entry_t*) (g_list_nth(entries, 0))->data)->widget));
00471     }
00472 
00473     /* update text */
00474     char* temp;
00475     char* escaped_value = escape(((girara_internal_completion_entry_t *) entries_current->data)->value);
00476     if (command_mode == true) {
00477       char* space = (n_elements == 1) ? " " : "";
00478       temp = g_strconcat(":", escaped_value, space, NULL);
00479     } else {
00480       temp = g_strconcat(":", previous_command, " ", escaped_value, NULL);
00481     }
00482 
00483     gtk_entry_set_text(session->gtk.inputbar_entry, temp);
00484     gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
00485     g_free(escaped_value);
00486 
00487     /* update previous */
00488     g_free(previous_command);
00489     g_free(previous_parameter);
00490     previous_command   = g_strdup((command_mode) ? ((girara_internal_completion_entry_t*) entries_current->data)->value : current_command);
00491     previous_parameter = g_strdup((command_mode) ? current_parameter : ((girara_internal_completion_entry_t*) entries_current->data)->value);
00492     previous_length    = strlen(temp);
00493     g_free(temp);
00494   }
00495 
00496   g_free(current_command);
00497   g_free(current_parameter);
00498 
00499   g_strfreev(elements);
00500 
00501   return false;
00502 }
00503 
00504 static GtkEventBox*
00505 girara_completion_row_create(girara_session_t* session, const char* command, const char* description, bool group)
00506 {
00507 #if GTK_MAJOR_VERSION == 2
00508   GtkBox *col = GTK_BOX(gtk_hbox_new(FALSE, 0));
00509 #else
00510   GtkBox *col = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
00511 #endif
00512 
00513   GtkEventBox *row = GTK_EVENT_BOX(gtk_event_box_new());
00514 
00515   GtkLabel *show_command     = GTK_LABEL(gtk_label_new(NULL));
00516   GtkLabel *show_description = GTK_LABEL(gtk_label_new(NULL));
00517 
00518   gtk_misc_set_alignment(GTK_MISC(show_command),     0.0, 0.0);
00519   gtk_misc_set_alignment(GTK_MISC(show_description), 0.0, 0.0);
00520 
00521   if (group == true) {
00522     gtk_misc_set_padding(GTK_MISC(show_command),     2, 4);
00523     gtk_misc_set_padding(GTK_MISC(show_description), 2, 4);
00524   } else {
00525     gtk_misc_set_padding(GTK_MISC(show_command),     1, 1);
00526     gtk_misc_set_padding(GTK_MISC(show_description), 1, 1);
00527   }
00528 
00529   gtk_label_set_use_markup(show_command,     TRUE);
00530   gtk_label_set_use_markup(show_description, TRUE);
00531 
00532   gchar* c = g_markup_printf_escaped(FORMAT_COMMAND,     command ? command : "");
00533   gchar* d = g_markup_printf_escaped(FORMAT_DESCRIPTION, description ? description : "");
00534   gtk_label_set_markup(show_command,     c);
00535   gtk_label_set_markup(show_description, d);
00536   g_free(c);
00537   g_free(d);
00538 
00539   if (group == true) {
00540     gtk_widget_override_color(GTK_WIDGET(show_command),     GTK_STATE_NORMAL, &(session->style.completion_group_foreground));
00541     gtk_widget_override_color(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(session->style.completion_group_foreground));
00542     gtk_widget_override_background_color(GTK_WIDGET(row),   GTK_STATE_NORMAL, &(session->style.completion_group_background));
00543   } else {
00544     gtk_widget_override_color(GTK_WIDGET(show_command),     GTK_STATE_NORMAL, &(session->style.completion_foreground));
00545     gtk_widget_override_color(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(session->style.completion_foreground));
00546     gtk_widget_override_background_color(GTK_WIDGET(row),   GTK_STATE_NORMAL, &(session->style.completion_background));
00547  }
00548 
00549   gtk_widget_override_font(GTK_WIDGET(show_command),     session->style.font);
00550   gtk_widget_override_font(GTK_WIDGET(show_description), session->style.font);
00551 
00552   gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_command),     TRUE,  TRUE,  2);
00553   gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_description), FALSE, FALSE, 2);
00554 
00555   gtk_container_add(GTK_CONTAINER(row), GTK_WIDGET(col));
00556   gtk_widget_show_all(GTK_WIDGET(row));
00557 
00558   return row;
00559 }
00560 
00561 static void
00562 girara_completion_row_set_color(girara_session_t* session, GtkEventBox* row, int mode)
00563 {
00564   g_return_if_fail(session != NULL);
00565   g_return_if_fail(row     != NULL);
00566 
00567   GtkBox *col    = GTK_BOX(gtk_bin_get_child(GTK_BIN(row)));
00568   GList* items   = gtk_container_get_children(GTK_CONTAINER(col));
00569   GtkLabel *cmd  = GTK_LABEL(g_list_nth_data(items, 0));
00570   GtkLabel *desc = GTK_LABEL(g_list_nth_data(items, 1));
00571 
00572   if (mode == GIRARA_HIGHLIGHT) {
00573     gtk_widget_override_color(GTK_WIDGET(cmd),            GTK_STATE_NORMAL, &(session->style.completion_highlight_foreground));
00574     gtk_widget_override_color(GTK_WIDGET(desc),           GTK_STATE_NORMAL, &(session->style.completion_highlight_foreground));
00575     gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_highlight_background));
00576   } else {
00577     gtk_widget_override_color(GTK_WIDGET(cmd),            GTK_STATE_NORMAL, &(session->style.completion_foreground));
00578     gtk_widget_override_color(GTK_WIDGET(desc),           GTK_STATE_NORMAL, &(session->style.completion_foreground));
00579     gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_background));
00580   }
00581 
00582   g_list_free(items);
00583 }
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines