Branch data Line data Source code
1 : : /* GIO - GLib Input, Output and Streaming Library
2 : : *
3 : : * Copyright (C) 2006-2007 Red Hat, Inc.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library 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 : : * This library 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
18 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 : : *
20 : : * Author: Alexander Larsson <alexl@redhat.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : : #include "gfilenamecompleter.h"
25 : : #include "gfileenumerator.h"
26 : : #include "gfileattribute.h"
27 : : #include "gfile.h"
28 : : #include "gfileinfo.h"
29 : : #include "gcancellable.h"
30 : : #include <string.h>
31 : : #include "glibintl.h"
32 : :
33 : :
34 : : /**
35 : : * GFilenameCompleter:
36 : : *
37 : : * Completes partial file and directory names given a partial string by
38 : : * looking in the file system for clues. Can return a list of possible
39 : : * completion strings for widget implementations.
40 : : */
41 : :
42 : : enum {
43 : : GOT_COMPLETION_DATA,
44 : : LAST_SIGNAL
45 : : };
46 : :
47 : : static guint signals[LAST_SIGNAL] = { 0 };
48 : :
49 : : typedef struct {
50 : : GFilenameCompleter *completer;
51 : : GFileEnumerator *enumerator;
52 : : GCancellable *cancellable;
53 : : gboolean should_escape;
54 : : GFile *dir;
55 : : GList *basenames;
56 : : gboolean dirs_only;
57 : : } LoadBasenamesData;
58 : :
59 : : struct _GFilenameCompleter {
60 : : GObject parent;
61 : :
62 : : GFile *basenames_dir;
63 : : gboolean basenames_are_escaped;
64 : : gboolean dirs_only;
65 : : GList *basenames;
66 : :
67 : : LoadBasenamesData *basename_loader;
68 : : };
69 : :
70 : 9 : G_DEFINE_TYPE (GFilenameCompleter, g_filename_completer, G_TYPE_OBJECT)
71 : :
72 : : static void cancel_load_basenames (GFilenameCompleter *completer);
73 : :
74 : : static void
75 : 1 : g_filename_completer_finalize (GObject *object)
76 : : {
77 : : GFilenameCompleter *completer;
78 : :
79 : 1 : completer = G_FILENAME_COMPLETER (object);
80 : :
81 : 1 : cancel_load_basenames (completer);
82 : :
83 : 1 : if (completer->basenames_dir)
84 : 0 : g_object_unref (completer->basenames_dir);
85 : :
86 : 1 : g_list_free_full (completer->basenames, g_free);
87 : :
88 : 1 : G_OBJECT_CLASS (g_filename_completer_parent_class)->finalize (object);
89 : 1 : }
90 : :
91 : : static void
92 : 2 : g_filename_completer_class_init (GFilenameCompleterClass *klass)
93 : : {
94 : 2 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
95 : :
96 : 2 : gobject_class->finalize = g_filename_completer_finalize;
97 : : /**
98 : : * GFilenameCompleter::got-completion-data:
99 : : *
100 : : * Emitted when the file name completion information comes available.
101 : : **/
102 : 2 : signals[GOT_COMPLETION_DATA] = g_signal_new (I_("got-completion-data"),
103 : : G_TYPE_FILENAME_COMPLETER,
104 : : G_SIGNAL_RUN_LAST,
105 : : G_STRUCT_OFFSET (GFilenameCompleterClass, got_completion_data),
106 : : NULL, NULL,
107 : : NULL,
108 : : G_TYPE_NONE, 0);
109 : 2 : }
110 : :
111 : : static void
112 : 1 : g_filename_completer_init (GFilenameCompleter *completer)
113 : : {
114 : 1 : }
115 : :
116 : : /**
117 : : * g_filename_completer_new:
118 : : *
119 : : * Creates a new filename completer.
120 : : *
121 : : * Returns: a #GFilenameCompleter.
122 : : **/
123 : : GFilenameCompleter *
124 : 0 : g_filename_completer_new (void)
125 : : {
126 : 0 : return g_object_new (G_TYPE_FILENAME_COMPLETER, NULL);
127 : : }
128 : :
129 : : static char *
130 : 0 : longest_common_prefix (char *a, char *b)
131 : : {
132 : : char *start;
133 : :
134 : 0 : start = a;
135 : :
136 : 0 : while (g_utf8_get_char (a) == g_utf8_get_char (b))
137 : : {
138 : 0 : a = g_utf8_next_char (a);
139 : 0 : b = g_utf8_next_char (b);
140 : : }
141 : :
142 : 0 : return g_strndup (start, a - start);
143 : : }
144 : :
145 : : static void
146 : 0 : load_basenames_data_free (LoadBasenamesData *data)
147 : : {
148 : 0 : if (data->enumerator)
149 : 0 : g_object_unref (data->enumerator);
150 : :
151 : 0 : g_object_unref (data->cancellable);
152 : 0 : g_object_unref (data->dir);
153 : :
154 : 0 : g_list_free_full (data->basenames, g_free);
155 : :
156 : 0 : g_free (data);
157 : 0 : }
158 : :
159 : : static void
160 : 0 : got_more_files (GObject *source_object,
161 : : GAsyncResult *res,
162 : : gpointer user_data)
163 : : {
164 : 0 : LoadBasenamesData *data = user_data;
165 : : GList *infos, *l;
166 : : GFileInfo *info;
167 : : const char *name;
168 : : gboolean append_slash;
169 : : char *t;
170 : : char *basename;
171 : :
172 : 0 : if (data->completer == NULL)
173 : : {
174 : : /* Was cancelled */
175 : 0 : load_basenames_data_free (data);
176 : 0 : return;
177 : : }
178 : :
179 : 0 : infos = g_file_enumerator_next_files_finish (data->enumerator, res, NULL);
180 : :
181 : 0 : for (l = infos; l != NULL; l = l->next)
182 : : {
183 : 0 : info = l->data;
184 : :
185 : 0 : if (data->dirs_only &&
186 : 0 : g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
187 : : {
188 : 0 : g_object_unref (info);
189 : 0 : continue;
190 : : }
191 : :
192 : 0 : append_slash = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
193 : 0 : name = g_file_info_get_name (info);
194 : 0 : if (name == NULL)
195 : : {
196 : 0 : g_object_unref (info);
197 : 0 : continue;
198 : : }
199 : :
200 : :
201 : 0 : if (data->should_escape)
202 : 0 : basename = g_uri_escape_string (name,
203 : : G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
204 : : TRUE);
205 : : else
206 : : /* If not should_escape, must be a local filename, convert to utf8 */
207 : 0 : basename = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
208 : :
209 : 0 : if (basename)
210 : : {
211 : 0 : if (append_slash)
212 : : {
213 : 0 : t = basename;
214 : 0 : basename = g_strconcat (basename, "/", NULL);
215 : 0 : g_free (t);
216 : : }
217 : :
218 : 0 : data->basenames = g_list_prepend (data->basenames, basename);
219 : : }
220 : :
221 : 0 : g_object_unref (info);
222 : : }
223 : :
224 : 0 : g_list_free (infos);
225 : :
226 : 0 : if (infos)
227 : : {
228 : : /* Not last, get more files */
229 : 0 : g_file_enumerator_next_files_async (data->enumerator,
230 : : 100,
231 : : 0,
232 : : data->cancellable,
233 : : got_more_files, data);
234 : : }
235 : : else
236 : : {
237 : 0 : data->completer->basename_loader = NULL;
238 : :
239 : 0 : if (data->completer->basenames_dir)
240 : 0 : g_object_unref (data->completer->basenames_dir);
241 : 0 : g_list_free_full (data->completer->basenames, g_free);
242 : :
243 : 0 : data->completer->basenames_dir = g_object_ref (data->dir);
244 : 0 : data->completer->basenames = data->basenames;
245 : 0 : data->completer->basenames_are_escaped = data->should_escape;
246 : 0 : data->basenames = NULL;
247 : :
248 : 0 : g_file_enumerator_close_async (data->enumerator, 0, NULL, NULL, NULL);
249 : :
250 : 0 : g_signal_emit (data->completer, signals[GOT_COMPLETION_DATA], 0);
251 : 0 : load_basenames_data_free (data);
252 : : }
253 : : }
254 : :
255 : :
256 : : static void
257 : 0 : got_enum (GObject *source_object,
258 : : GAsyncResult *res,
259 : : gpointer user_data)
260 : : {
261 : 0 : LoadBasenamesData *data = user_data;
262 : :
263 : 0 : if (data->completer == NULL)
264 : : {
265 : : /* Was cancelled */
266 : 0 : load_basenames_data_free (data);
267 : 0 : return;
268 : : }
269 : :
270 : 0 : data->enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, NULL);
271 : :
272 : 0 : if (data->enumerator == NULL)
273 : : {
274 : 0 : data->completer->basename_loader = NULL;
275 : :
276 : 0 : if (data->completer->basenames_dir)
277 : 0 : g_object_unref (data->completer->basenames_dir);
278 : 0 : g_list_free_full (data->completer->basenames, g_free);
279 : :
280 : : /* Mark up-to-date with no basenames */
281 : 0 : data->completer->basenames_dir = g_object_ref (data->dir);
282 : 0 : data->completer->basenames = NULL;
283 : 0 : data->completer->basenames_are_escaped = data->should_escape;
284 : :
285 : 0 : load_basenames_data_free (data);
286 : 0 : return;
287 : : }
288 : :
289 : 0 : g_file_enumerator_next_files_async (data->enumerator,
290 : : 100,
291 : : 0,
292 : : data->cancellable,
293 : : got_more_files, data);
294 : : }
295 : :
296 : : static void
297 : 0 : schedule_load_basenames (GFilenameCompleter *completer,
298 : : GFile *dir,
299 : : gboolean should_escape)
300 : : {
301 : : LoadBasenamesData *data;
302 : :
303 : 0 : cancel_load_basenames (completer);
304 : :
305 : 0 : data = g_new0 (LoadBasenamesData, 1);
306 : 0 : data->completer = completer;
307 : 0 : data->cancellable = g_cancellable_new ();
308 : 0 : data->dir = g_object_ref (dir);
309 : 0 : data->should_escape = should_escape;
310 : 0 : data->dirs_only = completer->dirs_only;
311 : :
312 : 0 : completer->basename_loader = data;
313 : :
314 : 0 : g_file_enumerate_children_async (dir,
315 : : G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
316 : : 0, 0,
317 : : data->cancellable,
318 : : got_enum, data);
319 : 0 : }
320 : :
321 : : static void
322 : 1 : cancel_load_basenames (GFilenameCompleter *completer)
323 : : {
324 : : LoadBasenamesData *loader;
325 : :
326 : 1 : if (completer->basename_loader)
327 : : {
328 : 0 : loader = completer->basename_loader;
329 : 0 : loader->completer = NULL;
330 : :
331 : 0 : g_cancellable_cancel (loader->cancellable);
332 : :
333 : 0 : completer->basename_loader = NULL;
334 : : }
335 : 1 : }
336 : :
337 : :
338 : : /* Returns a list of possible matches and the basename to use for it */
339 : : static GList *
340 : 0 : init_completion (GFilenameCompleter *completer,
341 : : const char *initial_text,
342 : : char **basename_out)
343 : : {
344 : : gboolean should_escape;
345 : : GFile *file, *parent;
346 : : char *basename;
347 : : char *t;
348 : : size_t len;
349 : :
350 : 0 : *basename_out = NULL;
351 : :
352 : 0 : should_escape = ! (g_path_is_absolute (initial_text) || *initial_text == '~');
353 : :
354 : 0 : len = strlen (initial_text);
355 : :
356 : 0 : if (len > 0 &&
357 : 0 : initial_text[len - 1] == '/')
358 : 0 : return NULL;
359 : :
360 : 0 : file = g_file_parse_name (initial_text);
361 : 0 : parent = g_file_get_parent (file);
362 : 0 : if (parent == NULL)
363 : : {
364 : 0 : g_object_unref (file);
365 : 0 : return NULL;
366 : : }
367 : :
368 : 0 : if (completer->basenames_dir == NULL ||
369 : 0 : completer->basenames_are_escaped != should_escape ||
370 : 0 : !g_file_equal (parent, completer->basenames_dir))
371 : : {
372 : 0 : schedule_load_basenames (completer, parent, should_escape);
373 : 0 : g_object_unref (file);
374 : 0 : return NULL;
375 : : }
376 : :
377 : 0 : basename = g_file_get_basename (file);
378 : 0 : if (should_escape)
379 : : {
380 : 0 : t = basename;
381 : 0 : basename = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
382 : 0 : g_free (t);
383 : : }
384 : : else
385 : : {
386 : 0 : t = basename;
387 : 0 : basename = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
388 : 0 : g_free (t);
389 : :
390 : 0 : if (basename == NULL)
391 : 0 : return NULL;
392 : : }
393 : :
394 : 0 : *basename_out = basename;
395 : :
396 : 0 : return completer->basenames;
397 : : }
398 : :
399 : : /**
400 : : * g_filename_completer_get_completion_suffix:
401 : : * @completer: the filename completer.
402 : : * @initial_text: text to be completed.
403 : : *
404 : : * Obtains a completion for @initial_text from @completer.
405 : : *
406 : : * Returns: (nullable) (transfer full): a completed string, or %NULL if no
407 : : * completion exists. This string is not owned by GIO, so remember to g_free()
408 : : * it when finished.
409 : : **/
410 : : char *
411 : 0 : g_filename_completer_get_completion_suffix (GFilenameCompleter *completer,
412 : : const char *initial_text)
413 : : {
414 : : GList *possible_matches, *l;
415 : : char *prefix;
416 : : char *suffix;
417 : : char *possible_match;
418 : : char *lcp;
419 : :
420 : 0 : g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
421 : 0 : g_return_val_if_fail (initial_text != NULL, NULL);
422 : :
423 : 0 : possible_matches = init_completion (completer, initial_text, &prefix);
424 : :
425 : 0 : suffix = NULL;
426 : :
427 : 0 : for (l = possible_matches; l != NULL; l = l->next)
428 : : {
429 : 0 : possible_match = l->data;
430 : :
431 : 0 : if (g_str_has_prefix (possible_match, prefix))
432 : : {
433 : 0 : if (suffix == NULL)
434 : 0 : suffix = g_strdup (possible_match + strlen (prefix));
435 : : else
436 : : {
437 : 0 : lcp = longest_common_prefix (suffix,
438 : 0 : possible_match + strlen (prefix));
439 : 0 : g_free (suffix);
440 : 0 : suffix = lcp;
441 : :
442 : 0 : if (*suffix == 0)
443 : 0 : break;
444 : : }
445 : : }
446 : : }
447 : :
448 : 0 : g_free (prefix);
449 : :
450 : 0 : return suffix;
451 : : }
452 : :
453 : : /**
454 : : * g_filename_completer_get_completions:
455 : : * @completer: the filename completer.
456 : : * @initial_text: text to be completed.
457 : : *
458 : : * Gets an array of completion strings for a given initial text.
459 : : *
460 : : * Returns: (array zero-terminated=1) (transfer full): array of strings with possible completions for @initial_text.
461 : : * This array must be freed by g_strfreev() when finished.
462 : : **/
463 : : char **
464 : 0 : g_filename_completer_get_completions (GFilenameCompleter *completer,
465 : : const char *initial_text)
466 : : {
467 : : GList *possible_matches, *l;
468 : : char *prefix;
469 : : char *possible_match;
470 : : GPtrArray *res;
471 : :
472 : 0 : g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
473 : 0 : g_return_val_if_fail (initial_text != NULL, NULL);
474 : :
475 : 0 : possible_matches = init_completion (completer, initial_text, &prefix);
476 : :
477 : 0 : res = g_ptr_array_new ();
478 : 0 : for (l = possible_matches; l != NULL; l = l->next)
479 : : {
480 : 0 : possible_match = l->data;
481 : :
482 : 0 : if (g_str_has_prefix (possible_match, prefix))
483 : 0 : g_ptr_array_add (res,
484 : 0 : g_strconcat (initial_text, possible_match + strlen (prefix), NULL));
485 : : }
486 : :
487 : 0 : g_free (prefix);
488 : :
489 : 0 : g_ptr_array_add (res, NULL);
490 : :
491 : 0 : return (char**)g_ptr_array_free (res, FALSE);
492 : : }
493 : :
494 : : /**
495 : : * g_filename_completer_set_dirs_only:
496 : : * @completer: the filename completer.
497 : : * @dirs_only: a #gboolean.
498 : : *
499 : : * If @dirs_only is %TRUE, @completer will only
500 : : * complete directory names, and not file names.
501 : : **/
502 : : void
503 : 0 : g_filename_completer_set_dirs_only (GFilenameCompleter *completer,
504 : : gboolean dirs_only)
505 : : {
506 : 0 : g_return_if_fail (G_IS_FILENAME_COMPLETER (completer));
507 : :
508 : 0 : completer->dirs_only = dirs_only;
509 : : }
|