1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 * anjuta-completion.c
4 * Copyright (C) 2013 Carl-Anton Ingmarsson <carlantoni@gnome.org>
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * anjuta is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.";
18 */
19
20 #include <string.h>
21
22 #include "anjuta-completion.h"
23
24
25 struct _AnjutaCompletionPrivate
26 {
27 GPtrArray* items;
28 gboolean items_sorted;
29
30 char* last_complete;
31 gint last_complete_start;
32 gint last_complete_end;
33
34 AnjutaCompletionNameFunc name_func;
35 GDestroyNotify item_destroy_func;
36
37 AnjutaCompletionFilterFunc filter_func;
38 void* filter_func_user_data;
39
40 /* Properties */
41 gboolean case_sensitive;
42 };
43
44 enum
45 {
46 PROP_0,
47 PROP_CASE_SENSITIVE,
48 PROP_LAST
49 };
50 static GParamSpec* properties[PROP_LAST];
51
52 G_DEFINE_TYPE (AnjutaCompletion, anjuta_completion, G_TYPE_OBJECT);
53
54 static inline
anjuta_completion_default_name_func(const void * item)55 const char* anjuta_completion_default_name_func (const void* item)
56 {
57 return (const char*)item;
58 }
59
60 static gint
anjuta_completion_item_sort_func(gconstpointer a,gconstpointer b,void * user_data)61 anjuta_completion_item_sort_func (gconstpointer a, gconstpointer b,
62 void* user_data)
63 {
64 AnjutaCompletion* self = ANJUTA_COMPLETION (user_data);
65
66 const char* name_a;
67 const char* name_b;
68
69 name_a = self->priv->name_func (*(const void**)a);
70 name_b = self->priv->name_func (*(const void**)b);
71
72 if (self->priv->case_sensitive)
73 return strcmp (name_a, name_b);
74 else
75 return g_ascii_strcasecmp (name_a, name_b);
76 }
77
78 /**
79 * anjuta_completion_complete:
80 * @self: A #AnjutaCompletion
81 * @prefix: The prefix to complete against.
82 *
83 * Returns: (transfer container): The list of completions that matched
84 * @prefix.
85 */
86 GList*
anjuta_completion_complete(AnjutaCompletion * self,const char * prefix,gint max_completions)87 anjuta_completion_complete (AnjutaCompletion* self,
88 const char* prefix,
89 gint max_completions)
90
91 {
92 gint start, end;
93 gint l, r;
94 gint (*ncmp_func)(const char* s1, const char* s2, gsize n);
95 gint first_match, last_match;
96 GList* completions = NULL;
97
98 g_return_val_if_fail (ANJUTA_IS_COMPLETION (self), NULL);
99 g_return_val_if_fail (prefix, NULL);
100
101 /* Start from old completion if possible */
102 if (self->priv->last_complete &&
103 self->priv->items_sorted &&
104 g_str_has_prefix (prefix, self->priv->last_complete))
105 {
106 start = self->priv->last_complete_start;
107 end = self->priv->last_complete_end;
108 }
109 else
110 {
111 start = 0;
112 end = self->priv->items->len - 1;
113 }
114
115 /* Free last complete */
116 if (self->priv->last_complete)
117 {
118 g_free (self->priv->last_complete);
119 self->priv->last_complete = NULL;
120 }
121
122 /* Sort the items if they're not already sorted */
123 if (!self->priv->items_sorted)
124 {
125 g_ptr_array_sort_with_data (self->priv->items, anjuta_completion_item_sort_func,
126 self);
127 self->priv->items_sorted = TRUE;
128 }
129
130 ncmp_func = self->priv->case_sensitive ? strncmp : g_ascii_strncasecmp;
131
132 /* Do a binary search to find the first match */
133 for (l = start, r = end; l < r;)
134 {
135 gint mid;
136 void* item;
137 const char* name;
138 gint cmp;
139
140 mid = l + (r - l) / 2;
141 item = g_ptr_array_index (self->priv->items, mid);
142 name = self->priv->name_func (item);
143
144 cmp = ncmp_func (prefix, name, strlen (prefix));
145 if (cmp > 0)
146 l = mid + 1;
147 else if (cmp < 0)
148 r = mid - 1;
149 else
150 r = mid;
151 }
152 first_match = l;
153
154 /* Do a binary search to find the last match */
155 for (l = first_match, r = end; l < r;)
156 {
157 gint mid;
158 void* item;
159 const char* name;
160 gint cmp;
161
162 mid = l + ((r - l) / 2) + 1;
163 item = g_ptr_array_index (self->priv->items, mid);
164 name = self->priv->name_func (item);
165
166 cmp = ncmp_func (prefix, name, strlen (prefix));
167 if (cmp == 0)
168 l = mid;
169 else
170 r = mid - 1;
171
172 }
173 last_match = r;
174
175 if (first_match <= last_match)
176 {
177 gint i;
178 gint n_completions;
179
180 n_completions = 0;
181 for (i = first_match; i <= last_match; i++)
182 {
183 void* item;
184 const char* name;
185
186 item = g_ptr_array_index (self->priv->items, i);
187 name = self->priv->name_func (item);
188
189 if (ncmp_func (prefix, name, strlen (prefix)) != 0)
190 break;
191
192 if (self->priv->filter_func &&
193 !self->priv->filter_func (item, self->priv->filter_func_user_data))
194 continue;
195
196 completions = g_list_prepend (completions, item);
197 n_completions++;
198 if (max_completions > 0 && n_completions == max_completions)
199 break;
200 }
201 completions = g_list_reverse (completions);
202 }
203
204 self->priv->last_complete = g_strdup (prefix);
205 self->priv->last_complete_start = first_match;
206 self->priv->last_complete_end = last_match;
207
208 return completions;
209 }
210
211 static void
anjuta_completion_clear_items(AnjutaCompletion * self)212 anjuta_completion_clear_items (AnjutaCompletion* self)
213 {
214 if (self->priv->item_destroy_func)
215 {
216 gint i;
217
218 for (i = 0; i < self->priv->items->len; i++)
219 {
220 void* item = g_ptr_array_index (self->priv->items, i);
221
222 self->priv->item_destroy_func (item);
223 }
224 }
225 g_ptr_array_unref (self->priv->items);
226
227 g_free (self->priv->last_complete);
228 self->priv->last_complete = NULL;
229 }
230
231 /**
232 * anjuta_completion_clear:
233 * @self: a #AnjutaCompletion
234 *
235 * Clear all items added to the completion.
236 */
237 void
anjuta_completion_clear(AnjutaCompletion * self)238 anjuta_completion_clear (AnjutaCompletion* self)
239 {
240 g_return_if_fail (ANJUTA_IS_COMPLETION (self));
241
242 anjuta_completion_clear_items (self);
243 self->priv->items = g_ptr_array_new ();
244 }
245
246 /**
247 * anjuta_completion_add_item:
248 * @self: a #AnjutaCompletion
249 * @item: the item to be added.
250 *
251 * Add an item to the completion.
252 */
253 void
anjuta_completion_add_item(AnjutaCompletion * self,void * item)254 anjuta_completion_add_item (AnjutaCompletion* self,
255 void* item)
256 {
257 g_ptr_array_add (self->priv->items, item);
258
259 self->priv->items_sorted = FALSE;
260 }
261
262 void
anjuta_completion_set_filter_func(AnjutaCompletion * self,AnjutaCompletionFilterFunc filter_func,void * user_data)263 anjuta_completion_set_filter_func (AnjutaCompletion* self,
264 AnjutaCompletionFilterFunc filter_func,
265 void* user_data)
266 {
267 g_return_if_fail (ANJUTA_IS_COMPLETION (self));
268
269 self->priv->filter_func = filter_func;
270 self->priv->filter_func_user_data = user_data;
271 }
272
273 /**
274 * anjuta_completion_set_item_destroy_func:
275 * @self: a #AnjutaCompletion
276 * @item_destroy_func: (allow-none): the function to be called on
277 * the added items when the #AnjutaCompletion object is destroyed.
278 */
279 void
anjuta_completion_set_item_destroy_func(AnjutaCompletion * self,GDestroyNotify item_destroy_func)280 anjuta_completion_set_item_destroy_func (AnjutaCompletion* self,
281 GDestroyNotify item_destroy_func)
282 {
283 g_return_if_fail (ANJUTA_IS_COMPLETION (self));
284
285 self->priv->item_destroy_func = item_destroy_func;
286 }
287
288 gboolean
anjuta_completion_get_case_sensitive(AnjutaCompletion * self)289 anjuta_completion_get_case_sensitive (AnjutaCompletion* self)
290 {
291 g_return_val_if_fail (ANJUTA_IS_COMPLETION (self), FALSE);
292
293 return self->priv->case_sensitive;
294 }
295
296 void
anjuta_completion_set_case_sensitive(AnjutaCompletion * self,gboolean case_sensitive)297 anjuta_completion_set_case_sensitive (AnjutaCompletion* self,
298 gboolean case_sensitive)
299 {
300 g_return_if_fail (ANJUTA_IS_COMPLETION (self));
301
302 if (self->priv->case_sensitive == case_sensitive)
303 return;
304
305 g_free (self->priv->last_complete);
306 self->priv->last_complete = NULL;
307
308 self->priv->items_sorted = FALSE;
309
310 self->priv->case_sensitive = case_sensitive;
311 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CASE_SENSITIVE]);
312 }
313
314 AnjutaCompletion*
anjuta_completion_new(AnjutaCompletionNameFunc name_func)315 anjuta_completion_new (AnjutaCompletionNameFunc name_func)
316 {
317 AnjutaCompletion* self;
318
319 self = g_object_new (ANJUTA_TYPE_COMPLETION, NULL);
320
321 if (name_func)
322 self->priv->name_func = name_func;
323
324 return self;
325 }
326
327 static void
anjuta_completion_init(AnjutaCompletion * self)328 anjuta_completion_init (AnjutaCompletion* self)
329 {
330 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ANJUTA_TYPE_COMPLETION,
331 AnjutaCompletionPrivate);
332
333 self->priv->name_func = anjuta_completion_default_name_func;
334 self->priv->case_sensitive = TRUE;
335
336 self->priv->items = g_ptr_array_new ();
337 }
338
339 static void
anjuta_completion_finalize(GObject * object)340 anjuta_completion_finalize (GObject *object)
341 {
342 AnjutaCompletion* self = ANJUTA_COMPLETION (object);
343
344 anjuta_completion_clear_items (self);
345
346 G_OBJECT_CLASS (anjuta_completion_parent_class)->finalize (object);
347 }
348
349 static void
anjuta_completion_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)350 anjuta_completion_get_property (GObject* object, guint prop_id, GValue* value,
351 GParamSpec* pspec)
352 {
353 AnjutaCompletion* self = ANJUTA_COMPLETION (object);
354
355 switch (prop_id)
356 {
357 case PROP_CASE_SENSITIVE:
358 g_value_set_boolean (value,
359 anjuta_completion_get_case_sensitive (self));
360 break;
361
362 default:
363 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
364 }
365 }
366
367 static void
anjuta_completion_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)368 anjuta_completion_set_property (GObject* object, guint prop_id,
369 const GValue* value, GParamSpec* pspec)
370 {
371 AnjutaCompletion* self = ANJUTA_COMPLETION (object);
372
373 switch (prop_id)
374 {
375 case PROP_CASE_SENSITIVE:
376 anjuta_completion_set_case_sensitive (self, g_value_get_boolean (value));
377 break;
378
379 default:
380 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
381 }
382 }
383
384 static void
anjuta_completion_class_init(AnjutaCompletionClass * klass)385 anjuta_completion_class_init (AnjutaCompletionClass *klass)
386 {
387 GObjectClass* object_class = G_OBJECT_CLASS (klass);
388
389 object_class->get_property = anjuta_completion_get_property;
390 object_class->set_property = anjuta_completion_set_property;
391 object_class->finalize = anjuta_completion_finalize;
392
393 properties[PROP_CASE_SENSITIVE] =
394 g_param_spec_boolean ("case-sensitive", "Case sensitive", "Case sensitive",
395 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
396
397 g_object_class_install_properties (object_class, PROP_LAST, properties);
398 g_type_class_add_private (klass, sizeof (AnjutaCompletionPrivate));
399 }
400