1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- *
2  * gtksourcecompletioncontext.c
3  * This file is part of GtkSourceView
4  *
5  * Copyright (C) 2009 - Jesse van den Kieboom <jessevdk@gnome.org>
6  *
7  * GtkSourceView is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (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 GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 /**
23  * SECTION:completioncontext
24  * @title: GtkSourceCompletionContext
25  * @short_description: The context of a completion
26  *
27  * Initially, the completion window is hidden. For a completion to occur, it has
28  * to be activated. The different possible activations are listed in
29  * #GtkSourceCompletionActivation. When an activation occurs, a
30  * #GtkSourceCompletionContext object is created, and the eligible providers are
31  * asked to add proposals with gtk_source_completion_context_add_proposals().
32  *
33  * If no proposals are added, the completion window remains hidden, and the
34  * context is destroyed.
35  *
36  * On the other hand, if proposals are added, the completion window becomes
37  * visible, and the user can choose a proposal. If the user is not happy with
38  * the shown proposals, he or she can insert or delete characters, to modify the
39  * completion context and therefore hoping to see the proposal he or she wants.
40  * This means that when an insertion or deletion occurs in the #GtkTextBuffer
41  * when the completion window is visible, the eligible providers are again asked
42  * to add proposals. The #GtkSourceCompletionContext:activation remains the
43  * same in this case.
44  *
45  * When the completion window is hidden, the interactive completion is triggered
46  * only on insertion in the buffer, not on deletion. Once the completion window
47  * is visible, then on each insertion or deletion, there is a new population and
48  * the providers are asked to add proposals. If there are no more proposals, the
49  * completion window disappears. So if you want to keep the completion window
50  * visible, but there are no proposals, you can insert a dummy proposal named
51  * "No proposals". For example, the user types progressively the name of
52  * a function, and some proposals appear. The user types a bad character and
53  * there are no proposals anymore. What the user wants is to delete the last
54  * character, and see the previous proposals. If the completion window
55  * disappears, the previous proposals will not reappear on the character
56  * deletion.
57  *
58  * A #GtkTextIter is associated with the context, this is where the completion
59  * takes place. With this #GtkTextIter, you can get the associated
60  * #GtkTextBuffer with gtk_text_iter_get_buffer().
61  */
62 
63 #ifdef HAVE_CONFIG_H
64 #include <config.h>
65 #endif
66 
67 #include "gtksourcecompletioncontext.h"
68 #include "gtksourceview-enumtypes.h"
69 #include "gtksourcecompletionprovider.h"
70 #include "gtksourceview-i18n.h"
71 #include "gtksourcecompletion.h"
72 
73 struct _GtkSourceCompletionContextPrivate
74 {
75 	GtkSourceCompletion *completion;
76 
77 	GtkTextMark *mark;
78 	GtkSourceCompletionActivation activation;
79 };
80 
81 enum
82 {
83 	PROP_0,
84 	PROP_COMPLETION,
85 	PROP_ITER,
86 	PROP_ACTIVATION
87 };
88 
89 enum
90 {
91 	CANCELLED,
92 	N_SIGNALS
93 };
94 
95 static guint context_signals[N_SIGNALS];
96 
G_DEFINE_TYPE_WITH_PRIVATE(GtkSourceCompletionContext,gtk_source_completion_context,G_TYPE_INITIALLY_UNOWNED)97 G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceCompletionContext, gtk_source_completion_context, G_TYPE_INITIALLY_UNOWNED)
98 
99 static void
100 gtk_source_completion_context_dispose (GObject *object)
101 {
102 	GtkSourceCompletionContext *context = GTK_SOURCE_COMPLETION_CONTEXT (object);
103 
104 	if (context->priv->mark != NULL)
105 	{
106 		GtkTextBuffer *buffer = gtk_text_mark_get_buffer (context->priv->mark);
107 
108 		if (buffer != NULL)
109 		{
110 			gtk_text_buffer_delete_mark (buffer, context->priv->mark);
111 		}
112 
113 		g_object_unref (context->priv->mark);
114 		context->priv->mark = NULL;
115 	}
116 
117 	g_clear_object (&context->priv->completion);
118 
119 	G_OBJECT_CLASS (gtk_source_completion_context_parent_class)->dispose (object);
120 }
121 
122 static void
set_iter(GtkSourceCompletionContext * context,GtkTextIter * iter)123 set_iter (GtkSourceCompletionContext *context,
124 	  GtkTextIter                *iter)
125 {
126 	GtkTextBuffer *buffer;
127 
128 	buffer = gtk_text_iter_get_buffer (iter);
129 
130 	if (context->priv->mark != NULL)
131 	{
132 		GtkTextBuffer *old_buffer;
133 
134 		old_buffer = gtk_text_mark_get_buffer (context->priv->mark);
135 
136 		if (old_buffer != buffer)
137 		{
138 			g_object_unref (context->priv->mark);
139 			context->priv->mark = NULL;
140 		}
141 	}
142 
143 	if (context->priv->mark == NULL)
144 	{
145 		context->priv->mark = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
146 		g_object_ref (context->priv->mark);
147 	}
148 	else
149 	{
150 		gtk_text_buffer_move_mark (buffer, context->priv->mark, iter);
151 	}
152 
153 	g_object_notify (G_OBJECT (context), "iter");
154 }
155 
156 static void
gtk_source_completion_context_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)157 gtk_source_completion_context_set_property (GObject      *object,
158                                             guint         prop_id,
159                                             const GValue *value,
160                                             GParamSpec   *pspec)
161 {
162 	GtkSourceCompletionContext *context = GTK_SOURCE_COMPLETION_CONTEXT (object);
163 
164 	switch (prop_id)
165 	{
166 		case PROP_COMPLETION:
167 			context->priv->completion = g_value_dup_object (value);
168 			break;
169 
170 		case PROP_ITER:
171 			set_iter (context, g_value_get_boxed (value));
172 			break;
173 
174 		case PROP_ACTIVATION:
175 			context->priv->activation = g_value_get_flags (value);
176 			break;
177 
178 		default:
179 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
180 	}
181 }
182 
183 static void
gtk_source_completion_context_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)184 gtk_source_completion_context_get_property (GObject    *object,
185                                             guint       prop_id,
186                                             GValue     *value,
187                                             GParamSpec *pspec)
188 {
189 	GtkSourceCompletionContext *context = GTK_SOURCE_COMPLETION_CONTEXT (object);
190 
191 	switch (prop_id)
192 	{
193 		case PROP_COMPLETION:
194 			g_value_set_object (value, context->priv->completion);
195 			break;
196 
197 		case PROP_ITER:
198 			{
199 				GtkTextIter iter;
200 
201 				if (gtk_source_completion_context_get_iter (context, &iter))
202 				{
203 					g_value_set_boxed (value, &iter);
204 				}
205 			}
206 			break;
207 
208 		case PROP_ACTIVATION:
209 			g_value_set_flags (value, context->priv->activation);
210 			break;
211 
212 		default:
213 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214 	}
215 }
216 
217 static void
gtk_source_completion_context_class_init(GtkSourceCompletionContextClass * klass)218 gtk_source_completion_context_class_init (GtkSourceCompletionContextClass *klass)
219 {
220 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
221 
222 	object_class->set_property = gtk_source_completion_context_set_property;
223 	object_class->get_property = gtk_source_completion_context_get_property;
224 	object_class->dispose = gtk_source_completion_context_dispose;
225 
226 	/**
227 	 * GtkSourceCompletionContext::cancelled:
228 	 *
229 	 * Emitted when the current population of proposals has been cancelled.
230 	 * Providers adding proposals asynchronously should connect to this signal
231 	 * to know when to cancel running proposal queries.
232 	 **/
233 	context_signals[CANCELLED] =
234 		g_signal_new ("cancelled",
235 		              G_TYPE_FROM_CLASS (klass),
236 		              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
237 		              G_STRUCT_OFFSET (GtkSourceCompletionContextClass, cancelled),
238 		              NULL, NULL, NULL,
239 		              G_TYPE_NONE, 0);
240 
241 	/**
242 	 * GtkSourceCompletionContext:completion:
243 	 *
244 	 * The #GtkSourceCompletion associated with the context.
245 	 **/
246 	g_object_class_install_property (object_class,
247 	                                 PROP_COMPLETION,
248 	                                 g_param_spec_object ("completion",
249 	                                                      "Completion",
250 	                                                      "The completion object to which the context belongs",
251 	                                                      GTK_SOURCE_TYPE_COMPLETION,
252 	                                                      G_PARAM_READWRITE |
253 							      G_PARAM_CONSTRUCT_ONLY |
254 							      G_PARAM_STATIC_STRINGS));
255 
256 	/**
257 	 * GtkSourceCompletionContext:iter:
258 	 *
259 	 * The #GtkTextIter at which the completion is invoked.
260 	 **/
261 	g_object_class_install_property (object_class,
262 	                                 PROP_ITER,
263 					 g_param_spec_boxed ("iter",
264 							     "Iterator",
265 							     "The GtkTextIter at which the completion was invoked",
266 							     GTK_TYPE_TEXT_ITER,
267 							     G_PARAM_READWRITE |
268 							     G_PARAM_STATIC_STRINGS));
269 
270 	/**
271 	 * GtkSourceCompletionContext:activation:
272 	 *
273 	 * The completion activation
274 	 **/
275 	g_object_class_install_property (object_class,
276 	                                 PROP_ACTIVATION,
277 	                                 g_param_spec_flags ("activation",
278 	                                                     "Activation",
279 	                                                     "The type of activation",
280 	                                                     GTK_SOURCE_TYPE_COMPLETION_ACTIVATION,
281 	                                                     GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED,
282 	                                                     G_PARAM_READWRITE |
283 							     G_PARAM_CONSTRUCT |
284 							     G_PARAM_STATIC_STRINGS));
285 }
286 
287 static void
gtk_source_completion_context_init(GtkSourceCompletionContext * context)288 gtk_source_completion_context_init (GtkSourceCompletionContext *context)
289 {
290 	context->priv = gtk_source_completion_context_get_instance_private (context);
291 }
292 
293 /**
294  * gtk_source_completion_context_add_proposals:
295  * @context: a #GtkSourceCompletionContext.
296  * @provider: a #GtkSourceCompletionProvider.
297  * @proposals: (nullable) (element-type GtkSource.CompletionProposal): The list of proposals to add.
298  * @finished: Whether the provider is finished adding proposals.
299  *
300  * Providers can use this function to add proposals to the completion. They
301  * can do so asynchronously by means of the @finished argument. Providers must
302  * ensure that they always call this function with @finished set to %TRUE
303  * once each population (even if no proposals need to be added).
304  * Population occurs when the gtk_source_completion_provider_populate()
305  * function is called.
306  **/
307 void
gtk_source_completion_context_add_proposals(GtkSourceCompletionContext * context,GtkSourceCompletionProvider * provider,GList * proposals,gboolean finished)308 gtk_source_completion_context_add_proposals (GtkSourceCompletionContext  *context,
309                                              GtkSourceCompletionProvider *provider,
310                                              GList                       *proposals,
311                                              gboolean                     finished)
312 {
313 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
314 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider));
315 
316 	_gtk_source_completion_add_proposals (context->priv->completion,
317 	                                      context,
318 	                                      provider,
319 	                                      proposals,
320 	                                      finished);
321 }
322 
323 /**
324  * gtk_source_completion_context_get_iter:
325  * @context: a #GtkSourceCompletionContext.
326  * @iter: (out): a #GtkTextIter.
327  *
328  * Get the iter at which the completion was invoked. Providers can use this
329  * to determine how and if to match proposals.
330  *
331  * Returns: %TRUE if @iter is correctly set, %FALSE otherwise.
332  **/
333 gboolean
gtk_source_completion_context_get_iter(GtkSourceCompletionContext * context,GtkTextIter * iter)334 gtk_source_completion_context_get_iter (GtkSourceCompletionContext *context,
335                                         GtkTextIter                *iter)
336 {
337 	GtkTextBuffer *mark_buffer;
338 	GtkSourceView *view;
339 	GtkTextBuffer *completion_buffer;
340 
341 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
342 
343 	if (context->priv->mark == NULL)
344 	{
345 		/* This should never happen: context should be always be created
346 		   providing a position iter */
347 		g_warning ("Completion context without mark");
348 		return FALSE;
349 	}
350 
351 	mark_buffer = gtk_text_mark_get_buffer (context->priv->mark);
352 
353 	if (mark_buffer == NULL)
354 	{
355 		return FALSE;
356 	}
357 
358 	view = gtk_source_completion_get_view (context->priv->completion);
359 	if (view == NULL)
360 	{
361 		return FALSE;
362 	}
363 
364 	completion_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
365 
366 	if (completion_buffer != mark_buffer)
367 	{
368 		return FALSE;
369 	}
370 
371 	gtk_text_buffer_get_iter_at_mark (mark_buffer, iter, context->priv->mark);
372 	return TRUE;
373 }
374 
375 /**
376  * gtk_source_completion_context_get_activation:
377  * @context: a #GtkSourceCompletionContext.
378  *
379  * Get the context activation.
380  *
381  * Returns: The context activation.
382  */
383 GtkSourceCompletionActivation
gtk_source_completion_context_get_activation(GtkSourceCompletionContext * context)384 gtk_source_completion_context_get_activation (GtkSourceCompletionContext *context)
385 {
386 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context),
387 			      GTK_SOURCE_COMPLETION_ACTIVATION_NONE);
388 
389 	return context->priv->activation;
390 }
391 
392 void
_gtk_source_completion_context_cancel(GtkSourceCompletionContext * context)393 _gtk_source_completion_context_cancel (GtkSourceCompletionContext *context)
394 {
395 	g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
396 
397 	g_signal_emit (context, context_signals[CANCELLED], 0);
398 }
399 
400 GtkSourceCompletionContext *
_gtk_source_completion_context_new(GtkSourceCompletion * completion,GtkTextIter * position)401 _gtk_source_completion_context_new (GtkSourceCompletion *completion,
402 				    GtkTextIter         *position)
403 {
404 	g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION (completion), NULL);
405 	g_return_val_if_fail (position != NULL, NULL);
406 
407 	return g_object_new (GTK_SOURCE_TYPE_COMPLETION_CONTEXT,
408 	                     "completion", completion,
409 	                     "iter", position,
410 	                      NULL);
411 }
412