Blame gio/gfilenamecompleter.c

Packit ae235b
/* GIO - GLib Input, Output and Streaming Library
Packit ae235b
 * 
Packit ae235b
 * Copyright (C) 2006-2007 Red Hat, Inc.
Packit ae235b
 *
Packit ae235b
 * This library is free software; you can redistribute it and/or
Packit ae235b
 * modify it under the terms of the GNU Lesser General Public
Packit ae235b
 * License as published by the Free Software Foundation; either
Packit ae235b
 * version 2.1 of the License, or (at your option) any later version.
Packit ae235b
 *
Packit ae235b
 * This library is distributed in the hope that it will be useful,
Packit ae235b
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
 * Lesser General Public License for more details.
Packit ae235b
 *
Packit ae235b
 * You should have received a copy of the GNU Lesser General
Packit ae235b
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
 *
Packit ae235b
 * Author: Alexander Larsson <alexl@redhat.com>
Packit ae235b
 */
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
#include "gfilenamecompleter.h"
Packit ae235b
#include "gfileenumerator.h"
Packit ae235b
#include "gfileattribute.h"
Packit ae235b
#include "gfile.h"
Packit ae235b
#include "gfileinfo.h"
Packit ae235b
#include "gcancellable.h"
Packit ae235b
#include <string.h>
Packit ae235b
#include "glibintl.h"
Packit ae235b
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * SECTION:gfilenamecompleter
Packit ae235b
 * @short_description: Filename Completer
Packit ae235b
 * @include: gio/gio.h
Packit ae235b
 * 
Packit ae235b
 * Completes partial file and directory names given a partial string by
Packit ae235b
 * looking in the file system for clues. Can return a list of possible 
Packit ae235b
 * completion strings for widget implementations.
Packit ae235b
 * 
Packit ae235b
 **/
