Beispiel #1
0
gboolean
ide_file_equal (IdeFile *self,
                IdeFile *other)
{
  g_return_val_if_fail (IDE_IS_FILE (self), FALSE);
  g_return_val_if_fail (IDE_IS_FILE (other), FALSE);

  return g_file_equal (self->file, other->file);
}
Beispiel #2
0
static GtkSourceLanguage *
ide_file_create_language (IdeFile *self)
{
  GtkSourceLanguageManager *manager;
  GtkSourceLanguage *srclang;
  g_autofree gchar *content_type = NULL;
  const gchar *filename;
  gboolean uncertain = FALSE;

  g_assert (IDE_IS_FILE (self));

  filename = g_file_get_basename (self->file);

  if (self->content_type)
    content_type = g_strdup (self->content_type);
  else
    content_type = g_content_type_guess (filename, NULL, 0, &uncertain);

  if (uncertain)
    g_clear_pointer (&content_type, g_free);
  else if (self->content_type == NULL)
    self->content_type = g_strdup (content_type);

  manager = gtk_source_language_manager_get_default ();
  srclang = gtk_source_language_manager_guess_language (manager, filename, content_type);

  return srclang;
}
Beispiel #3
0
/**
 * ide_file_get_temporary_id:
 * @self: (in): A #IdeFile.
 *
 * Gets the #IdeFile:temporary-id property for the file.
 *
 * Temporary files have unique identifiers associated with them so that we can
 * display names such as "unsaved file 1" and know that it will not collide with
 * another temporary file.
 *
 * Files that are not temporary, will return zero.
 *
 * Returns: A positive integer greater than zero if the file is a temporary file.
 */
guint
ide_file_get_temporary_id (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), 0);

  return self->temporary_id;
}
static void
ide_autotools_build_system_get_build_flags_async (IdeBuildSystem      *build_system,
                                                  IdeFile             *file,
                                                  GCancellable        *cancellable,
                                                  GAsyncReadyCallback  callback,
                                                  gpointer             user_data)
{
  IdeAutotoolsBuildSystem *self = (IdeAutotoolsBuildSystem *)build_system;
  g_autoptr(GTask) task = NULL;
  GFile *gfile;

  g_assert (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (self));
  g_assert (IDE_IS_FILE (file));

  EGG_COUNTER_INC (build_flags);

  gfile = ide_file_get_file (file);

  task = g_task_new (self, cancellable, callback, user_data);
  g_task_set_task_data (task, g_object_ref (gfile), g_object_unref);

  ide_autotools_build_system_get_makecache_async (self,
                                                  cancellable,
                                                  ide_autotools_build_system__makecache_cb,
                                                  g_object_ref (task));
}
Beispiel #5
0
gboolean
ide_file_get_is_temporary (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), FALSE);

  return (self->temporary_id != 0);
}
Beispiel #6
0
guint
ide_file_hash (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), 0);

  return g_file_hash (self->file);
}
Beispiel #7
0
static IdeUnsavedFile *
get_unsaved_file (IdeGettextDiagnosticProvider *self,
                  IdeFile                      *file)
{
  g_autoptr(GPtrArray) array = NULL;
  IdeUnsavedFiles *unsaved_files;
  IdeContext *context;
  guint i;

  g_assert (IDE_IS_GETTEXT_DIAGNOSTIC_PROVIDER (self));
  g_assert (IDE_IS_FILE (file));

  context = ide_object_get_context (IDE_OBJECT (self));
  unsaved_files = ide_context_get_unsaved_files (context);
  array = ide_unsaved_files_to_array (unsaved_files);

  for (i = 0; i < array->len; i++)
    {
      IdeUnsavedFile *unsaved_file = g_ptr_array_index (array, i);
      GFile *ufile = ide_unsaved_file_get_file (unsaved_file);
      GFile *ifile = ide_file_get_file (file);

      g_assert (G_IS_FILE (ufile));
      g_assert (G_IS_FILE (ifile));

      if (g_file_equal (ufile, ifile))
        return ide_unsaved_file_ref (unsaved_file);
    }

  return NULL;
}
Beispiel #8
0
/**
 * ide_file_get_file:
 *
 * Retrieves the underlying #GFile represented by @self.
 *
 * Returns: (transfer none): A #GFile.
 */
