girara
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros
input-history.c
Go to the documentation of this file.
1 /* See LICENSE file for license and copyright information */
2 
3 #include "input-history.h"
4 #include "datastructures.h"
5 
6 G_DEFINE_TYPE(GiraraInputHistory, girara_input_history, G_TYPE_OBJECT)
7 
8 
11 typedef struct ih_private_s {
12  girara_list_t* history;
13  bool reset;
14  size_t current;
15  size_t current_match;
17  char* command_line;
18 } ih_private_t;
19 
20 #define GIRARA_INPUT_HISTORY_GET_PRIVATE(obj) \
21  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GIRARA_TYPE_INPUT_HISTORY, \
22  ih_private_t))
23 
24 /* Methods */
25 static void ih_finalize(GObject* object);
26 static void ih_set_property(GObject* object, guint prop_id,
27  const GValue* value, GParamSpec* pspec);
28 static void ih_get_property(GObject* object, guint prop_id, GValue* value,
29  GParamSpec* pspec);
30 static void ih_append(GiraraInputHistory* history, const char* input);
31 static girara_list_t* ih_list(GiraraInputHistory* history);
32 static const char* ih_next(GiraraInputHistory* history,
33  const char* current_input);
34 static const char* ih_previous(GiraraInputHistory* history,
35  const char* current_input);
36 static void ih_reset(GiraraInputHistory* history);
37 
38 /* Properties */
39 enum {
42 };
43 
44 /* Class init */
45 static void
46 girara_input_history_class_init(GiraraInputHistoryClass* class)
47 {
48  /* add private members */
49  g_type_class_add_private(class, sizeof(ih_private_t));
50 
51  /* overwrite methods */
52  GObjectClass* object_class = G_OBJECT_CLASS(class);
53  object_class->finalize = ih_finalize;
54  object_class->set_property = ih_set_property;
55  object_class->get_property = ih_get_property;
56 
57  class->append = ih_append;
58  class->list = ih_list;
59  class->next = ih_next;
60  class->previous = ih_previous;
61  class->reset = ih_reset;
62 
63  /* properties */
64  g_object_class_install_property(object_class, PROP_IO,
65  g_param_spec_object("io", "history reader/writer",
66  "GiraraInputHistoryIO object used to read and write history",
68  G_PARAM_WRITABLE | G_PARAM_READABLE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
69 }
70 
71 /* Object init */
72 static void
73 girara_input_history_init(GiraraInputHistory* history)
74 {
77  priv->reset = true;
78  priv->io = NULL;
79 }
80 
81 /* GObject finalize */
82 static void
83 ih_finalize(GObject* object)
84 {
87  g_free(priv->command_line);
88 
89  if (priv->io != NULL) {
90  g_object_unref(priv->io);
91  }
92 
93  G_OBJECT_CLASS(girara_input_history_parent_class)->finalize(object);
94 }
95 
96 /* GObject set_property */
97 static void
98 ih_set_property(GObject* object, guint prop_id, const GValue* value,
99  GParamSpec* pspec)
100 {
102 
103  switch (prop_id) {
104  case PROP_IO: {
105  if (priv->io != NULL) {
106  g_object_unref(priv->io);
107  }
108 
109  gpointer* tmp = g_value_dup_object(value);
110  if (tmp != NULL) {
111  priv->io = GIRARA_INPUT_HISTORY_IO(tmp);
112  } else {
113  priv->io = NULL;
114  }
116  break;
117  }
118  default:
119  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
120  }
121 }
122 
123 /* GObject get_property */
124 static void
125 ih_get_property(GObject* object, guint prop_id, GValue* value,
126  GParamSpec* pspec)
127 {
129 
130  switch (prop_id) {
131  case PROP_IO:
132  g_value_set_object(value, priv->io);
133  break;
134  default:
135  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
136  }
137 }
138 
139 /* Object new */
140 GiraraInputHistory*
142 {
143  return GIRARA_INPUT_HISTORY(g_object_new(GIRARA_TYPE_INPUT_HISTORY, "io",
144  io, NULL));
145 }
146 
147 /* Method implementions */
148 
149 static void
150 ih_append(GiraraInputHistory* history, const char* input)
151 {
152  if (input == NULL) {
153  return;
154  }
155 
156  girara_list_t* list = girara_input_history_list(history);
157  if (list == NULL) {
158  return;
159  }
160 
161  void* data = NULL;
162  while ((data = girara_list_find(list, (girara_compare_function_t) g_strcmp0, data)) != NULL) {
163  girara_list_remove(list, data);
164  }
165 
166  girara_list_append(list, g_strdup(input));
167 
169  if (priv->io != NULL) {
170  girara_input_history_io_append(priv->io, input);
171  }
172 
173  /* begin from the last command when navigating through history */
175 }
176 
177 static girara_list_t*
178 ih_list(GiraraInputHistory* history)
179 {
181  return priv->history;
182 }
183 
184 static const char*
185 find_next(GiraraInputHistory* history, const char* current_input, bool next)
186 {
188 
189  girara_list_t* list = girara_input_history_list(history);
190  if (list == NULL) {
191  return NULL;
192  }
193 
194  size_t length = girara_list_size(list);
195  if (length == 0) {
196  return NULL;
197  }
198 
199  if (priv->reset == true) {
200  priv->current = length;
201  priv->current_match = priv->current;
202  }
203 
204  /* Before moving into the history, save the current command-line. */
205  if (priv->current_match == length) {
206  g_free(priv->command_line);
207  priv->command_line = g_strdup(current_input);
208  }
209 
210  size_t i = 0;
211  const char* command = NULL;
212  while (i < length) {
213  if (priv->reset == true || next == false) {
214  if (priv->current < 1) {
215  priv->reset = false;
216  priv->current = priv->current_match;
217  return NULL;
218  } else {
219  --priv->current;
220  }
221  } else if (next == true) {
222  if (priv->current + 1 >= length) {
223  /* At the bottom of the history, return what the command-line was. */
224  priv->current_match = length;
225  priv->current = priv->current_match;
226  return priv->command_line;
227  } else {
228  ++priv->current;
229  }
230  } else {
231  return NULL;
232  }
233 
234  command = girara_list_nth(list, priv->current);
235  if (command == NULL) {
236  return NULL;
237  }
238 
239  /* Only match history items starting with what was on the command-line. */
240  if (g_str_has_prefix(command, priv->command_line)) {
241  priv->reset = false;
242  priv->current_match = priv->current;
243  break;
244  }
245 
246  ++i;
247  }
248 
249  if (i == length) {
250  return NULL;
251  }
252 
253  return command;
254 }
255 
256 static const char*
257 ih_next(GiraraInputHistory* history, const char* current_input)
258 {
259  return find_next(history, current_input, true);
260 }
261 
262 static const char*
263 ih_previous(GiraraInputHistory* history, const char* current_input)
264 {
265  return find_next(history, current_input, false);
266 }
267 
268 static void
269 ih_reset(GiraraInputHistory* history)
270 {
272  priv->reset = true;
273 
274  if (priv->io != NULL) {
275  girara_list_t* list = girara_input_history_list(history);
276  if (list == NULL) {
277  return;
278  }
279  girara_list_clear(list);
280 
281  girara_list_t* newlist = girara_input_history_io_read(priv->io);
282  if (newlist != NULL) {
283  GIRARA_LIST_FOREACH(newlist, const char*, iter, data)
284  girara_list_append(list, g_strdup(data));
285  GIRARA_LIST_FOREACH_END(newlist, const char*, iter, data);
286  girara_list_free(newlist);
287  }
288  }
289 }
290 
291 /* Wrapper functions for the members */
292 
293 void
294 girara_input_history_append(GiraraInputHistory* history, const char* input)
295 {
296  g_return_if_fail(GIRARA_IS_INPUT_HISTORY(history) == true);
297  GIRARA_INPUT_HISTORY_GET_CLASS(history)->append(history, input);
298 }
299 
300 girara_list_t*
301 girara_input_history_list(GiraraInputHistory* history)
302 {
303  g_return_val_if_fail(GIRARA_IS_INPUT_HISTORY(history) == true, NULL);
304  return GIRARA_INPUT_HISTORY_GET_CLASS(history)->list(history);
305 }
306 
307 const char*
308 girara_input_history_next(GiraraInputHistory* history, const char* current_input)
309 {
310  g_return_val_if_fail(GIRARA_IS_INPUT_HISTORY(history) == true, NULL);
311  return GIRARA_INPUT_HISTORY_GET_CLASS(history)->next(history, current_input);
312 }
313 
314 const char*
315 girara_input_history_previous(GiraraInputHistory* history, const char* current_input)
316 {
317  g_return_val_if_fail(GIRARA_IS_INPUT_HISTORY(history) == true, NULL);
318  return GIRARA_INPUT_HISTORY_GET_CLASS(history)->previous(history, current_input);
319 }
320 
321 void
322 girara_input_history_reset(GiraraInputHistory* history)
323 {
324  g_return_if_fail(GIRARA_IS_INPUT_HISTORY(history) == true);
325  GIRARA_INPUT_HISTORY_GET_CLASS(history)->reset(history);
326 }