1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
2 * gtksourcecompletionproviderwords.c
3 * This file is part of gtksourceview
4 *
5 * Copyright (C) 2009 - Jesse van den Kieboom
6 *
7 * gtksourceview is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * gtksourceview is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with gtksourceview; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301 USA
21 */
22
23 #include "gtksourcecompletionwords.h"
24 #include "gtksourcecompletionwordslibrary.h"
25 #include "gtksourcecompletionwordsbuffer.h"
26 #include "gtksourcecompletionwordsutils.h"
27
28 #include <gtksourceview/gtksourcecompletion.h>
29 #include <gtksourceview/gtksourceview-i18n.h>
30 #include <string.h>
31
32 #define GTK_SOURCE_COMPLETION_WORDS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GTK_TYPE_SOURCE_COMPLETION_WORDS, GtkSourceCompletionWordsPrivate))
33
34 #define BUFFER_KEY "GtkSourceCompletionWordsBufferKey"
35
36 #define GET_WORDS_BUFFER(buf) (((BufferBinding *)g_object_get_data(G_OBJECT(buf), BUFFER_KEY))->buffer)
37
38 enum
39 {
40 PROP_0,
41
42 PROP_NAME,
43 PROP_ICON,
44 PROP_PROPOSALS_BATCH_SIZE,
45 PROP_SCAN_BATCH_SIZE,
46 PROP_MINIMUM_WORD_SIZE,
47 PROP_INTERACTIVE_DELAY,
48 PROP_PRIORITY
49 };
50
51 struct _GtkSourceCompletionWordsPrivate
52 {
53 gchar *name;
54 GdkPixbuf *icon;
55
56 gchar *word;
57 gint word_len;
58 guint idle_id;
59
60 GtkSourceCompletionContext *context;
61 GSequenceIter *populate_iter;
62
63 guint cancel_id;
64
65 guint proposals_batch_size;
66 guint scan_batch_size;
67 guint minimum_word_size;
68
69 GtkSourceCompletionWordsLibrary *library;
70 GList *buffers;
71
72 gint interactive_delay;
73 gint priority;
74 };
75
76 typedef struct
77 {
78 GObjectClass parent_class;
79 } GscProposalWordsClass;
80
81 typedef struct
82 {
83 GtkSourceCompletionWords *words;
84 GtkSourceCompletionWordsBuffer *buffer;
85 } BufferBinding;
86
87 static void gtk_source_completion_words_iface_init (GtkSourceCompletionProviderIface *iface);
88
89 GType gsc_proposal_words_get_type (void);
90
G_DEFINE_TYPE_WITH_CODE(GtkSourceCompletionWords,gtk_source_completion_words,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GTK_TYPE_SOURCE_COMPLETION_PROVIDER,gtk_source_completion_words_iface_init))91 G_DEFINE_TYPE_WITH_CODE (GtkSourceCompletionWords,
92 gtk_source_completion_words,
93 G_TYPE_OBJECT,
94 G_IMPLEMENT_INTERFACE (GTK_TYPE_SOURCE_COMPLETION_PROVIDER,
95 gtk_source_completion_words_iface_init))
96
97 static gchar *
98 gtk_source_completion_words_get_name (GtkSourceCompletionProvider *self)
99 {
100 return g_strdup (GTK_SOURCE_COMPLETION_WORDS (self)->priv->name);
101 }
102
103 static GdkPixbuf *
gtk_source_completion_words_get_icon(GtkSourceCompletionProvider * self)104 gtk_source_completion_words_get_icon (GtkSourceCompletionProvider *self)
105 {
106 return GTK_SOURCE_COMPLETION_WORDS (self)->priv->icon;
107 }
108
109 static void
population_finished(GtkSourceCompletionWords * words)110 population_finished (GtkSourceCompletionWords *words)
111 {
112 if (words->priv->idle_id != 0)
113 {
114 g_source_remove (words->priv->idle_id);
115 words->priv->idle_id = 0;
116 }
117
118 g_free (words->priv->word);
119 words->priv->word = NULL;
120
121 if (words->priv->context != NULL)
122 {
123 if (words->priv->cancel_id)
124 {
125 g_signal_handler_disconnect (words->priv->context,
126 words->priv->cancel_id);
127 words->priv->cancel_id = 0;
128 }
129
130 g_object_unref (words->priv->context);
131 words->priv->context = NULL;
132 }
133 }
134
135 static gboolean
add_in_idle(GtkSourceCompletionWords * words)136 add_in_idle (GtkSourceCompletionWords *words)
137 {
138 guint idx = 0;
139 GList *ret = NULL;
140 gboolean finished;
141
142 if (words->priv->populate_iter == NULL)
143 {
144 words->priv->populate_iter =
145 gtk_source_completion_words_library_find_first (words->priv->library,
146 words->priv->word,
147 words->priv->word_len);
148 }
149
150 while (idx < words->priv->proposals_batch_size &&
151 words->priv->populate_iter)
152 {
153 GtkSourceCompletionWordsProposal *proposal =
154 gtk_source_completion_words_library_get_proposal (words->priv->populate_iter);
155
156 /* Only add non-exact matches */
157 if (strcmp (gtk_source_completion_words_proposal_get_word (proposal),
158 words->priv->word) != 0)
159 {
160 ret = g_list_prepend (ret, proposal);
161 }
162
163 words->priv->populate_iter =
164 gtk_source_completion_words_library_find_next (words->priv->populate_iter,
165 words->priv->word,
166 words->priv->word_len);
167 ++idx;
168 }
169
170 ret = g_list_reverse (ret);
171 finished = words->priv->populate_iter == NULL;
172
173 gtk_source_completion_context_add_proposals (words->priv->context,
174 GTK_SOURCE_COMPLETION_PROVIDER (words),
175 ret,
176 finished);
177
178 if (finished)
179 {
180 gtk_source_completion_words_library_unlock (words->priv->library);
181 population_finished (words);
182 }
183
184 return !finished;
185 }
186
187 static gboolean
valid_word_char(gunichar ch,gpointer data)188 valid_word_char (gunichar ch,
189 gpointer data)
190 {
191 return g_unichar_isprint (ch) && (ch == '_' || g_unichar_isalnum (ch));
192 }
193
194 static gboolean
valid_start_char(gunichar ch,gpointer data)195 valid_start_char (gunichar ch,
196 gpointer data)
197 {
198 return !g_unichar_isdigit (ch);
199 }
200
201 static gchar *
get_word_at_iter(GtkTextIter * iter,CharacterCheck valid,CharacterCheck valid_start,gpointer data)202 get_word_at_iter (GtkTextIter *iter,
203 CharacterCheck valid,
204 CharacterCheck valid_start,
205 gpointer data)
206 {
207 GtkTextIter end = *iter;
208
209 if (!gtk_source_completion_words_utils_forward_word_end (iter, valid, data) ||
210 !gtk_text_iter_equal (iter, &end))
211 {
212 return NULL;
213 }
214
215 if (!gtk_source_completion_words_utils_backward_word_start (iter,
216 valid,
217 valid_start,
218 data))
219 {
220 return NULL;
221 }
222
223 if (gtk_text_iter_equal (iter, &end))
224 {
225 return NULL;
226 }
227 else
228 {
229 return gtk_text_iter_get_text (iter, &end);
230 }
231 }
232
233 static gboolean
gtk_source_completion_words_match(GtkSourceCompletionProvider * provider,GtkSourceCompletionContext * context)234 gtk_source_completion_words_match (GtkSourceCompletionProvider *provider,
235 GtkSourceCompletionContext *context)
236 {
237 return TRUE;
238 }
239
240 static void
gtk_source_completion_words_populate(GtkSourceCompletionProvider * provider,GtkSourceCompletionContext * context)241 gtk_source_completion_words_populate (GtkSourceCompletionProvider *provider,
242 GtkSourceCompletionContext *context)
243 {
244 GtkSourceCompletionWords *words = GTK_SOURCE_COMPLETION_WORDS (provider);
245 GtkTextIter iter;
246 gchar *word;
247 GtkTextBuffer *buffer;
248 GtkSourceCompletionWordsBuffer *buf;
249
250 gtk_source_completion_context_get_iter (context, &iter);
251 buffer = gtk_text_iter_get_buffer (&iter);
252
253 g_free (words->priv->word);
254 words->priv->word = NULL;
255
256 word = get_word_at_iter (&iter,
257 valid_word_char,
258 valid_start_char,
259 words);
260
261 if (word == NULL ||
262 g_utf8_strlen (word, -1) < words->priv->minimum_word_size)
263 {
264 g_free (word);
265 gtk_source_completion_context_add_proposals (context,
266 provider,
267 NULL,
268 TRUE);
269 return;
270 }
271
272 words->priv->cancel_id =
273 g_signal_connect_swapped (context,
274 "cancelled",
275 G_CALLBACK (population_finished),
276 provider);
277
278 words->priv->context = g_object_ref (context);
279
280 words->priv->word = word;
281 words->priv->word_len = strlen (word);
282
283 buf = GET_WORDS_BUFFER (buffer);
284 gtk_text_buffer_move_mark (buffer,
285 gtk_source_completion_words_buffer_get_mark (buf),
286 &iter);
287
288 /* Do first right now */
289 if (add_in_idle (words))
290 {
291 gtk_source_completion_words_library_lock (words->priv->library);
292 words->priv->idle_id = g_idle_add ((GSourceFunc)add_in_idle,
293 words);
294 }
295 }
296
297 static void
remove_buffer(BufferBinding * binding)298 remove_buffer (BufferBinding *binding)
299 {
300 g_object_set_data (G_OBJECT (gtk_source_completion_words_buffer_get_buffer (binding->buffer)),
301 BUFFER_KEY,
302 NULL);
303 }
304
305 static void
gtk_source_completion_words_dispose(GObject * object)306 gtk_source_completion_words_dispose (GObject *object)
307 {
308 GtkSourceCompletionWords *provider = GTK_SOURCE_COMPLETION_WORDS (object);
309 GList *cp;
310
311 population_finished (provider);
312
313 cp = g_list_copy (provider->priv->buffers);
314 g_list_foreach (cp, (GFunc)remove_buffer, NULL);
315
316 g_list_free (cp);
317 g_list_free (provider->priv->buffers);
318
319 g_free (provider->priv->name);
320 provider->priv->name = NULL;
321
322 if (provider->priv->icon)
323 {
324 g_object_unref (provider->priv->icon);
325 provider->priv->icon = NULL;
326 }
327
328 if (provider->priv->library)
329 {
330 g_object_unref (provider->priv->library);
331 provider->priv->library = NULL;
332 }
333
334 G_OBJECT_CLASS (gtk_source_completion_words_parent_class)->dispose (object);
335 }
336
337 static void
update_buffers_batch_size(GtkSourceCompletionWords * words)338 update_buffers_batch_size (GtkSourceCompletionWords *words)
339 {
340 GList *item;
341
342 for (item = words->priv->buffers; item; item = g_list_next (item))
343 {
344 BufferBinding *binding = (BufferBinding *)item->data;
345 gtk_source_completion_words_buffer_set_scan_batch_size (binding->buffer,
346 words->priv->scan_batch_size);
347 }
348 }
349
350 static void
update_buffers_minimum_word_size(GtkSourceCompletionWords * words)351 update_buffers_minimum_word_size (GtkSourceCompletionWords *words)
352 {
353 GList *item;
354
355 for (item = words->priv->buffers; item; item = g_list_next (item))
356 {
357 BufferBinding *binding = (BufferBinding *)item->data;
358 gtk_source_completion_words_buffer_set_minimum_word_size (binding->buffer,
359 words->priv->minimum_word_size);
360 }
361 }
362
363 static void
gtk_source_completion_words_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)364 gtk_source_completion_words_set_property (GObject *object,
365 guint prop_id,
366 const GValue *value,
367 GParamSpec *pspec)
368 {
369 GtkSourceCompletionWords *self = GTK_SOURCE_COMPLETION_WORDS (object);
370
371 switch (prop_id)
372 {
373 case PROP_NAME:
374 g_free (self->priv->name);
375 self->priv->name = g_value_dup_string (value);
376
377 if (self->priv->name == NULL)
378 {
379 self->priv->name = g_strdup (_("Document Words"));
380 }
381 break;
382 case PROP_ICON:
383 if (self->priv->icon)
384 {
385 g_object_unref (self->priv->icon);
386 }
387
388 self->priv->icon = g_value_dup_object (value);
389 break;
390 case PROP_PROPOSALS_BATCH_SIZE:
391 self->priv->proposals_batch_size = g_value_get_uint (value);
392 break;
393 case PROP_SCAN_BATCH_SIZE:
394 self->priv->scan_batch_size = g_value_get_uint (value);
395 update_buffers_batch_size (self);
396 break;
397 case PROP_MINIMUM_WORD_SIZE:
398 self->priv->minimum_word_size = g_value_get_uint (value);
399 update_buffers_minimum_word_size (self);
400 break;
401 case PROP_INTERACTIVE_DELAY:
402 self->priv->interactive_delay = g_value_get_int (value);
403 break;
404 case PROP_PRIORITY:
405 self->priv->priority = g_value_get_int (value);
406 break;
407 default:
408 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
409 break;
410 }
411 }
412
413 static void
gtk_source_completion_words_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)414 gtk_source_completion_words_get_property (GObject *object,
415 guint prop_id,
416 GValue *value,
417 GParamSpec *pspec)
418 {
419 GtkSourceCompletionWords *self = GTK_SOURCE_COMPLETION_WORDS (object);
420
421 switch (prop_id)
422 {
423 case PROP_NAME:
424 g_value_set_string (value, self->priv->name);
425 break;
426 case PROP_ICON:
427 g_value_set_object (value, self->priv->icon);
428 break;
429 case PROP_PROPOSALS_BATCH_SIZE:
430 g_value_set_uint (value, self->priv->proposals_batch_size);
431 break;
432 case PROP_SCAN_BATCH_SIZE:
433 g_value_set_uint (value, self->priv->scan_batch_size);
434 break;
435 case PROP_MINIMUM_WORD_SIZE:
436 g_value_set_uint (value, self->priv->minimum_word_size);
437 break;
438 case PROP_INTERACTIVE_DELAY:
439 g_value_set_int (value, self->priv->interactive_delay);
440 break;
441 case PROP_PRIORITY:
442 g_value_set_int (value, self->priv->priority);
443 break;
444 default:
445 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
446 break;
447 }
448 }
449
450 static void
gtk_source_completion_words_class_init(GtkSourceCompletionWordsClass * klass)451 gtk_source_completion_words_class_init (GtkSourceCompletionWordsClass *klass)
452 {
453 GObjectClass *object_class = G_OBJECT_CLASS (klass);
454
455 object_class->dispose = gtk_source_completion_words_dispose;
456
457 object_class->set_property = gtk_source_completion_words_set_property;
458 object_class->get_property = gtk_source_completion_words_get_property;
459
460 g_object_class_install_property (object_class,
461 PROP_NAME,
462 g_param_spec_string ("name",
463 _("Name"),
464 _("The provider name"),
465 NULL,
466 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
467
468 g_object_class_install_property (object_class,
469 PROP_ICON,
470 g_param_spec_object ("icon",
471 _("Icon"),
472 _("The provider icon"),
473 GDK_TYPE_PIXBUF,
474 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
475
476 g_object_class_install_property (object_class,
477 PROP_PROPOSALS_BATCH_SIZE,
478 g_param_spec_uint ("proposals-batch-size",
479 _("Proposals Batch Size"),
480 _("Number of proposals added in one batch"),
481 1,
482 G_MAXUINT,
483 300,
484 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
485
486 g_object_class_install_property (object_class,
487 PROP_SCAN_BATCH_SIZE,
488 g_param_spec_uint ("scan-batch-size",
489 _("Scan Batch Size"),
490 _("Number of lines scanned in one batch"),
491 1,
492 G_MAXUINT,
493 50,
494 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
495
496 g_object_class_install_property (object_class,
497 PROP_MINIMUM_WORD_SIZE,
498 g_param_spec_uint ("minimum-word-size",
499 _("Minimum Word Size"),
500 _("The minimum word size to complete"),
501 2,
502 G_MAXUINT,
503 2,
504 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
505
506 g_object_class_install_property (object_class,
507 PROP_INTERACTIVE_DELAY,
508 g_param_spec_int ("interactive-delay",
509 _("Interactive Delay"),
510 _("The delay before initiating interactive completion"),
511 -1,
512 G_MAXINT,
513 50,
514 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
515
516 g_object_class_install_property (object_class,
517 PROP_PRIORITY,
518 g_param_spec_int ("priority",
519 _("Priority"),
520 _("Provider priority"),
521 G_MININT,
522 G_MAXINT,
523 0,
524 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
525
526 g_type_class_add_private (object_class, sizeof(GtkSourceCompletionWordsPrivate));
527 }
528
529 static gboolean
gtk_source_completion_words_get_start_iter(GtkSourceCompletionProvider * provider,GtkSourceCompletionContext * context,GtkSourceCompletionProposal * proposal,GtkTextIter * iter)530 gtk_source_completion_words_get_start_iter (GtkSourceCompletionProvider *provider,
531 GtkSourceCompletionContext *context,
532 GtkSourceCompletionProposal *proposal,
533 GtkTextIter *iter)
534 {
535 GtkTextBuffer *buffer;
536 GtkSourceCompletionWordsBuffer *buf;
537 GtkTextIter it;
538
539 gtk_source_completion_context_get_iter (context, &it);
540
541 buffer = gtk_text_iter_get_buffer (&it);
542 buf = GET_WORDS_BUFFER (buffer);
543
544 gtk_text_buffer_get_iter_at_mark (buffer,
545 iter,
546 gtk_source_completion_words_buffer_get_mark (buf));
547 return TRUE;
548 }
549
550 static gint
gtk_source_completion_words_get_interactive_delay(GtkSourceCompletionProvider * provider)551 gtk_source_completion_words_get_interactive_delay (GtkSourceCompletionProvider *provider)
552 {
553 return GTK_SOURCE_COMPLETION_WORDS (provider)->priv->interactive_delay;
554 }
555
556 static gint
gtk_source_completion_words_get_priority(GtkSourceCompletionProvider * provider)557 gtk_source_completion_words_get_priority (GtkSourceCompletionProvider *provider)
558 {
559 return GTK_SOURCE_COMPLETION_WORDS (provider)->priv->priority;
560 }
561
562 static void
gtk_source_completion_words_iface_init(GtkSourceCompletionProviderIface * iface)563 gtk_source_completion_words_iface_init (GtkSourceCompletionProviderIface *iface)
564 {
565 iface->get_name = gtk_source_completion_words_get_name;
566 iface->get_icon = gtk_source_completion_words_get_icon;
567
568 iface->populate = gtk_source_completion_words_populate;
569 iface->match = gtk_source_completion_words_match;
570
571 iface->get_start_iter = gtk_source_completion_words_get_start_iter;
572 iface->get_interactive_delay = gtk_source_completion_words_get_interactive_delay;
573 iface->get_priority = gtk_source_completion_words_get_priority;
574 }
575
576 static void
gtk_source_completion_words_init(GtkSourceCompletionWords * self)577 gtk_source_completion_words_init (GtkSourceCompletionWords *self)
578 {
579 self->priv = GTK_SOURCE_COMPLETION_WORDS_GET_PRIVATE (self);
580
581 self->priv->library = gtk_source_completion_words_library_new ();
582 }
583
584 GtkSourceCompletionWords *
gtk_source_completion_words_new(const gchar * name,GdkPixbuf * icon)585 gtk_source_completion_words_new (const gchar *name,
586 GdkPixbuf *icon)
587 {
588 return g_object_new (GTK_TYPE_SOURCE_COMPLETION_WORDS,
589 "name", name,
590 "icon", icon,
591 NULL);
592 }
593
594 static void
buffer_destroyed(BufferBinding * binding)595 buffer_destroyed (BufferBinding *binding)
596 {
597 binding->words->priv->buffers = g_list_remove (binding->words->priv->buffers,
598 binding);
599 g_object_unref (binding->buffer);
600 g_slice_free (BufferBinding, binding);
601 }
602
603 void
gtk_source_completion_words_register(GtkSourceCompletionWords * words,GtkTextBuffer * buffer)604 gtk_source_completion_words_register (GtkSourceCompletionWords *words,
605 GtkTextBuffer *buffer)
606 {
607 GtkSourceCompletionWordsBuffer *buf;
608 BufferBinding *binding;
609
610 g_return_if_fail (GTK_IS_SOURCE_COMPLETION_WORDS (words));
611 g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
612
613 binding = g_object_get_data (G_OBJECT (buffer), BUFFER_KEY);
614
615 if (binding != NULL)
616 {
617 return;
618 }
619
620 buf = gtk_source_completion_words_buffer_new (words->priv->library,
621 buffer);
622
623 gtk_source_completion_words_buffer_set_scan_batch_size (buf,
624 words->priv->scan_batch_size);
625
626 gtk_source_completion_words_buffer_set_minimum_word_size (buf,
627 words->priv->minimum_word_size);
628
629 binding = g_slice_new (BufferBinding);
630 binding->words = words;
631 binding->buffer = buf;
632
633 g_object_set_data_full (G_OBJECT (buffer),
634 BUFFER_KEY,
635 binding,
636 (GDestroyNotify)buffer_destroyed);
637
638 words->priv->buffers = g_list_prepend (words->priv->buffers,
639 binding);
640 }
641
642 void
gtk_source_completion_words_unregister(GtkSourceCompletionWords * words,GtkTextBuffer * buffer)643 gtk_source_completion_words_unregister (GtkSourceCompletionWords *words,
644 GtkTextBuffer *buffer)
645 {
646 g_return_if_fail (GTK_IS_SOURCE_COMPLETION_WORDS (words));
647 g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
648
649 g_object_set_data (G_OBJECT (buffer), BUFFER_KEY, NULL);
650 }
651
652