GFile *
ide_file_get_file (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), NULL);

  return self->file;
}
Beispiel #9
0
static void
ide_file_set_temporary_id (IdeFile *self,
                           guint    temporary_id)
{
  g_return_if_fail (IDE_IS_FILE (self));

  self->temporary_id = temporary_id;
}
/**
 * ide_clang_service_get_translation_unit_async:
 *
 * This function is used to asynchronously retrieve the translation unit for
 * a particular file.
 *
 * If the translation unit is up to date, then no parsing will occur and the
 * existing translation unit will be used.
 *
 * If the translation unit is out of date, then the source file(s) will be
 * parsed via clang_parseTranslationUnit() asynchronously.
 */
void
ide_clang_service_get_translation_unit_async (IdeClangService     *self,
                                              IdeFile             *file,
                                              gint64               min_serial,
                                              GCancellable        *cancellable,
                                              GAsyncReadyCallback  callback,
                                              gpointer             user_data)
{
  IdeClangTranslationUnit *cached;
  g_autoptr(GTask) task = NULL;

  g_return_if_fail (IDE_IS_CLANG_SERVICE (self));
  g_return_if_fail (IDE_IS_FILE (file));
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  task = g_task_new (self, cancellable, callback, user_data);

  /*
   * Clang likes to crash on our temporary files.
   */
  if (ide_file_get_is_temporary (file))
    {
      g_task_return_new_error (task,
                               G_IO_ERROR,
                               G_IO_ERROR_NOT_FOUND,
                               "File does not yet exist, ignoring translation unit request.");
      return;
    }

  if (min_serial == 0)
    {
      IdeContext *context;
      IdeUnsavedFiles *unsaved_files;

      context = ide_object_get_context (IDE_OBJECT (self));
      unsaved_files = ide_context_get_unsaved_files (context);
      min_serial = ide_unsaved_files_get_sequence (unsaved_files);
    }

  /*
   * If we have a cached unit, and it is new enough, then re-use it.
   */
  if ((cached = egg_task_cache_peek (self->units_cache, file)) &&
      (ide_clang_translation_unit_get_serial (cached) >= min_serial))
    {
      g_task_return_pointer (task, g_object_ref (cached), g_object_unref);
      return;
    }

  egg_task_cache_get_async (self->units_cache,
                            file,
                            TRUE,
                            cancellable,
                            ide_clang_service_get_translation_unit_cb,
                            g_object_ref (task));
}
Beispiel #11
0
static void
ide_file_set_path (IdeFile     *self,
                   const gchar *path)
{
  g_return_if_fail (IDE_IS_FILE (self));
  g_return_if_fail (!self->path);

  g_clear_pointer (&self->path, g_free);
  self->path = g_strdup (path);
}
Beispiel #12
0
const gchar *
_ide_file_get_content_type (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), NULL);

  if (self->content_type != NULL)
    return self->content_type;

  return "text/plain";
}
Beispiel #13
0
/**
 * ide_file_find_other_finish:
 *
 * Completes an asynchronous call to ide_file_find_other_async(). This function
 * will try to find a matching file for languages where this exists. Such cases
 * include C and C++ where a .c or .cpp file may have a .h or .hh header. Additional
 * suffixes are implemented including (.c, .cc, .cpp, .cxx, .h, .hh, .hpp, and .hxx).
 *
 * Returns an #IdeFile if successful, otherwise %NULL and @error is set.
 *
 * Returns: (transfer full) (nullable): An #IdeFIle or %NULL.
 */
IdeFile *
ide_file_find_other_finish (IdeFile       *self,
                            GAsyncResult  *result,
                            GError       **error)
{
  g_return_val_if_fail (IDE_IS_FILE (self), NULL);
  g_return_val_if_fail (G_IS_TASK (result), NULL);

  return g_task_propagate_pointer (G_TASK (result), error);
}
static void
ide_file_settings_set_file (IdeFileSettings *self,
                            IdeFile         *file)
{
  IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);

  g_return_if_fail (IDE_IS_FILE_SETTINGS (self));
  g_return_if_fail (IDE_IS_FILE (file));

  if (ide_set_weak_pointer (&priv->file, file))
    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
}
Beispiel #15
0
static void
ide_file_set_file (IdeFile *self,
                   GFile   *file)
{
  g_return_if_fail (IDE_IS_FILE (self));
  g_return_if_fail (G_IS_FILE (file));

  if (file != self->file)
    {
      if (g_set_object (&self->file, file))
        g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
    }
}
void
ide_diagnostic_provider_diagnose_async  (IdeDiagnosticProvider *self,
                                         IdeFile               *file,
                                         GCancellable          *cancellable,
                                         GAsyncReadyCallback    callback,
                                         gpointer               user_data)
{
  g_return_if_fail (IDE_IS_DIAGNOSTIC_PROVIDER (self));
  g_return_if_fail (IDE_IS_FILE (file));
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->diagnose_async (self, file, cancellable, callback, user_data);
}
/**
 * ide_clang_service_get_cached_translation_unit:
 * @self: A #IdeClangService.
 *
 * Gets a cached translation unit if one exists for the file.
 *
 * Returns: (transfer full) (nullable): An #IdeClangTranslationUnit or %NULL.
 */
