[gnome-builder/wip/chergert/docs] wip
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/docs] wip
- Date: Thu, 11 Jul 2019 19:33:21 +0000 (UTC)
commit dc1f8c29046a86a3e45ae1e3a5363760b6e9d2c3
Author: Christian Hergert <chergert redhat com>
Date: Fri Jul 5 16:31:59 2019 -0700
wip
src/libide/docs/ide-docs-item.c | 668 ++++++++++++++++++++++
src/libide/docs/ide-docs-item.h | 126 ++++
src/libide/docs/ide-docs-library.c | 314 ++++++++++
src/libide/docs/ide-docs-library.h | 49 ++
src/libide/docs/ide-docs-provider.c | 162 ++++++
src/libide/docs/ide-docs-provider.h | 80 +++
src/libide/docs/ide-docs-query.c | 255 +++++++++
src/libide/docs/ide-docs-query.h | 52 ++
src/libide/docs/ide-docs-search-group.c | 342 +++++++++++
src/libide/docs/ide-docs-search-group.h | 45 ++
src/libide/docs/ide-docs-search-group.ui | 45 ++
src/libide/docs/ide-docs-search-row.c | 220 +++++++
src/libide/docs/ide-docs-search-row.h | 36 ++
src/libide/docs/ide-docs-search-row.ui | 31 +
src/libide/docs/ide-docs-search-section.c | 247 ++++++++
src/libide/docs/ide-docs-search-section.h | 39 ++
src/libide/docs/ide-docs-search-view.c | 236 ++++++++
src/libide/docs/ide-docs-search-view.h | 47 ++
src/libide/docs/ide-docs-search-view.ui | 38 ++
src/libide/docs/ide-docs-workspace.c | 114 ++++
src/libide/docs/ide-docs-workspace.h | 35 ++
src/libide/docs/ide-docs-workspace.ui | 40 ++
src/libide/docs/libide-docs.c | 0
src/libide/docs/libide-docs.gresource.xml | 12 +
src/libide/docs/libide-docs.h | 34 ++
src/libide/docs/meson.build | 98 ++++
src/libide/meson.build | 1 +
src/libide/themes/libide-themes.gresource.xml | 1 +
src/libide/themes/themes/shared.css | 1 +
src/libide/themes/themes/shared/shared-docs.css | 25 +
src/meson.build | 1 +
src/plugins/devhelp/devhelp-plugin.c | 4 +
src/plugins/devhelp/devhelp2-parser.c | 318 ++++++++++
src/plugins/devhelp/devhelp2-parser.h | 62 ++
src/plugins/devhelp/gbp-devhelp-docs-provider.c | 269 +++++++++
src/plugins/devhelp/gbp-devhelp-docs-provider.h | 31 +
src/plugins/devhelp/meson.build | 2 +
src/plugins/docsui/docsui-plugin.c | 34 ++
src/plugins/docsui/docsui.gresource.xml | 6 +
src/plugins/docsui/docsui.plugin | 11 +
src/plugins/docsui/gbp-docsui-application-addin.c | 109 ++++
src/plugins/docsui/gbp-docsui-application-addin.h | 31 +
src/plugins/docsui/meson.build | 12 +
src/plugins/meson.build | 2 +
44 files changed, 4285 insertions(+)
---
diff --git a/src/libide/docs/ide-docs-item.c b/src/libide/docs/ide-docs-item.c
new file mode 100644
index 000000000..5034c8b55
--- /dev/null
+++ b/src/libide/docs/ide-docs-item.c
@@ -0,0 +1,668 @@
+/* ide-docs-item.c
+ *
+ * Copyright 2019 Christian Hergert <unknown domain org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-item"
+
+#include "config.h"
+
+#include "ide-docs-enums.h"
+#include "ide-docs-item.h"
+
+typedef struct
+{
+ IdeDocsItem *parent;
+ GQueue children;
+ GList link;
+ gchar *id;
+ gchar *title;
+ gchar *display_name;
+ gchar *since;
+ gchar *url;
+ IdeDocsItemKind kind;
+ gint priority;
+ guint deprecated : 1;
+} IdeDocsItemPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDocsItem, ide_docs_item, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_DEPRECATED,
+ PROP_DISPLAY_NAME,
+ PROP_ID,
+ PROP_KIND,
+ PROP_PRIORITY,
+ PROP_SINCE,
+ PROP_TITLE,
+ PROP_URL,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/**
+ * ide_docs_item_new:
+ *
+ * Create a new #IdeDocsItem.
+ *
+ * Returns: (transfer full): a newly created #IdeDocsItem
+ *
+ * Since: 3.34
+ */
+IdeDocsItem *
+ide_docs_item_new (void)
+{
+ return g_object_new (IDE_TYPE_DOCS_ITEM, NULL);
+}
+
+void
+ide_docs_item_remove (IdeDocsItem *self,
+ IdeDocsItem *child)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+ IdeDocsItemPrivate *child_priv = ide_docs_item_get_instance_private (child);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (child));
+ g_return_if_fail (child_priv->parent == self);
+
+ g_queue_unlink (&priv->children, &child_priv->link);
+ child_priv->parent = NULL;
+ g_object_unref (child);
+}
+
+static void
+ide_docs_item_dispose (GObject *object)
+{
+ IdeDocsItem *self = (IdeDocsItem *)object;
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ if (priv->parent != NULL)
+ ide_docs_item_remove (priv->parent, self);
+
+ G_OBJECT_CLASS (ide_docs_item_parent_class)->dispose (object);
+}
+
+static void
+ide_docs_item_finalize (GObject *object)
+{
+ IdeDocsItem *self = (IdeDocsItem *)object;
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_clear_pointer (&priv->id, g_free);
+ g_clear_pointer (&priv->since, g_free);
+ g_clear_pointer (&priv->display_name, g_free);
+ g_clear_pointer (&priv->title, g_free);
+ g_clear_pointer (&priv->url, g_free);
+
+ G_OBJECT_CLASS (ide_docs_item_parent_class)->finalize (object);
+}
+
+static void
+ide_docs_item_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsItem *self = IDE_DOCS_ITEM (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_string (value, ide_docs_item_get_id (self));
+ break;
+
+ case PROP_DISPLAY_NAME:
+ g_value_set_string (value, ide_docs_item_get_display_name (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, ide_docs_item_get_title (self));
+ break;
+
+ case PROP_SINCE:
+ g_value_set_string (value, ide_docs_item_get_since (self));
+ break;
+
+ case PROP_KIND:
+ g_value_set_enum (value, ide_docs_item_get_kind (self));
+ break;
+
+ case PROP_DEPRECATED:
+ g_value_set_boolean (value, ide_docs_item_get_deprecated (self));
+ break;
+
+ case PROP_URL:
+ g_value_set_string (value, ide_docs_item_get_url (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_item_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsItem *self = IDE_DOCS_ITEM (object);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ ide_docs_item_set_id (self, g_value_get_string (value));
+ break;
+
+ case PROP_DISPLAY_NAME:
+ ide_docs_item_set_display_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_TITLE:
+ ide_docs_item_set_title (self, g_value_get_string (value));
+ break;
+
+ case PROP_SINCE:
+ ide_docs_item_set_since (self, g_value_get_string (value));
+ break;
+
+ case PROP_KIND:
+ ide_docs_item_set_kind (self, g_value_get_enum (value));
+ break;
+
+ case PROP_DEPRECATED:
+ ide_docs_item_set_deprecated (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_URL:
+ ide_docs_item_set_url (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_item_class_init (IdeDocsItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_docs_item_dispose;
+ object_class->finalize = ide_docs_item_finalize;
+ object_class->get_property = ide_docs_item_get_property;
+ object_class->set_property = ide_docs_item_set_property;
+
+ properties [PROP_ID] =
+ g_param_spec_string ("id",
+ "Id",
+ "The identifier for the item, if any",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DISPLAY_NAME] =
+ g_param_spec_string ("display-name",
+ "Display Name",
+ "The display-name of the item, possibily containing pango markup",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "The title of the item",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SINCE] =
+ g_param_spec_string ("since",
+ "Since",
+ "When the item is added",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DEPRECATED] =
+ g_param_spec_string ("deprecated",
+ "Deprecated",
+ "When the item was deprecated",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_URL] =
+ g_param_spec_string ("url",
+ "Url",
+ "The url for the documentation",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_KIND] =
+ g_param_spec_enum ("kind",
+ "Kind",
+ "The kind of item",
+ IDE_TYPE_DOCS_ITEM_KIND,
+ IDE_DOCS_ITEM_KIND_NONE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PRIORITY] =
+ g_param_spec_int ("priority",
+ "Priority",
+ "The priority of the item",
+ G_MININT, G_MAXINT, 0,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_docs_item_init (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ priv->link.data = self;
+}
+
+const gchar *
+ide_docs_item_get_id (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ return priv->id;
+}
+
+void
+ide_docs_item_set_id (IdeDocsItem *self,
+ const gchar *id)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (g_strcmp0 (id, priv->id) != 0)
+ {
+ g_free (priv->id);
+ priv->id = g_strdup (id);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+ }
+}
+
+const gchar *
+ide_docs_item_get_display_name (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ return priv->display_name;
+}
+
+void
+ide_docs_item_set_display_name (IdeDocsItem *self,
+ const gchar *display_name)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (g_strcmp0 (display_name, priv->display_name) != 0)
+ {
+ g_free (priv->display_name);
+ priv->display_name = g_strdup (display_name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]);
+ }
+}
+
+const gchar *
+ide_docs_item_get_title (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ return priv->title;
+}
+
+void
+ide_docs_item_set_title (IdeDocsItem *self,
+ const gchar *title)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (g_strcmp0 (title, priv->title) != 0)
+ {
+ g_free (priv->title);
+ priv->title = g_strdup (title);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
+ }
+}
+
+const gchar *
+ide_docs_item_get_url (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ return priv->url;
+}
+
+void
+ide_docs_item_set_url (IdeDocsItem *self,
+ const gchar *url)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (g_strcmp0 (url, priv->url) != 0)
+ {
+ g_free (priv->url);
+ priv->url = g_strdup (url);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_URL]);
+ }
+}
+
+const gchar *
+ide_docs_item_get_since (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ return priv->since;
+}
+
+void
+ide_docs_item_set_since (IdeDocsItem *self,
+ const gchar *since)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (g_strcmp0 (since, priv->since) != 0)
+ {
+ g_free (priv->since);
+ priv->since = g_strdup (since);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SINCE]);
+ }
+}
+
+gboolean
+ide_docs_item_get_deprecated (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), FALSE);
+
+ return priv->deprecated;
+}
+
+void
+ide_docs_item_set_deprecated (IdeDocsItem *self,
+ gboolean deprecated)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ deprecated = !!deprecated;
+
+ if (deprecated != priv->deprecated)
+ {
+ priv->deprecated = deprecated;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEPRECATED]);
+ }
+}
+
+IdeDocsItemKind
+ide_docs_item_get_kind (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), FALSE);
+
+ return priv->kind;
+}
+
+void
+ide_docs_item_set_kind (IdeDocsItem *self,
+ IdeDocsItemKind kind)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (kind != priv->kind)
+ {
+ priv->kind = kind;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KIND]);
+ }
+}
+
+gboolean
+ide_docs_item_has_child (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), FALSE);
+
+ return priv->children.length > 0;
+}
+
+gboolean
+ide_docs_item_is_root (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), FALSE);
+
+ return priv->parent == NULL;
+}
+
+/**
+ * ide_docs_item_get_parent:
+ *
+ * Get the parent #IdeDocsItem if set.
+ *
+ * Returns: (transfer none): an #IdeDocsItem or %NULL
+ *
+ * Since: 3.34
+ */
+IdeDocsItem *
+ide_docs_item_get_parent (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ return priv->parent;
+}
+
+/**
+ * ide_docs_item_get_n_children:
+ * @self: a #IdeDocsItem
+ *
+ * Gets the nubmer of children #IdeDocsItem contained by @self.
+ *
+ * Returns: the number of children
+ *
+ * Since: 3.34
+ */
+guint
+ide_docs_item_get_n_children (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), 0);
+
+ return priv->children.length;
+}
+
+/**
+ * ide_docs_item_get_children:
+ * @self: an #IdeDocsItem
+ *
+ * Gets a #GList of #IdeDocsItem that are direct children of @self.
+ *
+ * The result may not be modified or freed.
+ *
+ * Returns: (transfer none) (element-type Ide.DocsItem): a #GList of
+ * #IdeDocsItem.
+ *
+ * Since: 3.34
+ */
+const GList *
+ide_docs_item_get_children (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ return priv->children.head;
+}
+
+void
+ide_docs_item_append (IdeDocsItem *self,
+ IdeDocsItem *child)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+ IdeDocsItemPrivate *child_priv = ide_docs_item_get_instance_private (child);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (child));
+ g_return_if_fail (child_priv->parent == NULL);
+ g_return_if_fail (child_priv->link.prev == NULL);
+ g_return_if_fail (child_priv->link.next == NULL);
+
+ g_object_ref (child);
+ child_priv->parent = self;
+ g_queue_push_tail_link (&priv->children, &child_priv->link);
+}
+
+void
+ide_docs_item_prepend (IdeDocsItem *self,
+ IdeDocsItem *child)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+ IdeDocsItemPrivate *child_priv = ide_docs_item_get_instance_private (child);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (child));
+ g_return_if_fail (child_priv->parent == NULL);
+ g_return_if_fail (child_priv->link.prev == NULL);
+ g_return_if_fail (child_priv->link.next == NULL);
+
+ g_object_ref (child);
+ child_priv->parent = self;
+ g_queue_push_head_link (&priv->children, &child_priv->link);
+}
+
+gint
+ide_docs_item_get_priority (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), 0);
+
+ return priv->priority;
+}
+
+void
+ide_docs_item_set_priority (IdeDocsItem *self,
+ gint priority)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (priority != priv->priority)
+ {
+ priv->priority = priority;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PRIORITY]);
+ }
+}
+
+/**
+ * ide_docs_item_find_child_by_id:
+ * @self: a #IdeDocsItem
+ * @id: the id of the child to locate
+ *
+ * Finds a child item based on the id of the child.
+ *
+ * Returns: (transfer none) (nullable): an #IdeDocsItem or %NULL
+ *
+ * Since: 3.34
+ */
+IdeDocsItem *
+ide_docs_item_find_child_by_id (IdeDocsItem *self,
+ const gchar *id)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (self), NULL);
+
+ for (const GList *iter = priv->children.head;
+ iter != NULL;
+ iter = iter->next)
+ {
+ IdeDocsItem *child = iter->data;
+ IdeDocsItemPrivate *child_priv = ide_docs_item_get_instance_private (child);
+
+ g_assert (IDE_IS_DOCS_ITEM (child));
+
+ if (g_strcmp0 (child_priv->id, id) == 0)
+ return child;
+ }
+
+ return NULL;
+}
+
+static gint
+sort_by_priority (IdeDocsItem *a,
+ IdeDocsItem *b)
+{
+ gint prio_a = ide_docs_item_get_priority (a);
+ gint prio_b = ide_docs_item_get_priority (b);
+
+ if (prio_a < prio_b)
+ return -1;
+ else if (prio_a > prio_b)
+ return 1;
+ else
+ return 0;
+}
+
+void
+ide_docs_item_sort_by_priority (IdeDocsItem *self)
+{
+ IdeDocsItemPrivate *priv = ide_docs_item_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_DOCS_ITEM (self));
+
+ if (priv->children.length != 0)
+ g_queue_sort (&priv->children,
+ (GCompareDataFunc) sort_by_priority,
+ NULL);
+}
diff --git a/src/libide/docs/ide-docs-item.h b/src/libide/docs/ide-docs-item.h
new file mode 100644
index 000000000..f69885e91
--- /dev/null
+++ b/src/libide/docs/ide-docs-item.h
@@ -0,0 +1,126 @@
+/* ide-docs-item.h
+ *
+ * Copyright 2019 Christian Hergert <unknown domain org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_ITEM (ide_docs_item_get_type())
+
+typedef enum
+{
+ IDE_DOCS_ITEM_KIND_NONE = 0,
+ IDE_DOCS_ITEM_KIND_COLLECTION,
+ IDE_DOCS_ITEM_KIND_BOOK,
+ IDE_DOCS_ITEM_KIND_CHAPTER,
+ IDE_DOCS_ITEM_KIND_CLASS,
+ IDE_DOCS_ITEM_KIND_CONSTANT,
+ IDE_DOCS_ITEM_KIND_ENUM,
+ IDE_DOCS_ITEM_KIND_FUNCTION,
+ IDE_DOCS_ITEM_KIND_MACRO,
+ IDE_DOCS_ITEM_KIND_MEMBER,
+ IDE_DOCS_ITEM_KIND_METHOD,
+ IDE_DOCS_ITEM_KIND_PROPERTY,
+ IDE_DOCS_ITEM_KIND_SIGNAL,
+ IDE_DOCS_ITEM_KIND_STRUCT,
+ IDE_DOCS_ITEM_KIND_UNION,
+} IdeDocsItemKind;
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_DERIVABLE_TYPE (IdeDocsItem, ide_docs_item, IDE, DOCS_ITEM, GObject)
+
+struct _IdeDocsItemClass
+{
+ GObjectClass parent_class;
+
+ /*< private >*/
+ gpointer _reserved[16];
+};
+
+IDE_AVAILABLE_IN_3_34
+IdeDocsItem *ide_docs_item_new (void);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_item_get_id (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_id (IdeDocsItem *self,
+ const gchar *id);
+IDE_AVAILABLE_IN_3_34
+IdeDocsItem *ide_docs_item_find_child_by_id (IdeDocsItem *self,
+ const gchar *id);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_item_get_title (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_title (IdeDocsItem *self,
+ const gchar *title);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_item_get_display_name (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_display_name (IdeDocsItem *self,
+ const gchar *display_name);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_item_get_url (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_url (IdeDocsItem *self,
+ const gchar *url);
+IDE_AVAILABLE_IN_3_34
+IdeDocsItemKind ide_docs_item_get_kind (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_kind (IdeDocsItem *self,
+ IdeDocsItemKind kind);
+IDE_AVAILABLE_IN_3_34
+gboolean ide_docs_item_get_deprecated (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_deprecated (IdeDocsItem *self,
+ gboolean deprecated);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_item_get_since (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_since (IdeDocsItem *self,
+ const gchar *since);
+IDE_AVAILABLE_IN_3_34
+gint ide_docs_item_get_priority (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_set_priority (IdeDocsItem *self,
+ gint priority);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_append (IdeDocsItem *self,
+ IdeDocsItem *child);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_prepend (IdeDocsItem *self,
+ IdeDocsItem *child);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_remove (IdeDocsItem *self,
+ IdeDocsItem *child);
+IDE_AVAILABLE_IN_3_34
+gboolean ide_docs_item_has_child (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+guint ide_docs_item_get_n_children (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+const GList *ide_docs_item_get_children (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+gboolean ide_docs_item_is_root (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+IdeDocsItem *ide_docs_item_get_parent (IdeDocsItem *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_item_sort_by_priority (IdeDocsItem *self);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-library.c b/src/libide/docs/ide-docs-library.c
new file mode 100644
index 000000000..76c49d950
--- /dev/null
+++ b/src/libide/docs/ide-docs-library.c
@@ -0,0 +1,314 @@
+/* ide-docs-library.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-library"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-plugins.h>
+#include <libide-threading.h>
+
+#include "ide-docs-library.h"
+#include "ide-docs-provider.h"
+
+struct _IdeDocsLibrary
+{
+ IdeObject parent_instance;
+ IdeExtensionSetAdapter *providers;
+};
+
+typedef struct
+{
+ GCancellable *cancellable;
+ IdeDocsQuery *query;
+ IdeDocsItem *results;
+ guint n_active;
+} Search;
+
+G_DEFINE_TYPE (IdeDocsLibrary, ide_docs_library, IDE_TYPE_OBJECT)
+
+static void
+search_free (Search *search)
+{
+ g_clear_object (&search->results);
+ g_clear_object (&search->cancellable);
+ g_clear_object (&search->query);
+ g_slice_free (Search, search);
+}
+
+static void
+ide_docs_library_init_provider_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeDocsProvider *provider = (IdeDocsProvider *)object;
+ g_autoptr(IdeDocsLibrary) self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (IDE_IS_DOCS_PROVIDER (provider));
+ g_assert (G_IS_ASYNC_INITABLE (provider));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_DOCS_LIBRARY (self));
+
+ if (!g_async_initable_init_finish (G_ASYNC_INITABLE (provider), result, &error))
+ g_warning ("%s failed to initialize: %s", G_OBJECT_TYPE_NAME (provider), error->message);
+}
+
+static void
+on_extension_added_cb (IdeExtensionSetAdapter *adapter,
+ PeasPluginInfo *plugin,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeDocsProvider *provider = (IdeDocsProvider *)exten;
+ IdeDocsLibrary *self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
+ g_assert (plugin != NULL);
+ g_assert (IDE_IS_DOCS_PROVIDER (provider));
+ g_assert (IDE_IS_DOCS_LIBRARY (self));
+
+ if (G_IS_INITABLE (provider) && !g_initable_init (G_INITABLE (provider), NULL, &error))
+ g_warning ("%s failed to initialize: %s", G_OBJECT_TYPE_NAME (provider), error->message);
+ else if (G_IS_ASYNC_INITABLE (provider))
+ g_async_initable_init_async (G_ASYNC_INITABLE (provider),
+ G_PRIORITY_DEFAULT,
+ NULL,
+ ide_docs_library_init_provider_cb,
+ g_object_ref (self));
+}
+
+static void
+ide_docs_library_parent_set (IdeObject *object,
+ IdeObject *parent)
+{
+ IdeDocsLibrary *self = (IdeDocsLibrary *)object;
+
+ g_assert (IDE_IS_OBJECT (object));
+ g_assert (!parent || IDE_IS_OBJECT (parent));
+
+ if (parent == NULL)
+ return;
+
+ self->providers = ide_extension_set_adapter_new (object,
+ peas_engine_get_default (),
+ IDE_TYPE_DOCS_PROVIDER,
+ NULL, NULL);
+ g_signal_connect (self->providers,
+ "extension-added",
+ G_CALLBACK (on_extension_added_cb),
+ self);
+ ide_extension_set_adapter_foreach (self->providers, on_extension_added_cb, self);
+}
+
+static void
+ide_docs_library_destroy (IdeObject *object)
+{
+ IdeDocsLibrary *self = (IdeDocsLibrary *)object;
+
+ ide_clear_and_destroy_object (&self->providers);
+
+ IDE_OBJECT_CLASS (ide_docs_library_parent_class)->destroy (object);
+}
+
+static void
+ide_docs_library_class_init (IdeDocsLibraryClass *klass)
+{
+ IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
+
+ i_object_class->parent_set = ide_docs_library_parent_set;
+ i_object_class->destroy = ide_docs_library_destroy;
+}
+
+static void
+ide_docs_library_init (IdeDocsLibrary *self)
+{
+}
+
+/**
+ * ide_docs_library_from_context:
+ *
+ * Gets the #IdeDocsLibrary for the context.
+ *
+ * Returns: (transfer none): an #IdeDocsLibrary
+ *
+ * Since: 3.34
+ */
+IdeDocsLibrary *
+ide_docs_library_from_context (IdeContext *context)
+{
+ g_autoptr(IdeDocsLibrary) ensured = NULL;
+
+ g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+ if ((ensured = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_DOCS_LIBRARY)))
+ return ide_context_peek_child_typed (context, IDE_TYPE_DOCS_LIBRARY);
+
+ return NULL;
+}
+
+static void
+ide_docs_library_search_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeDocsProvider *provider = (IdeDocsProvider *)object;
+ g_autoptr(GListModel) model = NULL;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+ Search *search;
+
+ g_assert (IDE_IS_TASK (task));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_DOCS_PROVIDER (provider));
+
+ search = ide_task_get_task_data (task);
+
+ if (!ide_docs_provider_search_finish (provider, result, &error))
+ g_warning ("Search failed: %s: %s",
+ G_OBJECT_TYPE_NAME (provider),
+ error->message);
+
+ search->n_active--;
+
+ if (search->n_active == 0)
+ ide_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_docs_library_search_foreach_cb (IdeExtensionSetAdapter *adapter,
+ PeasPluginInfo *plugin,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeDocsProvider *provider = (IdeDocsProvider *)exten;
+ IdeTask *task = user_data;
+ Search *search;
+
+ g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
+ g_assert (plugin != NULL);
+ g_assert (IDE_IS_DOCS_PROVIDER (provider));
+ g_assert (IDE_IS_TASK (task));
+
+ search = ide_task_get_task_data (task);
+ search->n_active++;
+
+ ide_docs_provider_search_async (provider,
+ search->query,
+ search->results,
+ search->cancellable,
+ ide_docs_library_search_cb,
+ g_object_ref (task));
+}
+
+/**
+ * ide_docs_library_search_async:
+ * @self: an #IdeDocsLibrary
+ * @query: an #IdeDocsQuery
+ * @results: an #IdeDocsItem to place results
+ * @cancellable: (nullable): a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * Asynchronously queries the documentation providers for docs that
+ * match @query.
+ *
+ * @callback should call ide_docs_library_search_finish() to obtain
+ * the result.
+ *
+ * Since: 3.34
+ */
+void
+ide_docs_library_search_async (IdeDocsLibrary *self,
+ IdeDocsQuery *query,
+ IdeDocsItem *results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ Search *search;
+ static const struct {
+ const gchar *id;
+ const gchar *title;
+ } default_groups[] = {
+ { "api", N_("API") },
+ { "tutorials", N_("Tutorials and Guides") },
+ { "guidelines", N_("Guidelines") },
+ { "other", N_("Other") },
+ };
+
+ g_return_if_fail (IDE_IS_DOCS_LIBRARY (self));
+ g_return_if_fail (IDE_IS_DOCS_QUERY (query));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (results));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ search = g_slice_new0 (Search);
+ search->results = g_object_ref (results);
+ search->query = g_object_ref (query);
+ search->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ search->n_active = 0;
+
+ for (guint i = 0; i < G_N_ELEMENTS (default_groups); i++)
+ {
+ const gchar *id = default_groups[i].id;
+ const gchar *group = g_dgettext (GETTEXT_PACKAGE, default_groups[i].title);
+ g_autoptr(IdeDocsItem) child = NULL;
+
+ child = ide_docs_item_new ();
+ ide_docs_item_set_id (child, id);
+ ide_docs_item_set_title (child, group);
+ ide_docs_item_append (results, child);
+ }
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, ide_docs_library_search_async);
+ ide_task_set_task_data (task, search, search_free);
+
+ ide_extension_set_adapter_foreach (self->providers,
+ ide_docs_library_search_foreach_cb,
+ task);
+
+ if (search->n_active == 0)
+ ide_task_return_boolean (task, TRUE);
+}
+
+/**
+ * ide_docs_library_search_finish:
+ * @self: an #IdeDocsLibrary
+ * @result: a #GAsyncResult provided to callack
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes a request to search the library.
+ *
+ * Since: 3.34
+ */
+gboolean
+ide_docs_library_search_finish (IdeDocsLibrary *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_LIBRARY (self), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
diff --git a/src/libide/docs/ide-docs-library.h b/src/libide/docs/ide-docs-library.h
new file mode 100644
index 000000000..8942f920b
--- /dev/null
+++ b/src/libide/docs/ide-docs-library.h
@@ -0,0 +1,49 @@
+/* ide-docs-library.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+#include "ide-docs-item.h"
+#include "ide-docs-query.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_LIBRARY (ide_docs_library_get_type())
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_FINAL_TYPE (IdeDocsLibrary, ide_docs_library, IDE, DOCS_LIBRARY, IdeObject)
+
+IDE_AVAILABLE_IN_3_34
+IdeDocsLibrary *ide_docs_library_from_context (IdeContext *context);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_library_search_async (IdeDocsLibrary *self,
+ IdeDocsQuery *query,
+ IdeDocsItem *results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_3_34
+gboolean ide_docs_library_search_finish (IdeDocsLibrary *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-provider.c b/src/libide/docs/ide-docs-provider.c
new file mode 100644
index 000000000..3afd7a790
--- /dev/null
+++ b/src/libide/docs/ide-docs-provider.c
@@ -0,0 +1,162 @@
+/* ide-docs-provider.c
+ *
+ * Copyright 2019 Christian Hergert <unknown domain org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-provider"
+
+#include "config.h"
+
+#include <libide-threading.h>
+
+#include "ide-docs-provider.h"
+
+G_DEFINE_INTERFACE (IdeDocsProvider, ide_docs_provider, G_TYPE_OBJECT)
+
+static void
+ide_docs_provider_real_populate_async (IdeDocsProvider *provider,
+ IdeDocsItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ide_task_report_new_error (provider, callback, user_data,
+ ide_docs_provider_real_populate_async,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "Not supported");
+}
+
+static gboolean
+ide_docs_provider_real_populate_finish (IdeDocsProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_DOCS_PROVIDER (provider));
+ g_assert (IDE_IS_TASK (result));
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+ide_docs_provider_real_search_async (IdeDocsProvider *provider,
+ IdeDocsQuery *query,
+ IdeDocsItem *results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ide_task_report_new_error (provider, callback, user_data,
+ ide_docs_provider_real_search_async,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "Not supported");
+}
+
+static gboolean
+ide_docs_provider_real_search_finish (IdeDocsProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_DOCS_PROVIDER (provider));
+ g_assert (IDE_IS_TASK (result));
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+ide_docs_provider_default_init (IdeDocsProviderInterface *iface)
+{
+ iface->populate_async = ide_docs_provider_real_populate_async;
+ iface->populate_finish = ide_docs_provider_real_populate_finish;
+ iface->search_async = ide_docs_provider_real_search_async;
+ iface->search_finish = ide_docs_provider_real_search_finish;
+}
+
+void
+ide_docs_provider_populate_async (IdeDocsProvider *self,
+ IdeDocsItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_DOCS_PROVIDER (self));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (item));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ IDE_DOCS_PROVIDER_GET_IFACE (self)->populate_async (self, item, cancellable, callback, user_data);
+}
+
+gboolean
+ide_docs_provider_populate_finish (IdeDocsProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+ return IDE_DOCS_PROVIDER_GET_IFACE (self)->populate_finish (self, result, error);
+}
+
+/**
+ * ide_docs_provider_search_async:
+ * @self: an #IdeDocsProvider
+ * @query: an #IdeDocsQuery
+ * @results: an #IdeDocsItem
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a callback to execute
+ * @user_data: closure data for @callback
+ *
+ * Asynchronously queries the documentation provider. The results will be placed
+ * into @results. @results should contain a series of "sections" for the results
+ * and then "groups" within those.
+ *
+ * You may not use @results outside of the main-thread.
+ *
+ * Since: 3.34
+ */
+void
+ide_docs_provider_search_async (IdeDocsProvider *self,
+ IdeDocsQuery *query,
+ IdeDocsItem *results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_DOCS_PROVIDER (self));
+ g_return_if_fail (IDE_IS_DOCS_QUERY (query));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (results));
+
+ IDE_DOCS_PROVIDER_GET_IFACE (self)->search_async (self, query, results, cancellable, callback, user_data);
+}
+
+/**
+ * ide_docs_provider_search_finish:
+ *
+ * Since: 3.34
+ */
+gboolean
+ide_docs_provider_search_finish (IdeDocsProvider *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_PROVIDER (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+ return IDE_DOCS_PROVIDER_GET_IFACE (self)->search_finish (self, result, error);
+}
diff --git a/src/libide/docs/ide-docs-provider.h b/src/libide/docs/ide-docs-provider.h
new file mode 100644
index 000000000..149a096bb
--- /dev/null
+++ b/src/libide/docs/ide-docs-provider.h
@@ -0,0 +1,80 @@
+/* ide-docs-provider.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+#include "ide-docs-item.h"
+#include "ide-docs-query.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_PROVIDER (ide_docs_provider_get_type ())
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_INTERFACE (IdeDocsProvider, ide_docs_provider, IDE, DOCS_PROVIDER, GObject)
+
+struct _IdeDocsProviderInterface
+{
+ GTypeInterface parent;
+
+ void (*populate_async) (IdeDocsProvider *self,
+ IdeDocsItem *parent,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*populate_finish) (IdeDocsProvider *self,
+ GAsyncResult *result,
+ GError **error);
+ void (*search_async) (IdeDocsProvider *self,
+ IdeDocsQuery *query,
+ IdeDocsItem *results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*search_finish) (IdeDocsProvider *self,
+ GAsyncResult *result,
+ GError **error);
+};
+
+IDE_AVAILABLE_IN_3_34
+void ide_docs_provider_populate_async (IdeDocsProvider *self,
+ IdeDocsItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_3_34
+gboolean ide_docs_provider_populate_finish (IdeDocsProvider *self,
+ GAsyncResult *result,
+ GError **error);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_provider_search_async (IdeDocsProvider *self,
+ IdeDocsQuery *query,
+ IdeDocsItem *results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_3_34
+gboolean ide_docs_provider_search_finish (IdeDocsProvider *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-query.c b/src/libide/docs/ide-docs-query.c
new file mode 100644
index 000000000..5f611503b
--- /dev/null
+++ b/src/libide/docs/ide-docs-query.c
@@ -0,0 +1,255 @@
+/* ide-docs-query.c
+ *
+ * Copyright 2019 Christian Hergert <unknown domain org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-query"
+
+#include "config.h"
+
+#include "ide-docs-query.h"
+
+struct _IdeDocsQuery
+{
+ GObject parent_instance;
+ gchar *keyword;
+ gchar *fuzzy;
+ gchar *sdk;
+ gchar *language;
+};
+
+G_DEFINE_TYPE (IdeDocsQuery, ide_docs_query, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_KEYWORD,
+ PROP_SDK,
+ PROP_LANGUAGE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/**
+ * ide_docs_query_new:
+ *
+ * Create a new #IdeDocsQuery.
+ *
+ * Returns: (transfer full): a newly created #IdeDocsQuery
+ */
+IdeDocsQuery *
+ide_docs_query_new (void)
+{
+ return g_object_new (IDE_TYPE_DOCS_QUERY, NULL);
+}
+
+static void
+ide_docs_query_finalize (GObject *object)
+{
+ IdeDocsQuery *self = (IdeDocsQuery *)object;
+
+ g_clear_pointer (&self->keyword, g_free);
+ g_clear_pointer (&self->fuzzy, g_free);
+ g_clear_pointer (&self->sdk, g_free);
+ g_clear_pointer (&self->language, g_free);
+
+ G_OBJECT_CLASS (ide_docs_query_parent_class)->finalize (object);
+}
+
+static void
+ide_docs_query_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsQuery *self = IDE_DOCS_QUERY (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEYWORD:
+ g_value_set_string (value, ide_docs_query_get_keyword (self));
+ break;
+
+ case PROP_LANGUAGE:
+ g_value_set_string (value, ide_docs_query_get_language (self));
+ break;
+
+ case PROP_SDK:
+ g_value_set_string (value, ide_docs_query_get_sdk (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_query_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsQuery *self = IDE_DOCS_QUERY (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEYWORD:
+ ide_docs_query_set_keyword (self, g_value_get_string (value));
+ break;
+
+ case PROP_LANGUAGE:
+ ide_docs_query_set_language (self, g_value_get_string (value));
+ break;
+
+ case PROP_SDK:
+ ide_docs_query_set_sdk (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_query_class_init (IdeDocsQueryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_docs_query_finalize;
+ object_class->get_property = ide_docs_query_get_property;
+ object_class->set_property = ide_docs_query_set_property;
+
+ properties [PROP_KEYWORD] =
+ g_param_spec_string ("keyword",
+ "Keyword",
+ "Keyword",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LANGUAGE] =
+ g_param_spec_string ("language",
+ "Language",
+ "Language",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SDK] =
+ g_param_spec_string ("sdk",
+ "SDK",
+ "SDK",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_docs_query_init (IdeDocsQuery *self)
+{
+}
+
+const gchar *
+ide_docs_query_get_keyword (IdeDocsQuery *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_QUERY (self), NULL);
+
+ return self->keyword;
+}
+
+void
+ide_docs_query_set_keyword (IdeDocsQuery *self,
+ const gchar *keyword)
+{
+ g_return_if_fail (IDE_IS_DOCS_QUERY (self));
+
+ if (g_strcmp0 (keyword, self->keyword) != 0)
+ {
+ g_free (self->keyword);
+ self->keyword = g_strdup (keyword);
+
+ if (keyword != NULL)
+ {
+ GString *str = g_string_new (NULL);
+
+ for (; *keyword; keyword = g_utf8_next_char (keyword))
+ {
+ gunichar ch = g_utf8_get_char (keyword);
+
+ if (!g_unichar_isspace (ch))
+ g_string_append_unichar (str, ch);
+ }
+
+ g_free (self->fuzzy);
+ self->fuzzy = g_string_free (str, FALSE);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KEYWORD]);
+ }
+}
+
+const gchar *
+ide_docs_query_get_sdk (IdeDocsQuery *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_QUERY (self), NULL);
+
+ return self->sdk;
+}
+
+void
+ide_docs_query_set_sdk (IdeDocsQuery *self,
+ const gchar *sdk)
+{
+ g_return_if_fail (IDE_IS_DOCS_QUERY (self));
+
+ if (g_strcmp0 (sdk, self->sdk) != 0)
+ {
+ g_free (self->sdk);
+ self->sdk = g_strdup (sdk);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SDK]);
+ }
+}
+
+const gchar *
+ide_docs_query_get_language (IdeDocsQuery *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_QUERY (self), NULL);
+
+ return self->language;
+}
+
+void
+ide_docs_query_set_language (IdeDocsQuery *self,
+ const gchar *language)
+{
+ g_return_if_fail (IDE_IS_DOCS_QUERY (self));
+
+ if (g_strcmp0 (language, self->language) != 0)
+ {
+ g_free (self->language);
+ self->language = g_strdup (language);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LANGUAGE]);
+ }
+}
+
+const gchar *
+ide_docs_query_get_fuzzy (IdeDocsQuery *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_QUERY (self), NULL);
+
+ return self->fuzzy;
+}
diff --git a/src/libide/docs/ide-docs-query.h b/src/libide/docs/ide-docs-query.h
new file mode 100644
index 000000000..8672079ec
--- /dev/null
+++ b/src/libide/docs/ide-docs-query.h
@@ -0,0 +1,52 @@
+/* ide-docs-query.h
+ *
+ * Copyright 2019 Christian Hergert <unknown domain org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_QUERY (ide_docs_query_get_type())
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_FINAL_TYPE (IdeDocsQuery, ide_docs_query, IDE, DOCS_QUERY, GObject)
+
+IDE_AVAILABLE_IN_3_34
+IdeDocsQuery *ide_docs_query_new (void);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_query_get_keyword (IdeDocsQuery *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_query_set_keyword (IdeDocsQuery *self,
+ const gchar *keyword);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_query_get_fuzzy (IdeDocsQuery *self);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_query_get_sdk (IdeDocsQuery *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_query_set_sdk (IdeDocsQuery *self,
+ const gchar *sdk);
+IDE_AVAILABLE_IN_3_34
+const gchar *ide_docs_query_get_language (IdeDocsQuery *self);
+IDE_AVAILABLE_IN_3_34
+void ide_docs_query_set_language (IdeDocsQuery *self,
+ const gchar *language);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-search-group.c b/src/libide/docs/ide-docs-search-group.c
new file mode 100644
index 000000000..92571d75f
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-group.c
@@ -0,0 +1,342 @@
+/* ide-docs-search-group.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-search-group"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "ide-docs-search-group.h"
+#include "ide-docs-search-row.h"
+
+#define DEFAULT_MAX_CHILDREN 3
+
+struct _IdeDocsSearchGroup
+{
+ GtkBin parent_instance;
+
+ GtkEventBox *header;
+ GtkLabel *more;
+ GtkLabel *title;
+ GtkBox *rows;
+
+ GtkGesture *gesture;
+ IdeDocsItem *items;
+
+ guint max_items;
+ gint priority;
+
+ guint expanded : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_MAX_ITEMS,
+ PROP_PRIORITY,
+ PROP_TITLE,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (IdeDocsSearchGroup, ide_docs_search_group, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+on_header_button_released_cb (IdeDocsSearchGroup *self,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ GtkGestureMultiPress *gesture)
+{
+ g_assert (IDE_IS_DOCS_SEARCH_GROUP (self));
+ g_assert (GTK_IS_GESTURE_MULTI_PRESS (gesture));
+
+ ide_docs_search_group_toggle (self);
+}
+
+static void
+ide_docs_search_group_header_realized (IdeDocsSearchGroup *self,
+ GtkWidget *widget)
+{
+ GdkCursor *cursor;
+
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (IDE_IS_DOCS_SEARCH_GROUP (self));
+
+ cursor = gdk_cursor_new_from_name (gtk_widget_get_display (widget), "pointer");
+ gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
+ g_clear_object (&cursor);
+}
+
+static void
+ide_docs_search_group_finalize (GObject *object)
+{
+ IdeDocsSearchGroup *self = (IdeDocsSearchGroup *)object;
+
+ g_clear_object (&self->gesture);
+ g_clear_object (&self->items);
+
+ G_OBJECT_CLASS (ide_docs_search_group_parent_class)->finalize (object);
+}
+
+static void
+ide_docs_search_group_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsSearchGroup *self = IDE_DOCS_SEARCH_GROUP (object);
+
+ switch (prop_id)
+ {
+ case PROP_MAX_ITEMS:
+ g_value_set_uint (value, ide_docs_search_group_get_max_items (self));
+ break;
+
+ case PROP_PRIORITY:
+ g_value_set_int (value, ide_docs_search_group_get_priority (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, gtk_label_get_label (self->title));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_search_group_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsSearchGroup *self = IDE_DOCS_SEARCH_GROUP (object);
+
+ switch (prop_id)
+ {
+ case PROP_MAX_ITEMS:
+ ide_docs_search_group_set_max_items (self, g_value_get_uint (value));
+ break;
+
+ case PROP_PRIORITY:
+ ide_docs_search_group_set_priority (self, g_value_get_int (value));
+ break;
+
+ case PROP_TITLE:
+ gtk_label_set_label (self->title, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_search_group_class_init (IdeDocsSearchGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = ide_docs_search_group_finalize;
+ object_class->get_property = ide_docs_search_group_get_property;
+ object_class->set_property = ide_docs_search_group_set_property;
+
+ properties [PROP_MAX_ITEMS] =
+ g_param_spec_uint ("max-items",
+ "Max Items",
+ "Max Items",
+ 0, G_MAXUINT, DEFAULT_MAX_CHILDREN,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PRIORITY] =
+ g_param_spec_int ("priority",
+ "Priority",
+ "THe priority of the group",
+ G_MININT, G_MAXINT, 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title of the group",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "IdeDocsSearchGroup");
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/libide-docs/ui/ide-docs-search-group.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchGroup, header);
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchGroup, more);
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchGroup, rows);
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchGroup, title);
+}
+
+static void
+ide_docs_search_group_init (IdeDocsSearchGroup *self)
+{
+ self->max_items = DEFAULT_MAX_CHILDREN;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->gesture = gtk_gesture_multi_press_new (GTK_WIDGET (self->header));
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->gesture), 1);
+ gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (self->gesture), FALSE);
+
+ g_signal_connect_object (self->gesture,
+ "released",
+ G_CALLBACK (on_header_button_released_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->header,
+ "realize",
+ G_CALLBACK (ide_docs_search_group_header_realized),
+ self,
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+}
+
+GtkWidget *
+ide_docs_search_group_new (const gchar *title)
+{
+ return g_object_new (IDE_TYPE_DOCS_SEARCH_GROUP,
+ "title", title,
+ NULL);
+}
+
+const gchar *
+ide_docs_search_group_get_title (IdeDocsSearchGroup *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_SEARCH_GROUP (self), NULL);
+
+ return gtk_label_get_label (self->title);
+}
+
+void
+ide_docs_search_group_add_items (IdeDocsSearchGroup *self,
+ IdeDocsItem *parent)
+{
+ const GList *iter;
+ guint n_children;
+
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_GROUP (self));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (parent));
+ n_children = ide_docs_item_get_n_children (parent);
+ g_return_if_fail (n_children > 0);
+
+ g_set_object (&self->items, parent);
+
+ if (n_children > self->max_items)
+ {
+ guint more = n_children - self->max_items;
+ g_autofree gchar *str = NULL;
+
+ if (self->expanded)
+ str = g_strdup (_("Show Fewer"));
+ else
+ str = g_strdup_printf ("+%u", more);
+
+ gtk_label_set_label (self->more, str);
+
+ if (!self->expanded)
+ n_children = self->max_items;
+ }
+ else
+ {
+ gtk_label_set_label (self->more, "");
+ }
+
+ iter = ide_docs_item_get_children (parent);
+
+ for (guint i = 0; i < n_children; i++)
+ {
+ IdeDocsItem *child = iter->data;
+ GtkWidget *row;
+
+ g_assert (IDE_IS_DOCS_ITEM (child));
+
+ row = ide_docs_search_row_new (child);
+ gtk_container_add (GTK_CONTAINER (self->rows), row);
+ gtk_widget_show (row);
+
+ iter = iter->next;
+ }
+}
+
+gint
+ide_docs_search_group_get_priority (IdeDocsSearchGroup *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_SEARCH_GROUP (self), 0);
+
+ return self->priority;
+}
+
+void
+ide_docs_search_group_set_priority (IdeDocsSearchGroup *self,
+ gint priority)
+{
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_GROUP (self));
+
+ if (self->priority != priority)
+ {
+ self->priority = priority;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PRIORITY]);
+ }
+}
+
+void
+ide_docs_search_group_toggle (IdeDocsSearchGroup *self)
+{
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_GROUP (self));
+
+ self->expanded = !self->expanded;
+
+ gtk_container_foreach (GTK_CONTAINER (self->rows),
+ (GtkCallback) gtk_widget_destroy,
+ NULL);
+ ide_docs_search_group_add_items (self, self->items);
+}
+
+guint
+ide_docs_search_group_get_max_items (IdeDocsSearchGroup *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_SEARCH_GROUP (self), 0);
+
+ return self->max_items;
+}
+
+void
+ide_docs_search_group_set_max_items (IdeDocsSearchGroup *self,
+ guint max_items)
+{
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_GROUP (self));
+
+ if (max_items == 0)
+ max_items = DEFAULT_MAX_CHILDREN;
+
+ if (max_items != self->max_items)
+ {
+ self->max_items = max_items;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MAX_ITEMS]);
+ }
+}
diff --git a/src/libide/docs/ide-docs-search-group.h b/src/libide/docs/ide-docs-search-group.h
new file mode 100644
index 000000000..79a6400ae
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-group.h
@@ -0,0 +1,45 @@
+/* ide-docs-search-group.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "ide-docs-item.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_SEARCH_GROUP (ide_docs_search_group_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDocsSearchGroup, ide_docs_search_group, IDE, DOCS_SEARCH_GROUP, GtkBin)
+
+GtkWidget *ide_docs_search_group_new (const gchar *title);
+const gchar *ide_docs_search_group_get_title (IdeDocsSearchGroup *self);
+gint ide_docs_search_group_get_priority (IdeDocsSearchGroup *self);
+void ide_docs_search_group_set_priority (IdeDocsSearchGroup *self,
+ gint priority);
+guint ide_docs_search_group_get_max_items (IdeDocsSearchGroup *self);
+void ide_docs_search_group_set_max_items (IdeDocsSearchGroup *self,
+ guint max_items);
+void ide_docs_search_group_add_items (IdeDocsSearchGroup *self,
+ IdeDocsItem *parent);
+void ide_docs_search_group_toggle (IdeDocsSearchGroup *self);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-search-group.ui b/src/libide/docs/ide-docs-search-group.ui
new file mode 100644
index 000000000..e50047333
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-group.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeDocsSearchGroup" parent="GtkBin">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkEventBox" id="header">
+ <property name="visible">true</property>
+ <property name="visible-window">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="header"/>
+ </style>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="ellipsize">end</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="more">
+ <property name="label">+34</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="rows">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/docs/ide-docs-search-row.c b/src/libide/docs/ide-docs-search-row.c
new file mode 100644
index 000000000..e07407183
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-row.c
@@ -0,0 +1,220 @@
+/* ide-docs-search-row.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-search-row"
+
+#include "config.h"
+
+#include "ide-docs-search-row.h"
+
+struct _IdeDocsSearchRow
+{
+ DzlListBoxRow parent_instance;
+
+ IdeDocsItem *item;
+
+ /* Template Widgets */
+ GtkLabel *label;
+ GtkImage *image;
+};
+
+enum {
+ PROP_0,
+ PROP_ITEM,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (IdeDocsSearchRow, ide_docs_search_row, DZL_TYPE_LIST_BOX_ROW)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_docs_search_row_set_item (IdeDocsSearchRow *self,
+ IdeDocsItem *item)
+{
+ const gchar *icon_name;
+ const gchar *title;
+ gboolean use_markup;
+
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_ROW (self));
+ g_return_if_fail (!item || IDE_IS_DOCS_ITEM (item));
+
+ g_set_object (&self->item, item);
+
+ if (item == NULL)
+ return;
+
+ gtk_label_set_use_markup (self->label, FALSE);
+
+ if ((title = ide_docs_item_get_display_name (self->item)))
+ use_markup = TRUE;
+ else
+ title = ide_docs_item_get_title (self->item);
+
+ switch (ide_docs_item_get_kind (self->item))
+ {
+ case IDE_DOCS_ITEM_KIND_FUNCTION:
+ icon_name = "lang-function-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_METHOD:
+ icon_name = "lang-method-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_CLASS:
+ icon_name = "lang-class-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_ENUM:
+ icon_name = "lang-enum-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_CONSTANT:
+ icon_name = "lang-enum-value-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_MACRO:
+ icon_name = "lang-define-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_STRUCT:
+ icon_name = "lang-struct-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_UNION:
+ icon_name = "lang-union-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_PROPERTY:
+ icon_name = "lang-variable-symbolic";
+ break;
+
+ case IDE_DOCS_ITEM_KIND_BOOK:
+ case IDE_DOCS_ITEM_KIND_CHAPTER:
+ case IDE_DOCS_ITEM_KIND_COLLECTION:
+ case IDE_DOCS_ITEM_KIND_MEMBER:
+ case IDE_DOCS_ITEM_KIND_NONE:
+ case IDE_DOCS_ITEM_KIND_SIGNAL:
+ default:
+ icon_name = NULL;
+ break;
+ }
+
+ g_object_set (self->image, "icon-name", icon_name, NULL);
+
+ gtk_label_set_label (self->label, title);
+ gtk_label_set_use_markup (self->label, use_markup);
+}
+
+static void
+ide_docs_search_row_finalize (GObject *object)
+{
+ IdeDocsSearchRow *self = (IdeDocsSearchRow *)object;
+
+ g_clear_object (&self->item);
+
+ G_OBJECT_CLASS (ide_docs_search_row_parent_class)->finalize (object);
+}
+
+static void
+ide_docs_search_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsSearchRow *self = IDE_DOCS_SEARCH_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ITEM:
+ g_value_set_object (value, self->item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_search_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsSearchRow *self = IDE_DOCS_SEARCH_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ITEM:
+ ide_docs_search_row_set_item (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_search_row_class_init (IdeDocsSearchRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = ide_docs_search_row_finalize;
+ object_class->get_property = ide_docs_search_row_get_property;
+ object_class->set_property = ide_docs_search_row_set_property;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/libide-docs/ui/ide-docs-search-row.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchRow, image);
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchRow, label);
+
+ properties [PROP_ITEM] =
+ g_param_spec_object ("item",
+ "Item",
+ "The item to display",
+ IDE_TYPE_DOCS_ITEM,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_docs_search_row_init (IdeDocsSearchRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget *
+ide_docs_search_row_new (IdeDocsItem *item)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_ITEM (item), NULL);
+
+ return g_object_new (IDE_TYPE_DOCS_SEARCH_ROW,
+ "item", item,
+ NULL);
+}
+
+IdeDocsItem *
+ide_docs_search_row_get_item (IdeDocsSearchRow *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_SEARCH_ROW (self), NULL);
+
+ return self->item;
+}
diff --git a/src/libide/docs/ide-docs-search-row.h b/src/libide/docs/ide-docs-search-row.h
new file mode 100644
index 000000000..220c68f24
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-row.h
@@ -0,0 +1,36 @@
+/* ide-docs-search-row.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <dazzle.h>
+
+#include "ide-docs-item.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_SEARCH_ROW (ide_docs_search_row_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDocsSearchRow, ide_docs_search_row, IDE, DOCS_SEARCH_ROW, DzlListBoxRow)
+
+GtkWidget *ide_docs_search_row_new (IdeDocsItem *item);
+IdeDocsItem *ide_docs_search_row_get_item (IdeDocsSearchRow *self);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-search-row.ui b/src/libide/docs/ide-docs-search-row.ui
new file mode 100644
index 000000000..ed198e3ed
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-row.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeDocsSearchRow" parent="DzlListBoxRow">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="ellipsize">end</property>
+ <property name="hexpand">true</property>
+ <property name="visible">true</property>
+ <property name="width-chars">60</property>
+ <property name="max-width-chars">55</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="image">
+ <property name="pixel-size">16</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/docs/ide-docs-search-section.c b/src/libide/docs/ide-docs-search-section.c
new file mode 100644
index 000000000..2521aae37
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-section.c
@@ -0,0 +1,247 @@
+/* ide-docs-search-section.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-search-section"
+
+#include "config.h"
+
+#include <dazzle.h>
+
+#include "ide-docs-search-group.h"
+#include "ide-docs-search-section.h"
+
+struct _IdeDocsSearchSection
+{
+ GtkBin parent_instance;
+
+ DzlPriorityBox *groups;
+
+ gchar *title;
+
+ gint priority;
+};
+
+G_DEFINE_TYPE (IdeDocsSearchSection, ide_docs_search_section, GTK_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_PRIORITY,
+ PROP_TITLE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_docs_search_section_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ IdeDocsSearchSection *self = (IdeDocsSearchSection *)container;
+
+ g_assert (IDE_IS_DOCS_SEARCH_SECTION (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+ if (IDE_IS_DOCS_SEARCH_GROUP (child))
+ {
+ gint priority = ide_docs_search_group_get_priority (IDE_DOCS_SEARCH_GROUP (child));
+ gtk_container_add_with_properties (GTK_CONTAINER (self->groups), child,
+ "priority", priority,
+ NULL);
+ return;
+ }
+
+ GTK_CONTAINER_CLASS (ide_docs_search_section_parent_class)->add (container, child);
+}
+
+static void
+ide_docs_search_section_finalize (GObject *object)
+{
+ IdeDocsSearchSection *self = (IdeDocsSearchSection *)object;
+
+ g_clear_pointer (&self->title, g_free);
+
+ G_OBJECT_CLASS (ide_docs_search_section_parent_class)->finalize (object);
+}
+
+static void
+ide_docs_search_section_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsSearchSection *self = IDE_DOCS_SEARCH_SECTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRIORITY:
+ g_value_set_int (value, ide_docs_search_section_get_priority (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, self->title);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_search_section_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeDocsSearchSection *self = IDE_DOCS_SEARCH_SECTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_PRIORITY:
+ ide_docs_search_section_set_priority (self, g_value_get_int (value));
+ break;
+
+ case PROP_TITLE:
+ self->title = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_docs_search_section_class_init (IdeDocsSearchSectionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->finalize = ide_docs_search_section_finalize;
+ object_class->get_property = ide_docs_search_section_get_property;
+ object_class->set_property = ide_docs_search_section_set_property;
+
+ container_class->add = ide_docs_search_section_add;
+
+ properties [PROP_PRIORITY] =
+ g_param_spec_int ("priority",
+ "Priority",
+ "THe priority of the section",
+ G_MININT, G_MAXINT, 0,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "The title of the section",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "IdeDocsSearchSection");
+}
+
+static void
+ide_docs_search_section_init (IdeDocsSearchSection *self)
+{
+ self->groups = g_object_new (DZL_TYPE_PRIORITY_BOX,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "spacing", 14,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->groups));
+}
+
+GtkWidget *
+ide_docs_search_section_new (const gchar *title)
+{
+ return g_object_new (IDE_TYPE_DOCS_SEARCH_SECTION,
+ "title", title,
+ NULL);
+}
+
+const gchar *
+ide_docs_search_section_get_title (IdeDocsSearchSection *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_SEARCH_SECTION (self), NULL);
+
+ return self->title;
+}
+
+gint
+ide_docs_search_section_get_priority (IdeDocsSearchSection *self)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_SEARCH_SECTION (self), 0);
+
+ return self->priority;
+}
+
+void
+ide_docs_search_section_set_priority (IdeDocsSearchSection *self,
+ gint priority)
+{
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_SECTION (self));
+
+ if (priority != self->priority)
+ {
+ self->priority = priority;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PRIORITY]);
+ }
+}
+
+void
+ide_docs_search_section_add_groups (IdeDocsSearchSection *self,
+ IdeDocsItem *parent)
+{
+ gboolean show_more_items = FALSE;
+
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_SECTION (self));
+ g_return_if_fail (IDE_IS_DOCS_ITEM (parent));
+
+ /* If there is a single group within the section, we want to show more
+ * items than we otherwise would.
+ */
+ if (ide_docs_item_get_n_children (parent) == 1)
+ show_more_items = TRUE;
+
+ for (const GList *iter = ide_docs_item_get_children (parent);
+ iter != NULL;
+ iter = iter->next)
+ {
+ IdeDocsItem *child = iter->data;
+ IdeDocsSearchGroup *group;
+ const gchar *title;
+ gint priority;
+
+ g_assert (IDE_IS_DOCS_ITEM (child));
+
+ title = ide_docs_item_get_title (child);
+ priority = ide_docs_item_get_priority (child);
+ group = g_object_new (IDE_TYPE_DOCS_SEARCH_GROUP,
+ "title", title,
+ "priority", priority,
+ "visible", TRUE,
+ NULL);
+ if (show_more_items)
+ ide_docs_search_group_set_max_items (group, 25);
+ ide_docs_search_group_add_items (group, child);
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (group));
+ }
+}
diff --git a/src/libide/docs/ide-docs-search-section.h b/src/libide/docs/ide-docs-search-section.h
new file mode 100644
index 000000000..75b4ea710
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-section.h
@@ -0,0 +1,39 @@
+/* ide-docs-search-section.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_SEARCH_SECTION (ide_docs_search_section_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDocsSearchSection, ide_docs_search_section, IDE, DOCS_SEARCH_SECTION, GtkBin)
+
+GtkWidget *ide_docs_search_section_new (const gchar *title);
+const gchar *ide_docs_search_section_get_title (IdeDocsSearchSection *self);
+gint ide_docs_search_section_get_priority (IdeDocsSearchSection *self);
+void ide_docs_search_section_set_priority (IdeDocsSearchSection *self,
+ gint priority);
+void ide_docs_search_section_add_groups (IdeDocsSearchSection *self,
+ IdeDocsItem *parent);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-search-view.c b/src/libide/docs/ide-docs-search-view.c
new file mode 100644
index 000000000..e81eff159
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-view.c
@@ -0,0 +1,236 @@
+/* ide-docs-search-view.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-search-view"
+
+#include "config.h"
+
+#include <dazzle.h>
+#include <libide-gui.h>
+#include <libide-threading.h>
+
+#include "ide-docs-library.h"
+#include "ide-docs-search-group.h"
+#include "ide-docs-search-section.h"
+#include "ide-docs-search-view.h"
+
+struct _IdeDocsSearchView
+{
+ GtkBin parent_instance;
+
+ DzlPriorityBox *sections;
+ DzlPriorityBox *titles;
+};
+
+G_DEFINE_TYPE (IdeDocsSearchView, ide_docs_search_view, GTK_TYPE_BIN)
+
+static void
+ide_docs_search_view_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ IdeDocsSearchView *self = (IdeDocsSearchView *)container;
+
+ g_assert (IDE_IS_DOCS_SEARCH_VIEW (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+ if (IDE_IS_DOCS_SEARCH_SECTION (child))
+ {
+ const gchar *title = ide_docs_search_section_get_title (IDE_DOCS_SEARCH_SECTION (child));
+ gint priority = ide_docs_search_section_get_priority (IDE_DOCS_SEARCH_SECTION (child));
+ GtkSizeGroup *group;
+ GtkLabel *label;
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", title,
+ "xalign", 1.0f,
+ "yalign", 0.0f,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (self->titles), GTK_WIDGET (label));
+ gtk_container_add_with_properties (GTK_CONTAINER (self->sections), child,
+ "priority", priority,
+ NULL);
+ group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+ gtk_size_group_add_widget (group, GTK_WIDGET (label));
+ gtk_size_group_add_widget (group, GTK_WIDGET (child));
+ g_object_unref (group);
+ return;
+ }
+
+ GTK_CONTAINER_CLASS (ide_docs_search_view_parent_class)->add (container, child);
+}
+
+static void
+ide_docs_search_view_class_init (IdeDocsSearchViewClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ container_class->add = ide_docs_search_view_add;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/libide-docs/ui/ide-docs-search-view.ui");
+ gtk_widget_class_set_css_name (widget_class, "IdeDocsSearchView");
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchView, sections);
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsSearchView, titles);
+}
+
+static void
+ide_docs_search_view_init (IdeDocsSearchView *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ide_docs_search_view_clear (IdeDocsSearchView *self)
+{
+ g_assert (IDE_IS_DOCS_SEARCH_VIEW (self));
+
+ gtk_container_foreach (GTK_CONTAINER (self->sections),
+ (GtkCallback) gtk_widget_destroy,
+ NULL);
+ gtk_container_foreach (GTK_CONTAINER (self->titles),
+ (GtkCallback) gtk_widget_destroy,
+ NULL);
+}
+
+void
+ide_docs_search_view_add_sections (IdeDocsSearchView *self,
+ IdeDocsItem *item)
+{
+ g_assert (IDE_IS_DOCS_SEARCH_VIEW (self));
+ g_assert (!item || IDE_IS_DOCS_ITEM (item));
+
+ ide_docs_search_view_clear (self);
+
+ if (item == NULL)
+ return;
+
+ /* The root IdeDocsItem contains the children which are groups,
+ * each containing the children within that category.
+ *
+ * For each group, we create a new searchgroup to contain the
+ * children items. If there are too many items to display, we
+ * let the user know how many items are in the group and provide
+ * a button to click to show the additional items.
+ */
+
+ for (const GList *iter = ide_docs_item_get_children (item);
+ iter != NULL;
+ iter = iter->next)
+ {
+ IdeDocsItem *child = iter->data;
+ IdeDocsSearchSection *section;
+ const gchar *title;
+ gint priority;
+
+ g_assert (IDE_IS_DOCS_ITEM (child));
+
+ /* Ignore children that have no items */
+ if (ide_docs_item_get_n_children (child) == 0)
+ continue;
+
+ /* Create a new group with the title */
+ title = ide_docs_item_get_title (child);
+ priority = ide_docs_item_get_priority (child);
+ section = g_object_new (IDE_TYPE_DOCS_SEARCH_SECTION,
+ "title", title,
+ "priority", priority,
+ NULL);
+ ide_docs_search_section_add_groups (section, child);
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (section));
+ gtk_widget_show (GTK_WIDGET (section));
+ }
+}
+
+GtkWidget *
+ide_docs_search_view_new (void)
+{
+ return g_object_new (IDE_TYPE_DOCS_SEARCH_VIEW, NULL);
+}
+
+static void
+ide_docs_search_view_search_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeDocsLibrary *library = (IdeDocsLibrary *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(IdeDocsItem) res = NULL;
+ g_autoptr(GError) error = NULL;
+ IdeDocsSearchView *self;
+ IdeDocsItem *results;
+
+ g_assert (IDE_IS_DOCS_LIBRARY (library));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ if (!ide_docs_library_search_finish (library, result, &error))
+ ide_task_return_error (task, g_steal_pointer (&error));
+ else
+ ide_task_return_boolean (task, TRUE);
+
+ self = ide_task_get_source_object (task);
+ results = ide_task_get_task_data (task);
+
+ ide_docs_search_view_add_sections (self, results);
+}
+
+void
+ide_docs_search_view_search_async (IdeDocsSearchView *self,
+ IdeDocsQuery *query,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ g_autoptr(IdeDocsItem) results = NULL;
+ IdeDocsLibrary *library;
+ IdeContext *context;
+
+ g_return_if_fail (IDE_IS_DOCS_SEARCH_VIEW (self));
+ g_return_if_fail (IDE_IS_DOCS_QUERY (query));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ context = ide_widget_get_context (GTK_WIDGET (self));
+ library = ide_docs_library_from_context (context);
+ results = ide_docs_item_new ();
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, ide_docs_search_view_search_async);
+ ide_task_set_task_data (task, g_object_ref (results), g_object_unref);
+
+ ide_docs_library_search_async (library,
+ query,
+ results,
+ cancellable,
+ ide_docs_search_view_search_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+ide_docs_search_view_search_finish (IdeDocsSearchView *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_DOCS_SEARCH_VIEW (self), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
diff --git a/src/libide/docs/ide-docs-search-view.h b/src/libide/docs/ide-docs-search-view.h
new file mode 100644
index 000000000..4a7b86161
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-view.h
@@ -0,0 +1,47 @@
+/* ide-docs-search-view.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "ide-docs-item.h"
+#include "ide-docs-query.h"
+#include "ide-docs-search-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_SEARCH_VIEW (ide_docs_search_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeDocsSearchView, ide_docs_search_view, IDE, DOCS_SEARCH_VIEW, GtkBin)
+
+GtkWidget *ide_docs_search_view_new (void);
+void ide_docs_search_view_add_sections (IdeDocsSearchView *self,
+ IdeDocsItem *parent);
+void ide_docs_search_view_search_async (IdeDocsSearchView *self,
+ IdeDocsQuery *query,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_docs_search_view_search_finish (IdeDocsSearchView *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-search-view.ui b/src/libide/docs/ide-docs-search-view.ui
new file mode 100644
index 000000000..dd7c75c00
--- /dev/null
+++ b/src/libide/docs/ide-docs-search-view.ui
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeDocsSearchView" parent="GtkBin">
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin">48</property>
+ <property name="spacing">24</property>
+ <property name="orientation">horizontal</property>
+ <property name="visible">true</property>
+ <child type="center">
+ <object class="DzlPriorityBox" id="sections">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <property name="spacing">24</property>
+ </object>
+ </child>
+ <child>
+ <object class="DzlPriorityBox" id="titles">
+ <property name="halign">end</property>
+ <property name="hexpand">true</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">24</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="titles"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/docs/ide-docs-workspace.c b/src/libide/docs/ide-docs-workspace.c
new file mode 100644
index 000000000..b0be737c7
--- /dev/null
+++ b/src/libide/docs/ide-docs-workspace.c
@@ -0,0 +1,114 @@
+/* ide-docs-workspace.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-docs-workspace"
+
+#include "config.h"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+#include <libide-plugins.h>
+#include <libide-threading.h>
+
+#include "ide-docs-item.h"
+#include "ide-docs-library.h"
+#include "ide-docs-provider.h"
+#include "ide-docs-search-view.h"
+#include "ide-docs-workspace.h"
+
+struct _IdeDocsWorkspace
+{
+ IdeWorkspace parent_instance;
+
+ /* Template Widgets */
+ IdeDocsSearchView *search_view;
+ GtkEntry *entry;
+};
+
+G_DEFINE_TYPE (IdeDocsWorkspace, ide_docs_workspace, IDE_TYPE_WORKSPACE)
+
+/**
+ * ide_docs_workspace_new:
+ *
+ * Create a new #IdeDocsWorkspace.
+ *
+ * Returns: (transfer full): a newly created #IdeDocsWorkspace
+ */
+IdeWorkspace *
+ide_docs_workspace_new (IdeApplication *application)
+{
+ return g_object_new (IDE_TYPE_DOCS_WORKSPACE,
+ "application", application,
+ "default-width", 800,
+ "default-height", 600,
+ NULL);
+}
+
+static void
+on_search_entry_changed_cb (IdeDocsWorkspace *self,
+ GtkEntry *entry)
+{
+ g_autoptr(IdeDocsQuery) query = NULL;
+ const gchar *text;
+
+ g_assert (IDE_IS_DOCS_WORKSPACE (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ text = gtk_entry_get_text (entry);
+
+ if (ide_str_empty0 (text))
+ return;
+
+ query = ide_docs_query_new ();
+ ide_docs_query_set_keyword (query, text);
+
+ ide_docs_search_view_search_async (self->search_view,
+ query,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void
+ide_docs_workspace_class_init (IdeDocsWorkspaceClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ IdeWorkspaceClass *workspace_class = IDE_WORKSPACE_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/libide-docs/ui/ide-docs-workspace.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsWorkspace, entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeDocsWorkspace, search_view);
+
+ ide_workspace_class_set_kind (workspace_class, "docs");
+
+ g_type_ensure (IDE_TYPE_DOCS_SEARCH_VIEW);
+}
+
+static void
+ide_docs_workspace_init (IdeDocsWorkspace *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (self->entry,
+ "changed",
+ G_CALLBACK (on_search_entry_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/src/libide/docs/ide-docs-workspace.h b/src/libide/docs/ide-docs-workspace.h
new file mode 100644
index 000000000..e71123a60
--- /dev/null
+++ b/src/libide/docs/ide-docs-workspace.h
@@ -0,0 +1,35 @@
+/* ide-docs-workspace.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-gui.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DOCS_WORKSPACE (ide_docs_workspace_get_type())
+
+IDE_AVAILABLE_IN_3_34
+G_DECLARE_FINAL_TYPE (IdeDocsWorkspace, ide_docs_workspace, IDE, DOCS_WORKSPACE, IdeWorkspace)
+
+IDE_AVAILABLE_IN_3_34
+IdeWorkspace *ide_docs_workspace_new (IdeApplication *application);
+
+G_END_DECLS
diff --git a/src/libide/docs/ide-docs-workspace.ui b/src/libide/docs/ide-docs-workspace.ui
new file mode 100644
index 000000000..9cc55c2f0
--- /dev/null
+++ b/src/libide/docs/ide-docs-workspace.ui
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeDocsWorkspace" parent="IdeWorkspace">
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="headerbar">
+ <property name="show-close-button">true</property>
+ <property name="visible">true</property>
+ <child type="title">
+ <object class="GtkEntry" id="entry">
+ <property name="visible">true</property>
+ <property name="max-width-chars">40</property>
+ <property name="placeholder-text" translatable="yes">Search Documentation (Ctrl+K)</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child internal-child="surfaces">
+ <object class="GtkStack">
+ <child>
+ <object class="IdeSurface" id="docs_surface">
+ <property name="visible">true</property>
+ <child type="left">
+ <object class="GtkBox">
+ <property name="width-request">200</property>
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="IdeDocsSearchView" id="search_view">
+ <property name="expand">true</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/docs/libide-docs.c b/src/libide/docs/libide-docs.c
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/libide/docs/libide-docs.gresource.xml b/src/libide/docs/libide-docs.gresource.xml
new file mode 100644
index 000000000..cc856e1b7
--- /dev/null
+++ b/src/libide/docs/libide-docs.gresource.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/libide-docs">
+ <!-- file preprocess="xml-stripblanks">gtk/menus.ui</file -->
+ </gresource>
+ <gresource prefix="/org/gnome/libide-docs/ui">
+ <file preprocess="xml-stripblanks">ide-docs-search-row.ui</file>
+ <file preprocess="xml-stripblanks">ide-docs-search-group.ui</file>
+ <file preprocess="xml-stripblanks">ide-docs-search-view.ui</file>
+ <file preprocess="xml-stripblanks">ide-docs-workspace.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/libide/docs/libide-docs.h b/src/libide/docs/libide-docs.h
new file mode 100644
index 000000000..797d13b8e
--- /dev/null
+++ b/src/libide/docs/libide-docs.h
@@ -0,0 +1,34 @@
+/* libide-docs.h
+ *
+ * Copyright 2014-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+#include <libide-threading.h>
+
+#define IDE_DOCS_INSIDE
+
+#include "ide-docs-item.h"
+#include "ide-docs-library.h"
+#include "ide-docs-provider.h"
+#include "ide-docs-query.h"
+#include "ide-docs-workspace.h"
+
+#undef IDE_DOCS_INSIDE
diff --git a/src/libide/docs/meson.build b/src/libide/docs/meson.build
new file mode 100644
index 000000000..31fe3fd9a
--- /dev/null
+++ b/src/libide/docs/meson.build
@@ -0,0 +1,98 @@
+libide_docs_header_dir = join_paths(libide_header_dir, 'docs')
+libide_docs_header_subdir = join_paths(libide_header_subdir, 'docs')
+libide_include_directories += include_directories('.')
+
+#
+# Public API Headers
+#
+
+libide_docs_public_headers = [
+ 'libide-docs.h',
+ 'ide-docs-item.h',
+ 'ide-docs-library.h',
+ 'ide-docs-provider.h',
+ 'ide-docs-query.h',
+ 'ide-docs-workspace.h',
+]
+
+install_headers(libide_docs_public_headers, subdir: libide_docs_header_subdir)
+
+#
+# Sources
+#
+
+libide_docs_public_sources = [
+ 'libide-docs.c',
+ 'ide-docs-item.c',
+ 'ide-docs-library.c',
+ 'ide-docs-provider.c',
+ 'ide-docs-query.c',
+ 'ide-docs-search-group.c',
+ 'ide-docs-search-row.c',
+ 'ide-docs-search-section.c',
+ 'ide-docs-search-view.c',
+ 'ide-docs-workspace.c',
+]
+
+libide_docs_sources = libide_docs_public_sources
+
+#
+# Enum generation
+#
+
+libide_docs_enum_headers = [
+ 'ide-docs-item.h',
+]
+
+libide_docs_enums = gnome.mkenums_simple('ide-docs-enums',
+ body_prefix: '#include "config.h"',
+ header_prefix: '#include <libide-core.h>',
+ decorator: '_IDE_EXTERN',
+ sources: libide_docs_enum_headers,
+ install_header: true,
+ install_dir: libide_docs_header_dir,
+)
+
+#
+# Generated Resource Files
+#
+
+libide_docs_resources = gnome.compile_resources(
+ 'ide-docs-resources',
+ 'libide-docs.gresource.xml',
+ c_name: 'ide_docs',
+)
+libide_docs_sources += libide_docs_resources
+
+#
+# Dependencies
+#
+
+libide_docs_deps = [
+ libgio_dep,
+ libpeas_dep,
+ libide_core_dep,
+ libide_threading_dep,
+ libide_gui_dep,
+]
+
+#
+# Library Definitions
+#
+
+libide_docs = static_library('ide-docs-' + libide_api_version,
+ libide_docs_sources + libide_docs_enums,
+ dependencies: libide_docs_deps,
+ c_args: libide_args + release_args + ['-DIDE_DOCS_COMPILATION'],
+)
+
+libide_docs_dep = declare_dependency(
+ dependencies: libide_docs_deps,
+ link_whole: libide_docs,
+ include_directories: include_directories('.'),
+)
+
+gnome_builder_public_sources += files(libide_docs_public_sources)
+gnome_builder_public_headers += files(libide_docs_public_headers)
+gnome_builder_include_subdirs += libide_docs_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-docs.h', '-DIDE_DOCS_COMPILATION']
diff --git a/src/libide/meson.build b/src/libide/meson.build
index 3412c50c9..8e8337a29 100644
--- a/src/libide/meson.build
+++ b/src/libide/meson.build
@@ -14,6 +14,7 @@ subdir('foundry')
subdir('debugger')
subdir('themes')
subdir('gui')
+subdir('docs')
subdir('terminal')
subdir('sourceview')
subdir('lsp')
diff --git a/src/libide/themes/libide-themes.gresource.xml b/src/libide/themes/libide-themes.gresource.xml
index 2d22cd6b7..9eed61bba 100644
--- a/src/libide/themes/libide-themes.gresource.xml
+++ b/src/libide/themes/libide-themes.gresource.xml
@@ -19,6 +19,7 @@
<file compressed="true">themes/shared/shared-buildui.css</file>
<file compressed="true">themes/shared/shared-completion.css</file>
<file compressed="true">themes/shared/shared-debugger.css</file>
+ <file compressed="true">themes/shared/shared-docs.css</file>
<file compressed="true">themes/shared/shared-editor.css</file>
<file compressed="true">themes/shared/shared-greeter.css</file>
<file compressed="true">themes/shared/shared-hoverer.css</file>
diff --git a/src/libide/themes/themes/shared.css b/src/libide/themes/themes/shared.css
index fcf9ea9c9..4abb305ef 100644
--- a/src/libide/themes/themes/shared.css
+++ b/src/libide/themes/themes/shared.css
@@ -1,6 +1,7 @@
@import url("resource:///org/gnome/builder/themes/shared/shared-buildui.css");
@import url("resource:///org/gnome/builder/themes/shared/shared-completion.css");
@import url("resource:///org/gnome/builder/themes/shared/shared-debugger.css");
+@import url("resource:///org/gnome/builder/themes/shared/shared-docs.css");
@import url("resource:///org/gnome/builder/themes/shared/shared-layout.css");
@import url("resource:///org/gnome/builder/themes/shared/shared-editor.css");
@import url("resource:///org/gnome/builder/themes/shared/shared-greeter.css");
diff --git a/src/libide/themes/themes/shared/shared-docs.css b/src/libide/themes/themes/shared/shared-docs.css
new file mode 100644
index 000000000..80ced0802
--- /dev/null
+++ b/src/libide/themes/themes/shared/shared-docs.css
@@ -0,0 +1,25 @@
+IdeDocsSearchGroup box.header {
+ padding: 7px;
+ }
+IdeDocsSearchGroup box.header label {
+ font-size: 0.8333em;
+ font-weight: bold;
+ color: @theme_selected_bg_color;
+ }
+IdeDocsSearchView box.titles label {
+ font-size: 1.3em;
+ font-weight: 500;
+ color: alpha(currentColor, 0.5);
+ }
+IdeDocsSearchGroup row:first-child {
+ border-top: 1px solid alpha(@borders, 0.5);
+ }
+IdeDocsSearchGroup row {
+ border-bottom: 1px solid alpha(@borders, 0.5);
+ }
+IdeDocsSearchGroup row > box {
+ padding: 7px;
+ }
+IdeDocsSearchGroup row > box image:last-child {
+ min-width: 16px;
+ }
diff --git a/src/meson.build b/src/meson.build
index bc7f91841..ae03f4e5d 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -29,6 +29,7 @@ gnome_builder_deps = [
libide_code_dep,
libide_core_dep,
+ libide_docs_dep,
libide_debugger_dep,
libide_editor_dep,
libide_foundry_dep,
diff --git a/src/plugins/devhelp/devhelp-plugin.c b/src/plugins/devhelp/devhelp-plugin.c
index 72c5b2fed..bc93f83b9 100644
--- a/src/plugins/devhelp/devhelp-plugin.c
+++ b/src/plugins/devhelp/devhelp-plugin.c
@@ -23,6 +23,7 @@
#include <libide-editor.h>
#include <libpeas/peas.h>
+#include "gbp-devhelp-docs-provider.h"
#include "gbp-devhelp-editor-addin.h"
#include "gbp-devhelp-hover-provider.h"
#include "gbp-devhelp-frame-addin.h"
@@ -30,6 +31,9 @@
_IDE_EXTERN void
_gbp_devhelp_register_types (PeasObjectModule *module)
{
+ peas_object_module_register_extension_type (module,
+ IDE_TYPE_DOCS_PROVIDER,
+ GBP_TYPE_DEVHELP_DOCS_PROVIDER);
peas_object_module_register_extension_type (module,
IDE_TYPE_EDITOR_ADDIN,
GBP_TYPE_DEVHELP_EDITOR_ADDIN);
diff --git a/src/plugins/devhelp/devhelp2-parser.c b/src/plugins/devhelp/devhelp2-parser.c
new file mode 100644
index 000000000..f5a6ece59
--- /dev/null
+++ b/src/plugins/devhelp/devhelp2-parser.c
@@ -0,0 +1,318 @@
+/* devhelp2-parser.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <unistd.h>
+
+#include "devhelp2-parser.h"
+
+static const gchar *
+devhelp2_parser_intern (Devhelp2Parser *state,
+ const gchar *str)
+{
+ if (str == NULL)
+ return NULL;
+
+ return g_string_chunk_insert_const (state->strings, str);
+}
+
+static Chapter *
+chapter_new (const gchar *name,
+ const gchar *link)
+{
+ Chapter *chapter;
+
+ chapter = g_slice_new0 (Chapter);
+ chapter->list.data = chapter;
+ chapter->name = name;
+ chapter->link = link;
+
+ return chapter;
+}
+
+static void
+chapter_free (Chapter *chapter)
+{
+ if (chapter->parent)
+ g_queue_unlink (&chapter->parent->children, &chapter->list);
+
+ chapter->parent = NULL;
+
+ while (chapter->children.head)
+ chapter_free (chapter->children.head->data);
+
+ g_assert (chapter->parent == NULL);
+ g_assert (chapter->list.prev == NULL);
+ g_assert (chapter->list.next == NULL);
+ g_assert (chapter->children.length == 0);
+
+ g_slice_free (Chapter, chapter);
+}
+
+static void
+chapter_append (Chapter *chapter,
+ Chapter *child)
+{
+ g_assert (chapter != NULL);
+ g_assert (child != NULL);
+ g_assert (child->parent == NULL);
+
+ child->parent = chapter;
+ g_queue_push_tail_link (&chapter->children, &child->list);
+}
+
+static void
+devhelp2_parser_start_element (GMarkupParseContext *context G_GNUC_UNUSED,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ Devhelp2Parser *state = user_data;
+
+ g_assert (element_name != NULL);
+ g_assert (state != NULL);
+ g_assert (state->context != NULL);
+
+ if (g_str_equal (element_name, "book"))
+ {
+ const gchar *title = NULL;
+ const gchar *link = NULL;
+ const gchar *author = NULL;
+ const gchar *name = NULL;
+ const gchar *version = NULL;
+ const gchar *language = NULL;
+ const gchar *online = NULL;
+ const gchar *xmlns = NULL;
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "title", &title,
+ G_MARKUP_COLLECT_STRING, "link", &link,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "author",
&author,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "version",
&version,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "language",
&language,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "online",
&online,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "xmlns", &xmlns,
+ G_MARKUP_COLLECT_INVALID))
+ return;
+
+ state->book.title = devhelp2_parser_intern (state, title);
+ state->book.link = devhelp2_parser_intern (state, link);
+ state->book.author = devhelp2_parser_intern (state, author);
+ state->book.name = devhelp2_parser_intern (state, name);
+ state->book.version = devhelp2_parser_intern (state, version);
+ state->book.language = devhelp2_parser_intern (state, language);
+ state->book.online = devhelp2_parser_intern (state, online);
+ }
+ else if (g_str_equal (element_name, "sub"))
+ {
+ const gchar *name = NULL;
+ const gchar *link = NULL;
+ Chapter *chapter;
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_STRING, "link", &link,
+ G_MARKUP_COLLECT_INVALID))
+ return;
+
+ chapter = chapter_new (devhelp2_parser_intern (state, name),
+ devhelp2_parser_intern (state, link));
+
+ if (state->chapter != NULL)
+ chapter_append (state->chapter, chapter);
+
+ state->chapter = chapter;
+ }
+ else if (g_str_equal (element_name, "keyword"))
+ {
+ Keyword keyword;
+ const gchar *name = NULL;
+ const gchar *link = NULL;
+ const gchar *type = NULL;
+ const gchar *since = NULL;
+ const gchar *deprecated = NULL;
+ const gchar *stability = NULL;
+
+ if (!g_markup_collect_attributes (element_name,
+ attribute_names,
+ attribute_values,
+ error,
+ G_MARKUP_COLLECT_STRING, "type", &type,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_STRING, "link", &link,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "deprecated",
&deprecated,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "since", &since,
+ G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING, "stability",
&stability,
+ G_MARKUP_COLLECT_INVALID))
+ return;
+
+ keyword.type = devhelp2_parser_intern (state, type);
+ keyword.kind = GPOINTER_TO_UINT (g_hash_table_lookup (state->kinds, keyword.type));
+ keyword.name = devhelp2_parser_intern (state, name);
+ keyword.link = devhelp2_parser_intern (state, link);
+ keyword.since = devhelp2_parser_intern (state, since);
+ keyword.deprecated = devhelp2_parser_intern (state, deprecated);
+ keyword.stability = devhelp2_parser_intern (state, stability);
+
+ g_array_append_val (state->keywords, keyword);
+ }
+}
+
+static void
+devhelp2_parser_end_element (GMarkupParseContext *context G_GNUC_UNUSED,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error G_GNUC_UNUSED)
+{
+ Devhelp2Parser *state = user_data;
+
+ if (g_str_equal (element_name, "sub"))
+ {
+ if (state->chapter->parent)
+ state->chapter = state->chapter->parent;
+ }
+}
+
+static GMarkupParser devhelp2_parser = {
+ .start_element = devhelp2_parser_start_element,
+ .end_element = devhelp2_parser_end_element,
+};
+
+Devhelp2Parser *
+devhelp2_parser_new (void)
+{
+ Devhelp2Parser *state;
+
+ state = g_slice_new0 (Devhelp2Parser);
+ state->kinds = g_hash_table_new (NULL, NULL);
+ state->strings = g_string_chunk_new (4096*4L);
+ state->keywords = g_array_new (FALSE, FALSE, sizeof (Keyword));
+ state->context = g_markup_parse_context_new (&devhelp2_parser,
+ G_MARKUP_IGNORE_QUALIFIED,
+ state, NULL);
+
+#define ADD_KIND(k, v) \
+ g_hash_table_insert (state->kinds, \
+ (gchar *)g_string_chunk_insert_const (state->strings, k), \
+ GUINT_TO_POINTER(v))
+
+ ADD_KIND ("function", IDE_DOCS_ITEM_KIND_FUNCTION);
+ ADD_KIND ("struct", IDE_DOCS_ITEM_KIND_STRUCT);
+ ADD_KIND ("enum", IDE_DOCS_ITEM_KIND_ENUM);
+ ADD_KIND ("property", IDE_DOCS_ITEM_KIND_PROPERTY);
+ ADD_KIND ("signal", IDE_DOCS_ITEM_KIND_SIGNAL);
+ ADD_KIND ("macro", IDE_DOCS_ITEM_KIND_MACRO);
+ ADD_KIND ("member", IDE_DOCS_ITEM_KIND_MEMBER);
+ ADD_KIND ("method", IDE_DOCS_ITEM_KIND_METHOD);
+ ADD_KIND ("constant", IDE_DOCS_ITEM_KIND_CONSTANT);
+
+#undef ADD_KIND
+
+ return state;
+}
+
+void
+devhelp2_parser_free (Devhelp2Parser *state)
+{
+ g_clear_pointer (&state->kinds, g_hash_table_unref);
+ g_clear_pointer (&state->context, g_markup_parse_context_free);
+ g_clear_pointer (&state->strings, g_string_chunk_free);
+ g_clear_pointer (&state->keywords, g_array_unref);
+ g_clear_pointer (&state->chapter, chapter_free);
+ g_clear_pointer (&state->directory, g_free);
+ g_slice_free (Devhelp2Parser, state);
+}
+
+gboolean
+devhelp2_parser_parse_file (Devhelp2Parser *state,
+ const gchar *filename,
+ GError **error)
+{
+ g_autofree gchar *contents = NULL;
+ gboolean ret = FALSE;
+ gint fd;
+
+ g_assert (state != NULL);
+ g_assert (filename != NULL);
+ g_assert (state->directory == NULL);
+
+ state->directory = g_path_get_dirname (filename);
+
+ fd = g_open (filename, O_RDONLY, 0);
+
+ if (fd == -1)
+ {
+ int errsv = errno;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errsv),
+ "%s",
+ g_strerror (errno));
+ goto failure;
+ }
+
+ for (;;)
+ {
+ gchar buf[4096*4L];
+ gssize n_read;
+
+ n_read = read (fd, buf, sizeof buf);
+
+ if (n_read > 0)
+ {
+ if (!g_markup_parse_context_parse (state->context, buf, n_read, error))
+ goto failure;
+ }
+ else if (n_read < 0)
+ {
+ int errsv = errno;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errsv),
+ "%s",
+ g_strerror (errno));
+ goto failure;
+ }
+ else
+ {
+ g_assert (n_read == 0);
+ break;
+ }
+ }
+
+ ret = g_markup_parse_context_end_parse (state->context, error);
+
+failure:
+ close (fd);
+
+ return ret;
+}
diff --git a/src/plugins/devhelp/devhelp2-parser.h b/src/plugins/devhelp/devhelp2-parser.h
new file mode 100644
index 000000000..fdd411d2e
--- /dev/null
+++ b/src/plugins/devhelp/devhelp2-parser.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <glib.h>
+
+#include <libide-docs.h>
+
+G_BEGIN_DECLS
+
+typedef struct _Devhelp2Parser Devhelp2Parser;
+typedef struct _Chapter Chapter;
+typedef struct _Keyword Keyword;
+
+struct _Chapter
+{
+ struct _Chapter *parent;
+ GQueue children;
+ GList list;
+ const gchar *name;
+ const gchar *link;
+};
+
+struct _Keyword
+{
+ GList list;
+ const gchar *type;
+ const gchar *name;
+ const gchar *link;
+ const gchar *since;
+ const gchar *deprecated;
+ const gchar *stability;
+ IdeDocsItemKind kind;
+};
+
+struct _Devhelp2Parser
+{
+ GMarkupParseContext *context;
+ GStringChunk *strings;
+ GHashTable *kinds;
+ Chapter *chapter;
+ GArray *keywords;
+ gchar *directory;
+ struct {
+ const gchar *title;
+ const gchar *link;
+ const gchar *author;
+ const gchar *name;
+ const gchar *version;
+ const gchar *language;
+ const gchar *online;
+ } book;
+};
+
+
+Devhelp2Parser *devhelp2_parser_new (void);
+void devhelp2_parser_free (Devhelp2Parser *state);
+gboolean devhelp2_parser_parse_file (Devhelp2Parser *state,
+ const gchar *filename,
+ GError **error);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (Devhelp2Parser, devhelp2_parser_free)
+
+G_END_DECLS
diff --git a/src/plugins/devhelp/gbp-devhelp-docs-provider.c b/src/plugins/devhelp/gbp-devhelp-docs-provider.c
new file mode 100644
index 000000000..aae78a7bb
--- /dev/null
+++ b/src/plugins/devhelp/gbp-devhelp-docs-provider.c
@@ -0,0 +1,269 @@
+/* gbp-devhelp-docs-provider.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-devhelp-docs-provider"
+
+#include "config.h"
+
+#include <libide-sourceview.h>
+#include <libide-threading.h>
+
+#include "devhelp2-parser.h"
+#include "gbp-devhelp-docs-provider.h"
+
+struct _GbpDevhelpDocsProvider
+{
+ GObject parent_instance;
+
+ GPtrArray *parsers;
+};
+
+static void
+gbp_devhelp_docs_provider_populate_async (IdeDocsProvider *provider,
+ IdeDocsItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GbpDevhelpDocsProvider *self = (GbpDevhelpDocsProvider *)provider;
+ g_autoptr(IdeTask) task = NULL;
+
+ g_assert (GBP_IS_DEVHELP_DOCS_PROVIDER (self));
+ g_assert (IDE_IS_DOCS_ITEM (item));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, gbp_devhelp_docs_provider_populate_async);
+ ide_task_set_task_data (task, g_object_ref (item), g_object_unref);
+
+ if (ide_docs_item_is_root (item))
+ {
+ g_autoptr(IdeDocsItem) child = NULL;
+
+ child = ide_docs_item_new ();
+ ide_docs_item_set_title (child, "Books");
+ ide_docs_item_set_kind (child, IDE_DOCS_ITEM_KIND_COLLECTION);
+
+ ide_docs_item_append (item, child);
+ }
+
+ ide_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gbp_devhelp_docs_provider_populate_finish (IdeDocsProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (GBP_IS_DEVHELP_DOCS_PROVIDER (provider));
+ g_assert (IDE_IS_TASK (result));
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+gbp_devhelp_docs_provider_search_async (IdeDocsProvider *provider,
+ IdeDocsQuery *query,
+ IdeDocsItem *results,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GbpDevhelpDocsProvider *self = (GbpDevhelpDocsProvider *)provider;
+ g_autoptr(GListStore) store = g_list_store_new (IDE_TYPE_DOCS_ITEM);
+ g_autoptr(IdeTask) task = NULL;
+ IdeDocsItem *api;
+ const gchar *text;
+
+ g_assert (GBP_IS_DEVHELP_DOCS_PROVIDER (self));
+ g_assert (IDE_IS_DOCS_QUERY (query));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, gbp_devhelp_docs_provider_search_async);
+ ide_task_set_task_data (task, g_object_ref (query), g_object_unref);
+
+ api = ide_docs_item_find_child_by_id (results, "api");
+ g_assert (IDE_IS_DOCS_ITEM (api));
+
+ text = ide_docs_query_get_fuzzy (query);
+
+ if (self->parsers != NULL && !ide_str_empty0 (text))
+ {
+ g_autofree gchar *needle = g_utf8_casefold (text, -1);
+
+ for (guint i = 0; i < self->parsers->len; i++)
+ {
+ Devhelp2Parser *parser = g_ptr_array_index (self->parsers, i);
+ g_autoptr(IdeDocsItem) group = NULL;
+ guint priority = G_MAXINT;
+
+ if (parser->book.title == NULL)
+ continue;
+
+ group = ide_docs_item_new ();
+ ide_docs_item_set_title (group, parser->book.title);
+
+ for (guint j = 0; j < parser->keywords->len; j++)
+ {
+ const Keyword *kw = &g_array_index (parser->keywords, Keyword, j);
+ guint prio = G_MAXINT;
+
+ if (ide_completion_fuzzy_match (kw->name, needle, &prio))
+ {
+ g_autoptr(IdeDocsItem) child = ide_docs_item_new ();
+ g_autofree gchar *highlight = ide_completion_fuzzy_highlight (kw->name, text);
+
+ ide_docs_item_set_title (child, kw->name);
+ ide_docs_item_set_display_name (child, highlight);
+ ide_docs_item_set_kind (child, kw->kind);
+ ide_docs_item_set_priority (group, prio);
+ ide_docs_item_append (group, child);
+
+ priority = MIN (prio, priority);
+ }
+ }
+
+ ide_docs_item_sort_by_priority (group);
+ ide_docs_item_set_priority (group, priority);
+
+ if (ide_docs_item_has_child (group))
+ ide_docs_item_append (api, group);
+ }
+ }
+
+ ide_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gbp_devhelp_docs_provider_search_finish (IdeDocsProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (GBP_IS_DEVHELP_DOCS_PROVIDER (provider));
+ g_assert (IDE_IS_TASK (result));
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+docs_provider_iface_init (IdeDocsProviderInterface *iface)
+{
+ iface->populate_async = gbp_devhelp_docs_provider_populate_async;
+ iface->populate_finish = gbp_devhelp_docs_provider_populate_finish;
+ iface->search_async = gbp_devhelp_docs_provider_search_async;
+ iface->search_finish = gbp_devhelp_docs_provider_search_finish;
+}
+
+static void
+gbp_devhelp_docs_provider_init_async (GAsyncInitable *initable,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GbpDevhelpDocsProvider *self = (GbpDevhelpDocsProvider *)initable;
+ g_autoptr(IdeTask) task = NULL;
+ static const gchar *dirs[] = {
+ "/app/share/gtk-doc/html",
+ "/usr/share/gtk-doc/html",
+ };
+
+ g_assert (GBP_IS_DEVHELP_DOCS_PROVIDER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, gbp_devhelp_docs_provider_init_async);
+
+ self->parsers = g_ptr_array_new_with_free_func ((GDestroyNotify)devhelp2_parser_free);
+
+ for (guint i = 0; i < G_N_ELEMENTS (dirs); i++)
+ {
+ g_autoptr(GDir) dir = g_dir_open (dirs[i], 0, NULL);
+ const gchar *name;
+
+ if (dir == NULL)
+ continue;
+
+ while ((name = g_dir_read_name (dir)))
+ {
+ g_autofree gchar *index = g_strdup_printf ("%s.devhelp2", name);
+ g_autofree gchar *path = g_build_filename (dirs[i], name, index, NULL);
+ Devhelp2Parser *parser;
+
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ continue;
+
+ parser = devhelp2_parser_new ();
+
+ if (devhelp2_parser_parse_file (parser, path, NULL))
+ g_ptr_array_add (self->parsers, parser);
+ else
+ devhelp2_parser_free (parser);
+ }
+ }
+
+ ide_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gbp_devhelp_docs_provider_init_finish (GAsyncInitable *initable,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (GBP_IS_DEVHELP_DOCS_PROVIDER (initable));
+ g_assert (IDE_IS_TASK (result));
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *iface)
+{
+ iface->init_async = gbp_devhelp_docs_provider_init_async;
+ iface->init_finish = gbp_devhelp_docs_provider_init_finish;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpDevhelpDocsProvider, gbp_devhelp_docs_provider, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_DOCS_PROVIDER, docs_provider_iface_init))
+
+static void
+gbp_devhelp_docs_provider_finalize (GObject *object)
+{
+ GbpDevhelpDocsProvider *self = (GbpDevhelpDocsProvider *)object;
+
+ g_clear_pointer (&self->parsers, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (gbp_devhelp_docs_provider_parent_class)->finalize (object);
+}
+
+static void
+gbp_devhelp_docs_provider_class_init (GbpDevhelpDocsProviderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gbp_devhelp_docs_provider_finalize;
+}
+
+static void
+gbp_devhelp_docs_provider_init (GbpDevhelpDocsProvider *self)
+{
+}
diff --git a/src/plugins/devhelp/gbp-devhelp-docs-provider.h b/src/plugins/devhelp/gbp-devhelp-docs-provider.h
new file mode 100644
index 000000000..fc2a9d8ad
--- /dev/null
+++ b/src/plugins/devhelp/gbp-devhelp-docs-provider.h
@@ -0,0 +1,31 @@
+/* gbp-devhelp-docs-provider.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-docs.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_DEVHELP_DOCS_PROVIDER (gbp_devhelp_docs_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpDevhelpDocsProvider, gbp_devhelp_docs_provider, GBP, DEVHELP_DOCS_PROVIDER, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/devhelp/meson.build b/src/plugins/devhelp/meson.build
index e4cbc0223..baa851f1e 100644
--- a/src/plugins/devhelp/meson.build
+++ b/src/plugins/devhelp/meson.build
@@ -6,6 +6,8 @@ plugins_deps += [
plugins_sources += files([
'devhelp-plugin.c',
+ 'devhelp2-parser.c',
+ 'gbp-devhelp-docs-provider.c',
'gbp-devhelp-editor-addin.c',
'gbp-devhelp-frame-addin.c',
'gbp-devhelp-hover-provider.c',
diff --git a/src/plugins/docsui/docsui-plugin.c b/src/plugins/docsui/docsui-plugin.c
new file mode 100644
index 000000000..c43ff1d69
--- /dev/null
+++ b/src/plugins/docsui/docsui-plugin.c
@@ -0,0 +1,34 @@
+/* docsui-plugin.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#include <libide-docs.h>
+#include <libpeas/peas.h>
+
+#include "gbp-docsui-application-addin.h"
+
+_IDE_EXTERN void
+_gbp_docsui_register_types (PeasObjectModule *module)
+{
+ peas_object_module_register_extension_type (module,
+ IDE_TYPE_APPLICATION_ADDIN,
+ GBP_TYPE_DOCSUI_APPLICATION_ADDIN);
+}
diff --git a/src/plugins/docsui/docsui.gresource.xml b/src/plugins/docsui/docsui.gresource.xml
new file mode 100644
index 000000000..f2b600497
--- /dev/null
+++ b/src/plugins/docsui/docsui.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/plugins/docsui">
+ <file>docsui.plugin</file>
+ </gresource>
+</gresources>
diff --git a/src/plugins/docsui/docsui.plugin b/src/plugins/docsui/docsui.plugin
new file mode 100644
index 000000000..c69d2b468
--- /dev/null
+++ b/src/plugins/docsui/docsui.plugin
@@ -0,0 +1,11 @@
+[Plugin]
+Authors=Christian Hergert <christian hergert me>
+Builtin=true
+Copyright=Copyright © 2015-2018 Christian Hergert
+Depends=editor;webkit;
+Description=Documentation UI integration
+Embedded=_gbp_docsui_register_types
+Hidden=true
+Module=docsui
+Name=Docs UI
+X-At-Startup=true
diff --git a/src/plugins/docsui/gbp-docsui-application-addin.c
b/src/plugins/docsui/gbp-docsui-application-addin.c
new file mode 100644
index 000000000..a170abdaa
--- /dev/null
+++ b/src/plugins/docsui/gbp-docsui-application-addin.c
@@ -0,0 +1,109 @@
+/* gbp-docsui-application-addin.c
+ *
+ * Copyright 2019 Christian Hergert <unknown domain org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-docsui-application-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-gui.h>
+#include <libide-docs.h>
+
+#include "gbp-docsui-application-addin.h"
+
+struct _GbpDocsuiApplicationAddin
+{
+ GObject parent_instance;
+};
+
+static void
+gbp_docsui_application_addin_handle_command_line (IdeApplicationAddin *addin,
+ IdeApplication *application,
+ GApplicationCommandLine *cmdline)
+{
+ g_autoptr(IdeWorkbench) workbench = NULL;
+ IdeApplication *app = (IdeApplication *)application;
+ g_autoptr(GFile) workdir = NULL;
+ GVariantDict *options;
+
+ g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+ g_assert (IDE_IS_APPLICATION (application));
+ g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+ if ((options = g_application_command_line_get_options_dict (cmdline)) &&
+ g_variant_dict_contains (options, "docs"))
+ {
+ IdeWorkspace *workspace;
+ IdeContext *context;
+
+ workdir = g_application_command_line_create_file_for_arg (cmdline, ".");
+ ide_application_set_command_line_handled (application, cmdline, TRUE);
+
+ workbench = ide_workbench_new ();
+ ide_application_add_workbench (app, workbench);
+
+ context = ide_workbench_get_context (workbench);
+ ide_context_set_workdir (context, workdir);
+
+ workspace = ide_docs_workspace_new (application);
+ ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+ ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+
+ return;
+ }
+}
+
+static void
+gbp_docsui_application_addin_add_option_entries (IdeApplicationAddin *addin,
+ IdeApplication *app)
+{
+ g_assert (GBP_IS_DOCSUI_APPLICATION_ADDIN (addin));
+ g_assert (G_IS_APPLICATION (app));
+
+ g_application_add_main_option (G_APPLICATION (app),
+ "docs",
+ 'd',
+ G_OPTION_FLAG_IN_MAIN,
+ G_OPTION_ARG_NONE,
+ _("Open documentation window"),
+ NULL);
+}
+
+static void
+application_addin_iface_init (IdeApplicationAddinInterface *iface)
+{
+ iface->add_option_entries = gbp_docsui_application_addin_add_option_entries;
+ iface->handle_command_line = gbp_docsui_application_addin_handle_command_line;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpDocsuiApplicationAddin, gbp_docsui_application_addin, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_APPLICATION_ADDIN,
+ application_addin_iface_init))
+
+static void
+gbp_docsui_application_addin_class_init (GbpDocsuiApplicationAddinClass *klass)
+{
+}
+
+static void
+gbp_docsui_application_addin_init (GbpDocsuiApplicationAddin *self)
+{
+}
diff --git a/src/plugins/docsui/gbp-docsui-application-addin.h
b/src/plugins/docsui/gbp-docsui-application-addin.h
new file mode 100644
index 000000000..61b14620a
--- /dev/null
+++ b/src/plugins/docsui/gbp-docsui-application-addin.h
@@ -0,0 +1,31 @@
+/* gbp-docsui-application-addin.h
+ *
+ * Copyright 2018 Christian Hergert <unknown domain org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_DOCSUI_APPLICATION_ADDIN (gbp_docsui_application_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpDocsuiApplicationAddin, gbp_docsui_application_addin, GBP,
DOCSUI_APPLICATION_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/docsui/meson.build b/src/plugins/docsui/meson.build
new file mode 100644
index 000000000..877532722
--- /dev/null
+++ b/src/plugins/docsui/meson.build
@@ -0,0 +1,12 @@
+plugins_sources += files([
+ 'docsui-plugin.c',
+ 'gbp-docsui-application-addin.c',
+])
+
+plugin_docsui_resources = gnome.compile_resources(
+ 'docsui-resources',
+ 'docsui.gresource.xml',
+ c_name: 'gbp_docsui',
+)
+
+plugins_sources += plugin_docsui_resources
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index 17583bf9e..d626708fe 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -14,6 +14,7 @@ plugins_deps = [
libide_code_dep,
libide_core_dep,
+ libide_docs_dep,
libide_debugger_dep,
libide_editor_dep,
libide_foundry_dep,
@@ -56,6 +57,7 @@ subdir('devhelp')
subdir('deviceui')
subdir('deviced')
subdir('doap')
+subdir('docsui')
subdir('dspy')
subdir('editor')
subdir('editorconfig')
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]