Packit ae235b
Packit ae235b
enum {
Packit ae235b
  GOT_COMPLETION_DATA,
Packit ae235b
  LAST_SIGNAL
Packit ae235b
};
Packit ae235b
Packit ae235b
static guint signals[LAST_SIGNAL] = { 0 };
Packit ae235b
Packit ae235b
typedef struct {
Packit ae235b
  GFilenameCompleter *completer;
Packit ae235b
  GFileEnumerator *enumerator;
Packit ae235b
  GCancellable *cancellable;
Packit ae235b
  gboolean should_escape;
Packit ae235b
  GFile *dir;
Packit ae235b
  GList *basenames;
Packit ae235b
  gboolean dirs_only;
Packit ae235b
} LoadBasenamesData;
Packit ae235b
Packit ae235b
struct _GFilenameCompleter {
Packit ae235b
  GObject parent;
Packit ae235b
Packit ae235b
  GFile *basenames_dir;
Packit ae235b
  gboolean basenames_are_escaped;
Packit ae235b
  gboolean dirs_only;
Packit ae235b
  GList *basenames;
Packit ae235b
Packit ae235b
  LoadBasenamesData *basename_loader;
Packit ae235b
};
Packit ae235b
Packit ae235b
G_DEFINE_TYPE (GFilenameCompleter, g_filename_completer, G_TYPE_OBJECT)
Packit ae235b
Packit ae235b
static void cancel_load_basenames (GFilenameCompleter *completer);
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_filename_completer_finalize (GObject *object)
Packit ae235b
{
Packit ae235b
  GFilenameCompleter *completer;
Packit ae235b
Packit ae235b
  completer = G_FILENAME_COMPLETER (object);
Packit ae235b
Packit ae235b
  cancel_load_basenames (completer);
Packit ae235b
Packit ae235b
  if (completer->basenames_dir)
Packit ae235b
    g_object_unref (completer->basenames_dir);
Packit ae235b
Packit ae235b
  g_list_free_full (completer->basenames, g_free);
Packit ae235b
Packit ae235b
  G_OBJECT_CLASS (g_filename_completer_parent_class)->finalize (object);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_filename_completer_class_init (GFilenameCompleterClass *klass)
Packit ae235b
{
Packit ae235b
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
Packit ae235b
  
Packit ae235b
  gobject_class->finalize = g_filename_completer_finalize;
Packit ae235b
  /**
Packit ae235b
   * GFilenameCompleter::got-completion-data:
Packit ae235b
   * 
Packit ae235b
   * Emitted when the file name completion information comes available.
Packit ae235b
   **/
Packit ae235b
  signals[GOT_COMPLETION_DATA] = g_signal_new (I_("got-completion-data"),
Packit ae235b
					  G_TYPE_FILENAME_COMPLETER,
Packit ae235b
					  G_SIGNAL_RUN_LAST,
Packit ae235b
					  G_STRUCT_OFFSET (GFilenameCompleterClass, got_completion_data),
Packit ae235b
					  NULL, NULL,
Packit ae235b
					  g_cclosure_marshal_VOID__VOID,
Packit ae235b
					  G_TYPE_NONE, 0);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
g_filename_completer_init (GFilenameCompleter *completer)
Packit ae235b
{
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_filename_completer_new:
Packit ae235b
 * 
Packit ae235b
 * Creates a new filename completer.
Packit ae235b
 * 
Packit ae235b
 * Returns: a #GFilenameCompleter.
Packit ae235b
 **/
Packit ae235b
GFilenameCompleter *
Packit ae235b
g_filename_completer_new (void)
Packit ae235b
{
Packit ae235b
  return g_object_new (G_TYPE_FILENAME_COMPLETER, NULL);
Packit ae235b
}
Packit ae235b
Packit ae235b
static char *
Packit ae235b
longest_common_prefix (char *a, char *b)
Packit ae235b
{
Packit ae235b
  char *start;
Packit ae235b
Packit ae235b
  start = a;
Packit ae235b
Packit ae235b
  while (g_utf8_get_char (a) == g_utf8_get_char (b))
Packit ae235b
    {
Packit ae235b
      a = g_utf8_next_char (a);
Packit ae235b
      b = g_utf8_next_char (b);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return g_strndup (start, a - start);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
load_basenames_data_free (LoadBasenamesData *data)
Packit ae235b
{
Packit ae235b
  if (data->enumerator)
Packit ae235b
    g_object_unref (data->enumerator);
Packit ae235b
  
Packit ae235b
  g_object_unref (data->cancellable);
Packit ae235b
  g_object_unref (data->dir);
Packit ae235b
  
Packit ae235b
  g_list_free_full (data->basenames, g_free);
Packit ae235b
  
Packit ae235b
  g_free (data);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
got_more_files (GObject *source_object,
Packit ae235b
		GAsyncResult *res,
Packit ae235b
		gpointer user_data)
Packit ae235b
{
Packit ae235b
  LoadBasenamesData *data = user_data;
Packit ae235b
  GList *infos, *l;
Packit ae235b
  GFileInfo *info;
Packit ae235b
  const char *name;
Packit ae235b
  gboolean append_slash;
Packit ae235b
  char *t;
Packit ae235b
  char *basename;
Packit ae235b
Packit ae235b
  if (data->completer == NULL)
Packit ae235b
    {
Packit ae235b
      /* Was cancelled */
Packit ae235b
      load_basenames_data_free (data);
Packit ae235b
      return;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  infos = g_file_enumerator_next_files_finish (data->enumerator, res, NULL);
Packit ae235b
Packit ae235b
  for (l = infos; l != NULL; l = l->next)
Packit ae235b
    {
Packit ae235b
      info = l->data;
Packit ae235b
Packit ae235b
      if (data->dirs_only &&
Packit ae235b
	  g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
Packit ae235b
	{
Packit ae235b
	  g_object_unref (info);
Packit ae235b
	  continue;
Packit ae235b
	}
Packit ae235b
      
Packit ae235b
      append_slash = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
Packit ae235b
      name = g_file_info_get_name (info);
Packit ae235b
      if (name == NULL)
Packit ae235b
	{
Packit ae235b
	  g_object_unref (info);
Packit ae235b
	  continue;
Packit ae235b
	}
Packit ae235b
Packit ae235b
      
Packit ae235b
      if (data->should_escape)
Packit ae235b
	basename = g_uri_escape_string (name,
Packit ae235b
					G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
Packit ae235b
					TRUE);
Packit ae235b
      else
Packit ae235b
	/* If not should_escape, must be a local filename, convert to utf8 */
Packit ae235b
	basename = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
Packit ae235b
      
Packit ae235b
      if (basename)
Packit ae235b
	{
Packit ae235b
	  if (append_slash)
Packit ae235b
	    {
Packit ae235b
	      t = basename;
Packit ae235b
	      basename = g_strconcat (basename, "/", NULL);
Packit ae235b
	      g_free (t);
Packit ae235b
	    }
Packit ae235b
	  
Packit ae235b
	  data->basenames = g_list_prepend (data->basenames, basename);
Packit ae235b
	}
Packit ae235b
      
Packit ae235b
      g_object_unref (info);
Packit ae235b
    }
Packit ae235b
  
Packit ae235b
  g_list_free (infos);
Packit ae235b
  
Packit ae235b
  if (infos)
Packit ae235b
    {
Packit ae235b
      /* Not last, get more files */
Packit ae235b
      g_file_enumerator_next_files_async (data->enumerator,
Packit ae235b
					  100,
Packit ae235b
					  0,
Packit ae235b
					  data->cancellable,
Packit ae235b
					  got_more_files, data);
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      data->completer->basename_loader = NULL;
Packit ae235b
      
Packit ae235b
      if (data->completer->basenames_dir)
Packit ae235b
	g_object_unref (data->completer->basenames_dir);
Packit ae235b
      g_list_free_full (data->completer->basenames, g_free);
Packit ae235b
      
Packit ae235b
      data->completer->basenames_dir = g_object_ref (data->dir);
Packit ae235b
      data->completer->basenames = data->basenames;
Packit ae235b
      data->completer->basenames_are_escaped = data->should_escape;
Packit ae235b
      data->basenames = NULL;
Packit ae235b
      
Packit ae235b
      g_file_enumerator_close_async (data->enumerator, 0, NULL, NULL, NULL);
Packit ae235b
Packit ae235b
      g_signal_emit (data->completer, signals[GOT_COMPLETION_DATA], 0);
Packit ae235b
      load_basenames_data_free (data);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
Packit ae235b
static void
Packit ae235b
got_enum (GObject *source_object,
Packit ae235b
	  GAsyncResult *res,
Packit ae235b
	  gpointer user_data)
Packit ae235b
{
Packit ae235b
  LoadBasenamesData *data = user_data;
Packit ae235b
Packit ae235b
  if (data->completer == NULL)
Packit ae235b
    {
Packit ae235b
      /* Was cancelled */
Packit ae235b
      load_basenames_data_free (data);
Packit ae235b
      return;
Packit ae235b
    }
Packit ae235b
  
Packit ae235b
  data->enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, NULL);
Packit ae235b
  
Packit ae235b
  if (data->enumerator == NULL)
Packit ae235b
    {
Packit ae235b
      data->completer->basename_loader = NULL;
Packit ae235b
Packit ae235b
      if (data->completer->basenames_dir)
Packit ae235b
	g_object_unref (data->completer->basenames_dir);
Packit ae235b
      g_list_free_full (data->completer->basenames, g_free);
Packit ae235b
Packit ae235b
      /* Mark uptodate with no basenames */
Packit ae235b
      data->completer->basenames_dir = g_object_ref (data->dir);
Packit ae235b
      data->completer->basenames = NULL;
Packit ae235b
      data->completer->basenames_are_escaped = data->should_escape;
Packit ae235b
      
Packit ae235b
      load_basenames_data_free (data);
Packit ae235b
      return;
Packit ae235b
    }
Packit ae235b
  
Packit ae235b
  g_file_enumerator_next_files_async (data->enumerator,
Packit ae235b
				      100,
Packit ae235b
				      0,
Packit ae235b
				      data->cancellable,
Packit ae235b
				      got_more_files, data);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
schedule_load_basenames (GFilenameCompleter *completer,
Packit ae235b
			 GFile *dir,
Packit ae235b
			 gboolean should_escape)
Packit ae235b
{
Packit ae235b
  LoadBasenamesData *data;
Packit ae235b
Packit ae235b
  cancel_load_basenames (completer);
Packit ae235b
Packit ae235b
  data = g_new0 (LoadBasenamesData, 1);
Packit ae235b
  data->completer = completer;
Packit ae235b
  data->cancellable = g_cancellable_new ();
Packit ae235b
  data->dir = g_object_ref (dir);
Packit ae235b
  data->should_escape = should_escape;
Packit ae235b
  data->dirs_only = completer->dirs_only;
Packit ae235b
Packit ae235b
  completer->basename_loader = data;
Packit ae235b
  
Packit ae235b
  g_file_enumerate_children_async (dir,
Packit ae235b
				   G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
Packit ae235b
				   0, 0,
Packit ae235b
				   data->cancellable,
Packit ae235b
				   got_enum, data);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
cancel_load_basenames (GFilenameCompleter *completer)
Packit ae235b
{
Packit ae235b
  LoadBasenamesData *loader;
Packit ae235b
  
Packit ae235b
  if (completer->basename_loader)
Packit ae235b
    {
Packit ae235b
      loader = completer->basename_loader; 
Packit ae235b
      loader->completer = NULL;
Packit ae235b
      
Packit ae235b
      g_cancellable_cancel (loader->cancellable);
Packit ae235b
      
Packit ae235b
      completer->basename_loader = NULL;
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
Packit ae235b
/* Returns a list of possible matches and the basename to use for it */
Packit ae235b
static GList *
Packit ae235b
init_completion (GFilenameCompleter *completer,
Packit ae235b
		 const char *initial_text,
Packit ae235b
		 char **basename_out)
Packit ae235b
{
Packit ae235b
  gboolean should_escape;
Packit ae235b
  GFile *file, *parent;
Packit ae235b
  char *basename;
Packit ae235b
  char *t;
Packit ae235b
  int len;
Packit ae235b
Packit ae235b
  *basename_out = NULL;
Packit ae235b
  
Packit ae235b
  should_escape = ! (g_path_is_absolute (initial_text) || *initial_text == '~');
Packit ae235b
Packit ae235b
  len = strlen (initial_text);
Packit ae235b
  
Packit ae235b
  if (len > 0 &&
Packit ae235b
      initial_text[len - 1] == '/')
Packit ae235b
    return NULL;
Packit ae235b
  
Packit ae235b
  file = g_file_parse_name (initial_text);
Packit ae235b
  parent = g_file_get_parent (file);
Packit ae235b
  if (parent == NULL)
Packit ae235b
    {
Packit ae235b
      g_object_unref (file);
Packit ae235b
      return NULL;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (completer->basenames_dir == NULL ||
Packit ae235b
      completer->basenames_are_escaped != should_escape ||
Packit ae235b
      !g_file_equal (parent, completer->basenames_dir))
Packit ae235b
    {
Packit ae235b
      schedule_load_basenames (completer, parent, should_escape);
Packit ae235b
      g_object_unref (file);
Packit ae235b
      return NULL;
Packit ae235b
    }
Packit ae235b
  
Packit ae235b
  basename = g_file_get_basename (file);
Packit ae235b
  if (should_escape)
Packit ae235b
    {
Packit ae235b
      t = basename;
Packit ae235b
      basename = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
Packit ae235b
      g_free (t);
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      t = basename;
Packit ae235b
      basename = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
Packit ae235b
      g_free (t);
Packit ae235b
      
Packit ae235b
      if (basename == NULL)
Packit ae235b
	return NULL;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  *basename_out = basename;
Packit ae235b
Packit ae235b
  return completer->basenames;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_filename_completer_get_completion_suffix:
Packit ae235b
 * @completer: the filename completer.
Packit ae235b
 * @initial_text: text to be completed.
Packit ae235b
 *
Packit ae235b
 * Obtains a completion for @initial_text from @completer.
Packit ae235b
 *  
Packit ae235b
 * Returns: a completed string, or %NULL if no completion exists. 
Packit ae235b
 *     This string is not owned by GIO, so remember to g_free() it 
Packit ae235b
 *     when finished.
Packit ae235b
 **/
Packit ae235b
char *
Packit ae235b
g_filename_completer_get_completion_suffix (GFilenameCompleter *completer,
Packit ae235b
					    const char *initial_text)
Packit ae235b
{
Packit ae235b
  GList *possible_matches, *l;
Packit ae235b
  char *prefix;
Packit ae235b
  char *suffix;
Packit ae235b
  char *possible_match;
Packit ae235b
  char *lcp;
Packit ae235b
Packit ae235b
  g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
Packit ae235b
  g_return_val_if_fail (initial_text != NULL, NULL);
Packit ae235b
Packit ae235b
  possible_matches = init_completion (completer, initial_text, &prefix);
Packit ae235b
Packit ae235b
  suffix = NULL;
Packit ae235b
  
Packit ae235b
  for (l = possible_matches; l != NULL; l = l->next)
Packit ae235b
    {
Packit ae235b
      possible_match = l->data;
Packit ae235b
      
Packit ae235b
      if (g_str_has_prefix (possible_match, prefix))
Packit ae235b
	{
Packit ae235b
	  if (suffix == NULL)
Packit ae235b
	    suffix = g_strdup (possible_match + strlen (prefix));
Packit ae235b
	  else
Packit ae235b
	    {
Packit ae235b
	      lcp = longest_common_prefix (suffix,
Packit ae235b
					   possible_match + strlen (prefix));
Packit ae235b
	      g_free (suffix);
Packit ae235b
	      suffix = lcp;
Packit ae235b
	      
Packit ae235b
	      if (*suffix == 0)
Packit ae235b
		break;
Packit ae235b
	    }
Packit ae235b
	}
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_free (prefix);
Packit ae235b
  
Packit ae235b
  return suffix;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_filename_completer_get_completions:
Packit ae235b
 * @completer: the filename completer.
Packit ae235b
 * @initial_text: text to be completed.
Packit ae235b
 * 
Packit ae235b
 * Gets an array of completion strings for a given initial text.
Packit ae235b
 * 
Packit ae235b
 * Returns: (array zero-terminated=1) (transfer full): array of strings with possible completions for @initial_text.
Packit ae235b
 * This array must be freed by g_strfreev() when finished. 
Packit ae235b
 **/
Packit ae235b
char **
Packit ae235b
g_filename_completer_get_completions (GFilenameCompleter *completer,
Packit ae235b
				      const char         *initial_text)
Packit ae235b
{
Packit ae235b
  GList *possible_matches, *l;
Packit ae235b
  char *prefix;
Packit ae235b
  char *possible_match;
Packit ae235b
  GPtrArray *res;
Packit ae235b
Packit ae235b
  g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
Packit ae235b
  g_return_val_if_fail (initial_text != NULL, NULL);
Packit ae235b
Packit ae235b
  possible_matches = init_completion (completer, initial_text, &prefix);
Packit ae235b
Packit ae235b
  res = g_ptr_array_new ();
Packit ae235b
  for (l = possible_matches; l != NULL; l = l->next)
Packit ae235b
    {
Packit ae235b
      possible_match = l->data;
Packit ae235b
Packit ae235b
      if (g_str_has_prefix (possible_match, prefix))
Packit ae235b
	g_ptr_array_add (res,
Packit ae235b
			 g_strconcat (initial_text, possible_match + strlen (prefix), NULL));
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_free (prefix);
Packit ae235b
Packit ae235b
  g_ptr_array_add (res, NULL);
Packit ae235b
Packit ae235b
  return (char**)g_ptr_array_free (res, FALSE);
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_filename_completer_set_dirs_only:
Packit ae235b
 * @completer: the filename completer.
Packit ae235b
 * @dirs_only: a #gboolean.
Packit ae235b
 * 
Packit ae235b
 * If @dirs_only is %TRUE, @completer will only 
Packit ae235b
 * complete directory names, and not file names.
Packit ae235b
 **/
Packit ae235b
void
Packit ae235b
g_filename_completer_set_dirs_only (GFilenameCompleter *completer,
Packit ae235b
				    gboolean dirs_only)
Packit ae235b
{
Packit ae235b
  g_return_if_fail (G_IS_FILENAME_COMPLETER (completer));
Packit ae235b
Packit ae235b
  completer->dirs_only = dirs_only;
Packit ae235b
}