IdeClangTranslationUnit *
ide_clang_service_get_cached_translation_unit (IdeClangService *self,
                                               IdeFile         *file)
{
  IdeClangTranslationUnit *cached;

  g_return_val_if_fail (IDE_IS_CLANG_SERVICE (self), NULL);
  g_return_val_if_fail (IDE_IS_FILE (file), NULL);

  cached = egg_task_cache_peek (self->units_cache, file);

  return cached ? g_object_ref (cached) : NULL;
}
Beispiel #18
0
void
ide_file_find_other_async (IdeFile             *self,
                           GCancellable        *cancellable,
                           GAsyncReadyCallback  callback,
                           gpointer             user_data)
{
  g_autoptr(GTask) task = NULL;

  g_return_if_fail (IDE_IS_FILE (self));
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  task = g_task_new (self, cancellable, callback, user_data);
  g_task_run_in_thread (task, ide_file_find_other_worker);
}
Beispiel #19
0
void
ide_file_load_settings_async (IdeFile              *self,
                              GCancellable         *cancellable,
                              GAsyncReadyCallback   callback,
                              gpointer              user_data)
{
  g_autoptr(GTask) task = NULL;
  g_autoptr(IdeFileSettings) file_settings = NULL;

  IDE_ENTRY;

  g_return_if_fail (IDE_IS_FILE (self));
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  task = g_task_new (self, cancellable, callback, user_data);

  /* Use shared instance if available */
  if (self->file_settings != NULL)
    {
      g_task_return_pointer (task, g_object_ref (self->file_settings), g_object_unref);
      IDE_EXIT;
    }

  /* Create our new settings instance, races are okay */
  file_settings = ide_file_settings_new (self);

  /* If this is settled immediately (not using editorconfig), then we can use this now
   * and cache the result for later
   */
  if (ide_file_settings_get_settled (file_settings))
    {
      self->file_settings = g_steal_pointer (&file_settings);
      g_task_return_pointer (task, g_object_ref (self->file_settings), g_object_unref);
      IDE_EXIT;
    }

  /*
   * We need to wait until the settings have settled. editorconfig may need to
   * background load a bunch of .editorconfig files off of disk/sshfs/etc to
   * determine the settings.
   */
  g_signal_connect (file_settings,
                    "notify::settled",
                    G_CALLBACK (ide_file__file_settings_settled_cb),
                    g_object_ref (task));
  g_task_set_task_data (task, g_steal_pointer (&file_settings), g_object_unref);

  IDE_EXIT;
}
Beispiel #20
0
void
_ide_file_set_content_type (IdeFile     *self,
                            const gchar *content_type)
{
  g_assert (IDE_IS_FILE (self));
  g_assert (content_type);

  if (0 != g_strcmp0 (self->content_type, content_type))
    {
      g_clear_pointer (&self->content_type, g_free);
      g_clear_object (&self->language);
      self->content_type = g_strdup (content_type);
      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LANGUAGE]);
    }
}
Beispiel #21
0
const gchar *
ide_file_get_path (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), NULL);

  if (g_once_init_enter (&self->path))
    {
      gchar *path;

      path = g_file_get_path (self->file);
      g_once_init_leave (&self->path, path);
    }

  return self->path;
}
Beispiel #22
0
/**
 * ide_file_get_language:
 *
 * Retrieves the #GtkSourceLanguage that was discovered for the file.
 *
 * Returns: (nullable) (transfer none): A #GtkSourceLanguage or %NULL.
 */
