1 /* ide-completion-provider.c
2  *
3  * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-completion-provider"
22 
23 #include "config.h"
24 
25 #include "ide-completion-context.h"
26 #include "ide-completion-private.h"
27 #include "ide-completion-proposal.h"
28 #include "ide-completion-provider.h"
29 #include "ide-completion-list-box-row.h"
30 
G_DEFINE_INTERFACE(IdeCompletionProvider,ide_completion_provider,G_TYPE_OBJECT)31 G_DEFINE_INTERFACE (IdeCompletionProvider, ide_completion_provider, G_TYPE_OBJECT)
32 
33 static void
34 ide_completion_provider_default_init (IdeCompletionProviderInterface *iface)
35 {
36 }
37 
38 /**
39  * ide_completion_provider_get_icon:
40  * @self: an #IdeCompletionProvider
41  *
42  * Gets the #GIcon to represent this provider. This may be used in UI
43  * to allow the user to filter the results to only those of this
44  * completion provider.
45  *
46  * Returns: (transfer full) (nullable): a #GIcon or %NULL.
47  *
48  * Since: 3.32
49  */
50 GIcon *
ide_completion_provider_get_icon(IdeCompletionProvider * self)51 ide_completion_provider_get_icon (IdeCompletionProvider *self)
52 {
53   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), NULL);
54 
55   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_icon)
56     return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_icon (self);
57 
58   return NULL;
59 }
60 
61 /**
62  * ide_completion_provider_get_priority:
63  * @self: an #IdeCompletionProvider
64  * @context: an #IdeCompletionContext
65  *
66  * Gets the priority for the completion provider.
67  *
68  * This value is used to group all of the providers proposals together
69  * when displayed, with relation to other providers.
70  *
71  * The @context is provided as some providers may want to lower their
72  * priority based on the position of the completion.
73  *
74  * Returns: an integer specific to the provider
75  *
76  * Since: 3.32
77  */
78 gint
ide_completion_provider_get_priority(IdeCompletionProvider * self,IdeCompletionContext * context)79 ide_completion_provider_get_priority (IdeCompletionProvider *self,
80                                       IdeCompletionContext  *context)
81 {
82   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), 0);
83   g_return_val_if_fail (IDE_IS_COMPLETION_CONTEXT (context), 0);
84 
85   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_priority)
86     return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_priority (self, context);
87 
88   return 0;
89 }
90 
91 /**
92  * ide_completion_provider_get_title:
93  * @self: an #IdeCompletionProvider
94  *
95  * Gets the title for the provider. This may be used in UI to give
96  * the user context about the type of results that are displayed.
97  *
98  * Returns: (transfer full) (nullable): a string or %NULL
99  *
100  * Since: 3.32
101  */
102 gchar *
ide_completion_provider_get_title(IdeCompletionProvider * self)103 ide_completion_provider_get_title (IdeCompletionProvider *self)
104 {
105   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), NULL);
106 
107   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_title)
108     return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_title (self);
109 
110   return NULL;
111 }
112 
113 /**
114  * ide_completion_provider_populate_async:
115  * @self: an #IdeCompletionProvider
116  * @context: the completion context
117  * @cancellable: (nullable): a #GCancellable, or %NULL
118  * @callback: (nullable) (scope async) (closure user_data): a #GAsyncReadyCallback
119  *   or %NULL. Called when the provider has completed loading proposals.
120  * @user_data: closure data for @callback
121  *
122  * Asynchronously requests the provider populate the contents.
123  *
124  * For completion providers that can provide intermediate results immediately,
125  * use ide_completion_context_set_proposals_for_provider() to notify of results
126  * while the async operation is in progress.
127  *
128  * Since: 3.32
129  */
130 void
ide_completion_provider_populate_async(IdeCompletionProvider * self,IdeCompletionContext * context,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)131 ide_completion_provider_populate_async (IdeCompletionProvider  *self,
132                                         IdeCompletionContext   *context,
133                                         GCancellable           *cancellable,
134                                         GAsyncReadyCallback     callback,
135                                         gpointer                user_data)
136 {
137   g_return_if_fail (IDE_IS_COMPLETION_PROVIDER (self));
138   g_return_if_fail (IDE_IS_COMPLETION_CONTEXT (context));
139   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
140 
141   IDE_COMPLETION_PROVIDER_GET_IFACE (self)->populate_async (self, context, cancellable, callback, user_data);
142 }
143 
144 /**
145  * ide_completion_provider_populate_finish:
146  * @self: an #IdeCompletionProvider
147  * @result: a #GAsyncResult provided to callback
148  * @error: a location for a GError, or %NULL
149  *
150  * Returns: (transfer full): a #GListModel of #IdeCompletionProposal
151  *
152  * Since: 3.32
153  */
154 GListModel *
ide_completion_provider_populate_finish(IdeCompletionProvider * self,GAsyncResult * result,GError ** error)155 ide_completion_provider_populate_finish (IdeCompletionProvider  *self,
156                                          GAsyncResult           *result,
157                                          GError                **error)
158 {
159   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), NULL);
160   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
161 
162   return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->populate_finish (self, result, error);
163 }
164 
165 void
ide_completion_provider_activate_poposal(IdeCompletionProvider * self,IdeCompletionContext * context,IdeCompletionProposal * proposal,const GdkEventKey * key)166 ide_completion_provider_activate_poposal (IdeCompletionProvider *self,
167                                           IdeCompletionContext  *context,
168                                           IdeCompletionProposal *proposal,
169                                           const GdkEventKey     *key)
170 {
171   g_return_if_fail (IDE_IS_COMPLETION_PROVIDER (self));
172   g_return_if_fail (IDE_IS_COMPLETION_CONTEXT (context));
173   g_return_if_fail (IDE_IS_COMPLETION_PROPOSAL (proposal));
174 
175   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->activate_proposal)
176     IDE_COMPLETION_PROVIDER_GET_IFACE (self)->activate_proposal (self, context, proposal, key);
177   else
178     g_critical ("%s does not implement activate_proposal()!", G_OBJECT_TYPE_NAME (self));
179 }
180 
181 /**
182  * ide_completion_provider_refilter:
183  * @self: an #IdeCompletionProvider
184  * @context: an #IdeCompletionContext
185  * @proposals: a #GListModel of results previously provided to the context
186  *
187  * This requests that the completion provider refilter the results based on
188  * changes to the #IdeCompletionContext, such as additional text typed by the
189  * user. If the provider can refine the results, then the provider should do
190  * so and return %TRUE.
191  *
192  * Otherwise, %FALSE is returned and the context will request a new set of
193  * completion results.
194  *
195  * Returns: %TRUE if refiltered; otherwise %FALSE
196  *
197  * Since: 3.32
198  */
199 gboolean
ide_completion_provider_refilter(IdeCompletionProvider * self,IdeCompletionContext * context,GListModel * proposals)200 ide_completion_provider_refilter (IdeCompletionProvider *self,
201                                   IdeCompletionContext  *context,
202                                   GListModel            *proposals)
203 {
204   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), FALSE);
205   g_return_val_if_fail (IDE_IS_COMPLETION_CONTEXT (context), FALSE);
206   g_return_val_if_fail (G_IS_LIST_MODEL (proposals), FALSE);
207 
208   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->refilter)
209     return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->refilter (self, context, proposals);
210 
211   return FALSE;
212 }
213 
214 /**
215  * ide_completion_provider_is_trigger:
216  * @self: an #IdeCompletionProvider
217  * @iter: the current insertion point
218  * @ch: the character that was just inserted
219  *
220  * Completion providers may want to trigger that the completion window is
221  * displayed upon insertion of a particular character. For example, a C
222  * indenter might want to trigger after -> or . is inserted.
223  *
224  * @ch is set to the character that was just inserted. If you need something
225  * more complex, copy @iter and move it backwards twice to check the character
226  * previous to @ch.
227  *
228  * Returns: %TRUE to request that the completion window is displayed.
229  *
230  * Since: 3.32
231  */
232 gboolean
ide_completion_provider_is_trigger(IdeCompletionProvider * self,const GtkTextIter * iter,gunichar ch)233 ide_completion_provider_is_trigger (IdeCompletionProvider *self,
234                                     const GtkTextIter     *iter,
235                                     gunichar               ch)
236 {
237   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), FALSE);
238   g_return_val_if_fail (iter != NULL, FALSE);
239 
240   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->is_trigger)
241     return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->is_trigger (self, iter, ch);
242 
243   return FALSE;
244 }
245 
246 /**
247  * ide_completion_provider_key_activates:
248  * @self: a #IdeCompletionProvider
249  * @proposal: an #IdeCompletionProposal created by the provider
250  * @key: the #GdkEventKey for the current keyboard event
251  *
252  * This function is called to ask the provider if the key-press event should
253  * force activation of the proposal. This is useful for languages where you
254  * might want to activate the completion from a language-specific character.
255  *
256  * For example, in C, you might want to use period (.) to activate the
257  * completion and insert either (.) or (->) based on the type.
258  *
259  * Returns: %TRUE if the proposal should be activated.
260  *
261  * Since: 3.32
262  */
263 gboolean
ide_completion_provider_key_activates(IdeCompletionProvider * self,IdeCompletionProposal * proposal,const GdkEventKey * key)264 ide_completion_provider_key_activates (IdeCompletionProvider *self,
265                                        IdeCompletionProposal *proposal,
266                                        const GdkEventKey     *key)
267 {
268   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), FALSE);
269   g_return_val_if_fail (IDE_IS_COMPLETION_PROPOSAL (proposal), FALSE);
270   g_return_val_if_fail (key != NULL, FALSE);
271 
272   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->key_activates)
273     return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->key_activates (self, proposal, key);
274 
275   return FALSE;
276 }
277 
278 void
_ide_completion_provider_load(IdeCompletionProvider * self,IdeContext * context)279 _ide_completion_provider_load (IdeCompletionProvider *self,
280                                IdeContext            *context)
281 {
282   g_return_if_fail (IDE_IS_COMPLETION_PROVIDER (self));
283   g_return_if_fail (IDE_IS_CONTEXT (context));
284 
285   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->load)
286     IDE_COMPLETION_PROVIDER_GET_IFACE (self)->load (self, context);
287 }
288 
289 /**
290  * ide_completion_provider_display_proposal:
291  * @self: a #IdeCompletionProvider
292  * @row: an #IdeCompletionListBoxRow
293  * @context: an #IdeCompletionContext
294  * @typed_text: (nullable): the typed text for the proposal
295  * @proposal: an #IdeCompletionProposal
296  *
297  * Requests that the provider update @row with values from @proposal.
298  *
299  * The design rational about having this operation part of the
300  * #IdeCompletionProvider interface (as opposed to the #IdeCompletionProposal
301  * interface) is that it allows for some optimizations and code simplification
302  * on behalf of completion providers.
303  *
304  * Since: 3.32
305  */
306 void
ide_completion_provider_display_proposal(IdeCompletionProvider * self,IdeCompletionListBoxRow * row,IdeCompletionContext * context,const gchar * typed_text,IdeCompletionProposal * proposal)307 ide_completion_provider_display_proposal (IdeCompletionProvider   *self,
308                                           IdeCompletionListBoxRow *row,
309                                           IdeCompletionContext    *context,
310                                           const gchar             *typed_text,
311                                           IdeCompletionProposal   *proposal)
312 {
313   g_return_if_fail (IDE_IS_COMPLETION_PROVIDER (self));
314   g_return_if_fail (IDE_IS_COMPLETION_LIST_BOX_ROW (row));
315   g_return_if_fail (IDE_IS_COMPLETION_CONTEXT (context));
316   g_return_if_fail (IDE_IS_COMPLETION_PROPOSAL (proposal));
317 
318   if (typed_text == NULL)
319     typed_text = "";
320 
321   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->display_proposal)
322     IDE_COMPLETION_PROVIDER_GET_IFACE (self)->display_proposal (self, row, context, typed_text, proposal);
323 }
324 
325 /**
326  * ide_completion_provider_get_comment:
327  * @self: an #IdeCompletionProvider
328  * @proposal: an #IdeCompletionProposal
329  *
330  * If the completion proposal has a comment, the provider should return
331  * a newly allocated string containing it.
332  *
333  * This is displayed at the bottom of the completion window.
334  *
335  * Returns: (transfer full) (nullable): A new string or %NULL
336  *
337  * Since: 3.32
338  */
339 gchar *
ide_completion_provider_get_comment(IdeCompletionProvider * self,IdeCompletionProposal * proposal)340 ide_completion_provider_get_comment (IdeCompletionProvider *self,
341                                      IdeCompletionProposal *proposal)
342 {
343   g_return_val_if_fail (IDE_IS_COMPLETION_PROVIDER (self), NULL);
344   g_return_val_if_fail (IDE_IS_COMPLETION_PROPOSAL (proposal), NULL);
345 
346   if (IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_comment)
347     return IDE_COMPLETION_PROVIDER_GET_IFACE (self)->get_comment (self, proposal);
348 
349   return NULL;
350 }
351