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

source code of gtk/subprojects/glib/gio/gfilenamecompleter.c