GtkSourceLanguage *
ide_file_get_language (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), NULL);

  if (self->language == NULL)
    {
      GtkSourceLanguage *language;

      language = ide_file_create_language (self);
      self->language = language ? g_object_ref (language) : NULL;
    }

  return self->language;
}
void
gb_view_stack_focus_location (GbViewStack       *self,
                              IdeSourceLocation *location)
{
  IdeBufferManager *buffer_manager;
  IdeBuffer *buffer;
  IdeFile *file;
  GFile *gfile;

  g_return_if_fail (GB_IS_VIEW_STACK (self));
  g_return_if_fail (location != NULL);

  if (self->context == NULL)
    return;

  file = ide_source_location_get_file (location);
  gfile = ide_file_get_file (file);

  g_assert (file != NULL);
  g_assert (IDE_IS_FILE (file));

  gfile = ide_file_get_file (file);

  buffer_manager = ide_context_get_buffer_manager (self->context);
  buffer = ide_buffer_manager_find_buffer (buffer_manager, gfile);

  if (buffer != NULL && GB_IS_DOCUMENT (buffer))
    {
      GtkWidget *active_view;

      gb_view_stack_focus_document (self, GB_DOCUMENT (buffer));
      active_view = gb_view_stack_get_active_view (self);
      g_assert (GB_DOCUMENT (buffer) == gb_view_get_document (GB_VIEW (active_view)));
      gb_view_navigate_to (GB_VIEW (active_view), location);
    }
  else
    {
      g_autoptr(GTask) task = NULL;

      task = g_task_new (self, NULL, NULL, NULL);
      g_task_set_task_data (task, ide_source_location_ref (location),
                            (GDestroyNotify)ide_source_location_unref);
      ide_buffer_manager_load_file_async (buffer_manager, file, FALSE, NULL, NULL,
                                          gb_view_stack__navigate_to_load_cb,
                                          g_object_ref (task));
    }
}
Beispiel #24
0
/**
 * _ide_file_get_source_file:
 * @self: (in): A #IdeFile.
 *
 * Gets the GtkSourceFile for the #IdeFile.
 *
 * Returns: (transfer none): A #GtkSourceFile.
 */
GtkSourceFile *
_ide_file_get_source_file (IdeFile *self)
{
  g_return_val_if_fail (IDE_IS_FILE (self), NULL);

  if (g_once_init_enter (&self->source_file))
    {
      GtkSourceFile *source_file;

      source_file = gtk_source_file_new ();
      gtk_source_file_set_location (source_file, self->file);

      g_once_init_leave (&self->source_file, source_file);
    }

  return self->source_file;
}
/**
 * ide_build_system_get_build_flags_async:
 *
 * Asynchronously requests the build flags for a file. For autotools and C based projects, this
 * would be similar to the $CFLAGS variable and is suitable for generating warnings and errors
 * with clang.
 */
void
ide_build_system_get_build_flags_async (IdeBuildSystem      *self,
                                        IdeFile             *file,
                                        GCancellable        *cancellable,
                                        GAsyncReadyCallback  callback,
                                        gpointer             user_data)
{
  g_autoptr(GTask) task = NULL;

  g_return_if_fail (IDE_IS_BUILD_SYSTEM (self));
  g_return_if_fail (IDE_IS_FILE (file));
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  if (IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_flags_async)
    return IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_flags_async (self, file, cancellable,
                                                                     callback, user_data);

  task = g_task_new (self, cancellable, callback, user_data);
  g_task_return_pointer (task, NULL, NULL);
}
Beispiel #26
0
/**
 * ide_source_location_new:
 * @file: an #IdeFile
 * @line: the line number starting from zero
 * @line_offset: the character offset within the line
 * @offset: the character offset in the file
 *
 * Creates a new #IdeSourceLocation, using the file, line, column, and character
 * offset provided.
 *
 * Returns: (transfer full): A newly allocated #IdeSourceLocation.
 */
