1 /* ide-lsp-search-provider.c
2  *
3  * Copyright 2020 Günther Wagner <info@gunibert.de>
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-lsp-search-provider"
22 
23 #include "config.h"
24 
25 #include <jsonrpc-glib.h>
26 
27 #include <libide-search.h>
28 
29 #include "ide-lsp-client.h"
30 #include "ide-lsp-util.h"
31 #include "ide-lsp-search-provider.h"
32 #include "ide-lsp-search-result.h"
33 
34 typedef struct
35 {
36   IdeLspClient *client;
37 } IdeLspSearchProviderPrivate;
38 
39 static void provider_iface_init (IdeSearchProviderInterface *iface);
40 
41 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (IdeLspSearchProvider, ide_lsp_search_provider, IDE_TYPE_OBJECT,
42                                   G_ADD_PRIVATE (IdeLspSearchProvider)
43                                   G_IMPLEMENT_INTERFACE (IDE_TYPE_SEARCH_PROVIDER, provider_iface_init))
44 
45 enum {
46   PROP_0,
47   PROP_CLIENT,
48   N_PROPS
49 };
50 
51 static GParamSpec *properties [N_PROPS];
52 
53 /**
54  * ide_lsp_search_provider_get_client:
55  * @self: An #IdeLspSearchProvider
56  *
57  * Gets the client for the search provider.
58  *
59  * Returns: (transfer none) (nullable): An #IdeLspClient or %NULL
60  */
61 IdeLspClient *
ide_lsp_search_provider_get_client(IdeLspSearchProvider * self)62 ide_lsp_search_provider_get_client (IdeLspSearchProvider *self)
63 {
64   IdeLspSearchProviderPrivate *priv = ide_lsp_search_provider_get_instance_private (self);
65 
66   g_return_val_if_fail (IDE_IS_LSP_SEARCH_PROVIDER (self), NULL);
67 
68   return priv->client;
69 }
70 
71 void
ide_lsp_search_provider_set_client(IdeLspSearchProvider * self,IdeLspClient * client)72 ide_lsp_search_provider_set_client (IdeLspSearchProvider *self,
73                                     IdeLspClient         *client)
74 {
75   IdeLspSearchProviderPrivate *priv = ide_lsp_search_provider_get_instance_private (self);
76 
77   g_return_if_fail (IDE_IS_LSP_SEARCH_PROVIDER (self));
78   g_return_if_fail (!client || IDE_IS_LSP_CLIENT (client));
79 
80   if (g_set_object (&priv->client, client))
81     g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
82 }
83 
84 static void
ide_lsp_search_provider_finalize(GObject * object)85 ide_lsp_search_provider_finalize (GObject *object)
86 {
87   IdeLspSearchProvider *self = (IdeLspSearchProvider *)object;
88   IdeLspSearchProviderPrivate *priv = ide_lsp_search_provider_get_instance_private (self);
89 
90   g_clear_object (&priv->client);
91 
92   G_OBJECT_CLASS (ide_lsp_search_provider_parent_class)->finalize (object);
93 }
94 
95 static void
ide_lsp_search_provider_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)96 ide_lsp_search_provider_get_property (GObject    *object,
97                                       guint       prop_id,
98                                       GValue     *value,
99                                       GParamSpec *pspec)
100 {
101   IdeLspSearchProvider *self = IDE_LSP_SEARCH_PROVIDER (object);
102 
103   switch (prop_id)
104     {
105     case PROP_CLIENT:
106       g_value_set_object (value, ide_lsp_search_provider_get_client (self));
107       break;
108 
109     default:
110       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
111     }
112 }
113 
114 static void
ide_lsp_search_provider_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)115 ide_lsp_search_provider_set_property (GObject      *object,
116                                       guint         prop_id,
117                                       const GValue *value,
118                                       GParamSpec   *pspec)
119 {
120   IdeLspSearchProvider *self = IDE_LSP_SEARCH_PROVIDER (object);
121 
122   switch (prop_id)
123     {
124     case PROP_CLIENT:
125       ide_lsp_search_provider_set_client (self, g_value_get_object (value));
126       break;
127 
128     default:
129       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
130     }
131 }
132 
133 static void
ide_lsp_search_provider_class_init(IdeLspSearchProviderClass * klass)134 ide_lsp_search_provider_class_init (IdeLspSearchProviderClass *klass)
135 {
136   GObjectClass *object_class = G_OBJECT_CLASS (klass);
137 
138   object_class->finalize = ide_lsp_search_provider_finalize;
139   object_class->get_property = ide_lsp_search_provider_get_property;
140   object_class->set_property = ide_lsp_search_provider_set_property;
141 
142   properties[PROP_CLIENT] =
143     g_param_spec_object ("client",
144                          "client",
145                          "The Language Server client",
146                          IDE_TYPE_LSP_CLIENT,
147                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
148 
149   g_object_class_install_properties (object_class, N_PROPS, properties);
150 }
151 
152 static void
ide_lsp_search_provider_init(IdeLspSearchProvider * self)153 ide_lsp_search_provider_init (IdeLspSearchProvider *self)
154 {
155 }
156 
157 static void
ide_lsp_search_provider_search_cb(GObject * object,GAsyncResult * res,gpointer user_data)158 ide_lsp_search_provider_search_cb (GObject      *object,
159                                    GAsyncResult *res,
160                                    gpointer      user_data)
161 {
162   IdeLspClient *client = (IdeLspClient *)object;
163   g_autoptr(IdeTask) task = user_data;
164   g_autoptr(GVariant) result = NULL;
165   g_autoptr(GVariantIter) iter = NULL;
166   g_autoptr(GError) error = NULL;
167   GVariant *symbol_information;
168   GPtrArray *ar;
169 
170   IDE_ENTRY;
171 
172   ar = g_ptr_array_new_with_free_func (g_object_unref);
173 
174   if (!ide_lsp_client_call_finish (client, res, &result, &error))
175     {
176       ide_task_return_error (task, g_steal_pointer (&error));
177       IDE_EXIT;
178     }
179 
180   iter = g_variant_iter_new (result);
181 
182   while (g_variant_iter_loop (iter, "v", &symbol_information))
183     {
184       g_autoptr(GFile) gfile = NULL;
185       g_autoptr(IdeLocation) location = NULL;
186       g_autofree gchar *base = NULL;
187       const gchar *title;
188       const gchar *uri;
189       gint64 kind;
190       gint64 line, character;
191       IdeSymbolKind symbol_kind;
192       const gchar *icon_name;
193 
194       JSONRPC_MESSAGE_PARSE (symbol_information,
195                              "name", JSONRPC_MESSAGE_GET_STRING (&title),
196                              "kind", JSONRPC_MESSAGE_GET_INT64 (&kind),
197                              "location", "{",
198                                "uri", JSONRPC_MESSAGE_GET_STRING (&uri),
199                                "range", "{",
200                                  "start", "{",
201                                    "line", JSONRPC_MESSAGE_GET_INT64 (&line),
202                                    "character", JSONRPC_MESSAGE_GET_INT64 (&character),
203                                  "}",
204                                "}",
205                              "}");
206 
207       symbol_kind = ide_lsp_decode_symbol_kind (kind);
208       icon_name = ide_symbol_kind_get_icon_name (symbol_kind);
209 
210       gfile = g_file_new_for_uri (uri);
211       location = ide_location_new (gfile, line, character);
212       base = g_file_get_basename (gfile);
213 
214       g_ptr_array_add (ar, ide_lsp_search_result_new (title, base, location, icon_name));
215     }
216 
217   ide_task_return_pointer (task,
218                            g_steal_pointer (&ar),
219                            g_ptr_array_unref);
220 
221   IDE_EXIT;
222 }
223 
224 static void
ide_lsp_search_provider_search_async(IdeSearchProvider * provider,const gchar * query,guint max_results,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)225 ide_lsp_search_provider_search_async (IdeSearchProvider   *provider,
226                                       const gchar         *query,
227                                       guint                max_results,
228                                       GCancellable        *cancellable,
229                                       GAsyncReadyCallback  callback,
230                                       gpointer             user_data)
231 {
232   IdeLspSearchProvider *self = (IdeLspSearchProvider *)provider;
233   IdeLspSearchProviderPrivate *priv = ide_lsp_search_provider_get_instance_private (self);
234   g_autoptr(IdeTask) task = NULL;
235   g_autoptr(GVariant) params = NULL;
236 
237   IDE_ENTRY;
238 
239   g_return_if_fail (IDE_IS_MAIN_THREAD ());
240   g_return_if_fail (IDE_IS_LSP_SEARCH_PROVIDER (self));
241   g_return_if_fail (query != NULL);
242   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
243 
244   task = ide_task_new (self, cancellable, callback, user_data);
245   ide_task_set_source_tag (task, ide_lsp_search_provider_search_async);
246 
247   if (priv->client == NULL)
248     {
249       ide_task_return_new_error (task,
250                                  G_IO_ERROR,
251                                  G_IO_ERROR_NOT_SUPPORTED,
252                                  "Cannot query, client not available");
253       IDE_EXIT;
254     }
255 
256   params = JSONRPC_MESSAGE_NEW ("query", JSONRPC_MESSAGE_PUT_STRING (query));
257 
258   ide_lsp_client_call_async (priv->client,
259                              "workspace/symbol",
260                              params,
261                              cancellable,
262                              ide_lsp_search_provider_search_cb,
263                              g_steal_pointer (&task));
264 
265   IDE_EXIT;
266 }
267 
268 static GPtrArray *
ide_lsp_search_provider_search_finish(IdeSearchProvider * provider,GAsyncResult * result,GError ** error)269 ide_lsp_search_provider_search_finish (IdeSearchProvider  *provider,
270                                        GAsyncResult       *result,
271                                        GError            **error)
272 {
273   g_autoptr(GPtrArray) ret = NULL;
274 
275   g_assert (IDE_IS_SEARCH_PROVIDER (provider));
276   g_assert (IDE_IS_TASK (result));
277 
278   if ((ret = ide_task_propagate_pointer (IDE_TASK (result), error)))
279     return IDE_PTR_ARRAY_STEAL_FULL (&ret);
280 
281   return NULL;
282 }
283 
284 static void
provider_iface_init(IdeSearchProviderInterface * iface)285 provider_iface_init (IdeSearchProviderInterface *iface)
286 {
287   iface->search_async = ide_lsp_search_provider_search_async;
288   iface->search_finish = ide_lsp_search_provider_search_finish;
289 }
290