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