IdeSourceLocation *
ide_source_location_new (IdeFile *file,
                         guint    line,
                         guint    line_offset,
                         guint    offset)
{
  IdeSourceLocation *ret;

  g_return_val_if_fail (IDE_IS_FILE (file), NULL);

  ret = g_slice_new0 (IdeSourceLocation);
  ret->ref_count = 1;
  ret->file = g_object_ref (file);
  ret->line = MIN (G_MAXINT, line);
  ret->line_offset = MIN (G_MAXINT, line_offset);
  ret->offset = offset;

  DZL_COUNTER_INC (instances);

  return ret;
}
Beispiel #27
0
static void
ide_file__file_settings_settled_cb (IdeFileSettings *file_settings,
                                    GParamSpec      *pspec,
                                    gpointer         user_data)
{
  g_autoptr(GTask) task = user_data;
  IdeFile *self;

  IDE_ENTRY;

  g_assert (IDE_IS_FILE_SETTINGS (file_settings));
  g_assert (G_IS_TASK (task));
  self = g_task_get_source_object (task);
  g_assert (IDE_IS_FILE (self));

  g_signal_handlers_disconnect_by_func (file_settings,
                                        G_CALLBACK (ide_file__file_settings_settled_cb),
                                        task);
  g_set_object (&self->file_settings, file_settings);
  g_task_return_pointer (task, g_object_ref (file_settings), g_object_unref);

  IDE_EXIT;
}
IdeFileSettings *
ide_file_settings_new (IdeFile *file)
{
  IdeFileSettingsPrivate *priv;
  GIOExtensionPoint *extension_point;
  IdeFileSettings *ret;
  IdeContext *context;
  GList *list;

  g_return_val_if_fail (IDE_IS_FILE (file), NULL);

  context = ide_object_get_context (IDE_OBJECT (file));
  ret = g_object_new (IDE_TYPE_FILE_SETTINGS,
                      "context", context,
                      "file", file,
                      NULL);
  priv = ide_file_settings_get_instance_private (ret);

  extension_point = g_io_extension_point_lookup (IDE_FILE_SETTINGS_EXTENSION_POINT);
  list = g_io_extension_point_get_extensions (extension_point);

  /*
   * Don't allow our unsettled count to hit zero until we are finished.
   */
  priv->unsettled_count++;

  for (; list; list = list->next)
    {
      GIOExtension *extension = list->data;
      g_autoptr(IdeFileSettings) child = NULL;
      GType gtype;

      gtype = g_io_extension_get_type (extension);

      if (!g_type_is_a (gtype, IDE_TYPE_FILE_SETTINGS))
        {
          g_warning ("%s is not an IdeFileSettings", g_type_name (gtype));
          continue;
        }

      child = g_object_new (gtype,
                            "file", file,
                            "context", context,
                            NULL);

      if (G_IS_INITABLE (child))
        {
          g_autoptr(GError) error = NULL;

          if (!g_initable_init (G_INITABLE (child), NULL, &error))
            g_warning ("%s", error->message);
        }
      else if (G_IS_ASYNC_INITABLE (child))
        {
          priv->unsettled_count++;
          g_async_initable_init_async (G_ASYNC_INITABLE (child),
                                       G_PRIORITY_DEFAULT,
                                       NULL,
                                       ide_file_settings__init_cb,
                                       g_object_ref (ret));
        }

      _ide_file_settings_append (ret, child);
    }

  priv->unsettled_count--;

  return ret;
}
Beispiel #29
0
static void
populate_cache (EggTaskCache  *cache,
                gconstpointer  key,
                GTask         *task,
                gpointer       user_data)
{
  IdeGettextDiagnosticProvider *self = user_data;
  g_autoptr(IdeUnsavedFile) unsaved_file = NULL;
  g_autoptr(GSubprocess) subprocess = NULL;
  GtkSourceLanguage *language;
  const gchar *language_id;
  const gchar *xgettext_lang;
  const gchar *temp_path;
  TranslationUnit *unit;
  IdeFile *file = (IdeFile *)key;
  GCancellable *cancellable;
  GError *error = NULL;
  GPtrArray *args;

  g_assert (EGG_IS_TASK_CACHE (cache));
  g_assert (IDE_IS_FILE (file));
  g_assert (IDE_IS_GETTEXT_DIAGNOSTIC_PROVIDER (self));

  cancellable = g_task_get_cancellable (task);

  if (NULL == (unsaved_file = get_unsaved_file (self, file)))
    {
      g_task_return_new_error (task,
                               G_IO_ERROR,
                               G_IO_ERROR_NOT_FOUND,
                               "Failed to locate file contents");
      return;
    }

  if (NULL == (language = ide_file_get_language (file)) ||
      NULL == (language_id = gtk_source_language_get_id (language)) ||
      NULL == (xgettext_lang = id_to_xgettext_language (language_id)))
    {
      g_task_return_new_error (task,
                               G_IO_ERROR,
                               G_IO_ERROR_NOT_SUPPORTED,
                               "Failed to determine language type");
      return;
    }

  if (!ide_unsaved_file_persist (unsaved_file, cancellable, &error))
    {
      g_task_return_error (task, error);
      return;
    }

  temp_path = ide_unsaved_file_get_temp_path (unsaved_file);

  g_assert (temp_path != NULL);

  args = g_ptr_array_new ();
  g_ptr_array_add (args, "xgettext");
  g_ptr_array_add (args, "--check=ellipsis-unicode");
  g_ptr_array_add (args, "--check=quote-unicode");
  g_ptr_array_add (args, "--check=space-ellipsis");
  g_ptr_array_add (args, "-k_");
  g_ptr_array_add (args, "-kN_");
  g_ptr_array_add (args, "-L");
  g_ptr_array_add (args, (gchar *)xgettext_lang);
  g_ptr_array_add (args, "-o");
  g_ptr_array_add (args, "-");
  g_ptr_array_add (args, (gchar *)temp_path);
  g_ptr_array_add (args, NULL);

#ifdef IDE_ENABLE_TRACE
  {
    g_autofree gchar *str = NULL;
    str = g_strjoinv (" ", (gchar **)args->pdata);
    IDE_TRACE_MSG ("Launching '%s'", str);
  }
#endif

  subprocess = g_subprocess_newv ((const gchar * const *)args->pdata,
                                  G_SUBPROCESS_FLAGS_STDIN_PIPE
                                  | G_SUBPROCESS_FLAGS_STDOUT_PIPE
                                  | G_SUBPROCESS_FLAGS_STDERR_PIPE,
                                  &error);

  g_ptr_array_free (args, TRUE);

  if (subprocess == NULL)
    {
      g_task_return_error (task, error);
      return;
    }

  unit = g_slice_new0 (TranslationUnit);
  unit->file = g_object_ref (file);
  unit->unsaved_file = ide_unsaved_file_ref (unsaved_file);
  g_task_set_task_data (task, unit, (GDestroyNotify)translation_unit_free);

  g_subprocess_wait_async (subprocess,
                           cancellable,
                           subprocess_wait_cb,
                           g_object_ref (task));
}
Beispiel #30
0
static void
ide_file_find_other_worker (GTask        *task,
                            gpointer      source_object,
                            gpointer      task_data,
                            GCancellable *cancellable)
{
  IdeFile *self = source_object;
  const gchar *src_suffixes[] = { "c", "cc", "cpp", "cxx", NULL };
  const gchar *hdr_suffixes[] = { "h", "hh", "hpp", "hxx", NULL };
  const gchar **target = NULL;
  g_autofree gchar *prefix = NULL;
  g_autofree gchar *uri = NULL;
  gsize i;

  g_assert (IDE_IS_FILE (self));
  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));

  uri = g_file_get_uri (self->file);

  if (has_suffix (uri, src_suffixes))
    {
      target = hdr_suffixes;
    }
  else if (has_suffix (uri, hdr_suffixes))
    {
      target = src_suffixes;
    }
  else
    {
      g_task_return_new_error (task,
                               G_IO_ERROR,
                               G_IO_ERROR_INVALID_FILENAME,
                               "File is missing a suffix.");
      return;
    }

  prefix = g_strndup (uri, strrchr (uri, '.') - uri);

  for (i = 0; target [i]; i++)
    {
      g_autofree gchar *new_uri = NULL;
      g_autoptr(GFile) gfile = NULL;

      new_uri = g_strdup_printf ("%s.%s", prefix, target [i]);
      gfile = g_file_new_for_uri (new_uri);

      if (g_file_query_exists (gfile, cancellable))
        {
          g_autofree gchar *path = NULL;
          IdeContext *context;
          IdeVcs *vcs;
          IdeFile *ret;
          GFile *workdir;

          context = ide_object_get_context (IDE_OBJECT (self));
          vcs = ide_context_get_vcs (context);
          workdir = ide_vcs_get_working_directory (vcs);
          path = g_file_get_relative_path (workdir, gfile);

          ret = g_object_new (IDE_TYPE_FILE,
                              "context", context,
                              "path", path,
                              "file", gfile,
                              NULL);
          g_task_return_pointer (task, ret, g_object_unref);
          return;
        }
    }

  g_task_return_new_error (task,
                           G_IO_ERROR,
                           G_IO_ERROR_NOT_FOUND,
                           "Failed to locate other file.");
}