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