Commit d816a50e authored by Christian Hergert's avatar Christian Hergert

search: port search to DzlSuggestion

This is a bit different style of search engine than we had before. It aims
to just be as simple as we can and still get the same performance and ease
of implementation.
parent cdbd387a
......@@ -20,6 +20,8 @@
#include <dazzle.h>
#include "ide-context.h"
#include "files/ide-file.h"
#include "diagnostics/ide-source-location.h"
......@@ -221,3 +223,18 @@ ide_source_location_hash (IdeSourceLocation *self)
{
return ide_file_hash (self->file) ^ g_int_hash (&self->line) ^ g_int_hash (&self->line_offset);
}
IdeSourceLocation *
ide_source_location_new_for_path (IdeContext *context,
const gchar *path,
guint line,
guint line_offset)
{
g_autoptr(IdeFile) ifile = NULL;
g_return_val_if_fail (!context || IDE_IS_CONTEXT (context), NULL);
ifile = ide_file_new_for_path (context, path);
return ide_source_location_new (ifile, line, line_offset, 0);
}
......@@ -34,6 +34,10 @@ IdeSourceLocation *ide_source_location_new (IdeFile
guint line,
guint line_offset,
guint offset);
IdeSourceLocation *ide_source_location_new_for_path (IdeContext *context,
const gchar *path,
guint line,
guint line_offset);
guint ide_source_location_get_line (IdeSourceLocation *self);
guint ide_source_location_get_line_offset (IdeSourceLocation *self);
guint ide_source_location_get_offset (IdeSourceLocation *self);
......
......@@ -106,9 +106,8 @@ G_BEGIN_DECLS
#include "runtimes/ide-runtime-manager.h"
#include "runtimes/ide-runtime-provider.h"
#include "runtimes/ide-runtime.h"
#include "search/ide-omni-search-row.h"
#include "search/ide-search-context.h"
#include "search/ide-search-engine.h"
#include "search/ide-search-entry.h"
#include "search/ide-search-provider.h"
#include "search/ide-search-reducer.h"
#include "search/ide-search-result.h"
......
......@@ -122,14 +122,9 @@ libide_public_headers = [
'runtimes/ide-runtime-manager.h',
'runtimes/ide-runtime-provider.h',
'runtimes/ide-runtime.h',
'search/ide-omni-search-display.h',
'search/ide-omni-search-entry.h',
'search/ide-omni-search-group.h',
'search/ide-omni-search-row.h',
'search/ide-search-context.h',
'search/ide-search-engine.h',
'search/ide-search-entry.h',
'search/ide-search-provider.h',
'search/ide-search-reducer.h',
'search/ide-search-result.h',
'snippets/ide-source-snippet-chunk.h',
'snippets/ide-source-snippet-context.h',
......@@ -321,12 +316,8 @@ libide_public_sources = [
'runtimes/ide-runtime-manager.c',
'runtimes/ide-runtime-provider.c',
'runtimes/ide-runtime.c',
'search/ide-omni-search-display.c',
'search/ide-omni-search-entry.c',
'search/ide-omni-search-group.c',
'search/ide-omni-search-row.c',
'search/ide-search-context.c',
'search/ide-search-engine.c',
'search/ide-search-entry.c',
'search/ide-search-provider.c',
'search/ide-search-result.c',
'snippets/ide-source-snippet-chunk.c',
......@@ -502,6 +493,7 @@ libide_sources = libide_generated_headers + libide_public_sources + [
'preferences/ide-preferences-language-row.h',
'runner/ide-run-manager-private.h',
'search/ide-search-reducer.c',
'search/ide-search-reducer.h',
'snippets/ide-source-snippet-completion-item.c',
'snippets/ide-source-snippet-completion-item.h',
'snippets/ide-source-snippet-completion-provider.c',
......@@ -663,6 +655,7 @@ libide_plugin_dep = declare_dependency(
libgd_dep,
libgio_dep,
libgtk_dep,
libgd_dep,
libgtksource_dep,
libtemplate_glib_dep,
libjson_glib_dep,
......
......@@ -64,8 +64,6 @@
<file compressed="true" alias="ide-layout-stack.ui">../workbench/ide-layout-stack.ui</file>
<file compressed="true" alias="ide-omni-bar.ui">../workbench/ide-omni-bar.ui</file>
<file compressed="true" alias="ide-omni-bar-row.ui">../workbench/ide-omni-bar-row.ui</file>
<file compressed="true" alias="ide-omni-search-group.ui">../search/ide-omni-search-group.ui</file>
<file compressed="true" alias="ide-omni-search-row.ui">../search/ide-omni-search-row.ui</file>
<file compressed="true" alias="ide-perspective-menu-button.ui">../workbench/ide-perspective-menu-button.ui</file>
<file compressed="true" alias="ide-preferences-language-row.ui">../preferences/ide-preferences-language-row.ui</file>
<file compressed="true" alias="ide-run-button.ui">../runner/ide-run-button.ui</file>
......
This diff is collapsed.
/* ide-omni-search-display.h
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* 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/>.
*/
#ifndef IDE_OMNI_SEARCH_DISPLAY_H
#define IDE_OMNI_SEARCH_DISPLAY_H
#include <gtk/gtk.h>
#include "ide-search-context.h"
G_BEGIN_DECLS
#define IDE_TYPE_OMNI_SEARCH_DISPLAY (ide_omni_search_display_get_type())
G_DECLARE_FINAL_TYPE (IdeOmniSearchDisplay, ide_omni_search_display, IDE, OMNI_SEARCH_DISPLAY, GtkBin)
IdeSearchContext *ide_omni_search_display_get_context (IdeOmniSearchDisplay *self);
void ide_omni_search_display_set_context (IdeOmniSearchDisplay *self,
IdeSearchContext *context);
guint64 ide_omni_search_display_get_count (IdeOmniSearchDisplay *self);
void ide_omni_search_display_move_next_result (IdeOmniSearchDisplay *self);
void ide_omni_search_display_move_previous_result (IdeOmniSearchDisplay *self);
G_END_DECLS
#endif /* IDE_OMNI_SEARCH_DISPLAY_H */
/* ide-omni-search-entry.c
*
* Copyright (C) 2014 Christian Hergert <christian@hergert.me>
*
* 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/>.
*/
#define G_LOG_DOMAIN "ide-omni-search-entry"
#include <glib/gi18n.h>
#include "ide-macros.h"
#include "search/ide-omni-search-entry.h"
#include "search/ide-omni-search-display.h"
#include "search/ide-search-result.h"
#include "util/ide-gtk.h"
#define SHORT_DELAY_TIMEOUT_MSEC 20
#define LONG_DELAY_TIMEOUT_MSEC 50
#define LONG_DELAY_MAX_CHARS 3
#define RESULTS_PER_PROVIDER 7
struct _IdeOmniSearchEntry
{
GtkEntry parent_instance;
/* Template references */
IdeOmniSearchDisplay *display;
GtkEntry *entry;
GtkPopover *popover;
guint delay_timeout;
gboolean has_results;
};
G_DEFINE_TYPE (IdeOmniSearchEntry, ide_omni_search_entry, GTK_TYPE_ENTRY)
enum {
CLEAR_SEARCH,
MOVE_NEXT_RESULT,
MOVE_PREVIOUS_RESULT,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL];
static void ide_omni_search_entry_changed (IdeOmniSearchEntry *self);
static void ide_omni_search_entry_popover_hide (IdeOmniSearchEntry *self,
GtkPopover *popover);
GtkWidget *
ide_omni_search_entry_new (void)
{
return g_object_new (IDE_TYPE_OMNI_SEARCH_ENTRY, NULL);
}
/**
* ide_omni_search_entry_get_search_engine:
* @self: An #IdeOmniSearchEntry.
*
* Gets the search engine to use with the current workbench.
*
* Returns: (transfer none): An #IdeSearchEngine.
*/
IdeSearchEngine *
ide_omni_search_entry_get_search_engine (IdeOmniSearchEntry *self)
{
IdeWorkbench *workbench;
IdeContext *context;
g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self), NULL);
workbench = ide_widget_get_workbench (GTK_WIDGET (self));
if (workbench == NULL)
return NULL;
context = ide_workbench_get_context (workbench);
if (context == NULL)
return NULL;
return ide_context_get_search_engine (context);
}
static void
ide_omni_search_entry_hide_popover (IdeOmniSearchEntry *self,
gboolean leave_entry)
{
g_autofree gchar *text = NULL;
IdeWorkbench *workbench;
IdePerspective *perspective;
gint position = 0;
/*
* Hiding the popover will cause the entry to get focus,
* thereby selecting all available text. We don't want
* that to happen.
*/
g_signal_handlers_block_by_func (self, ide_omni_search_entry_changed, NULL);
g_signal_handlers_block_by_func (self->popover, ide_omni_search_entry_popover_hide, self);
if (!leave_entry)
{
text = g_strdup (gtk_entry_get_text (GTK_ENTRY (self)));
position = gtk_editable_get_position (GTK_EDITABLE (self));
}
gtk_entry_set_text (GTK_ENTRY (self), "");
gtk_popover_popdown (self->popover);
if (!leave_entry)
{
gtk_entry_set_text (GTK_ENTRY (self), text);
gtk_editable_set_position (GTK_EDITABLE (self), position);
}
g_signal_handlers_unblock_by_func (self->popover, ide_omni_search_entry_popover_hide, self);
g_signal_handlers_unblock_by_func (self, ide_omni_search_entry_changed, NULL);
if (leave_entry)
{
workbench = ide_widget_get_workbench (GTK_WIDGET (self));
perspective = ide_workbench_get_visible_perspective (workbench);
gtk_widget_grab_focus (GTK_WIDGET (perspective));
self->has_results = FALSE;
}
}
static void
ide_omni_search_entry_clear_search (IdeOmniSearchEntry *self)
{
ide_omni_search_entry_hide_popover (self, TRUE);
}
static void
ide_omni_search_entry_completed (IdeOmniSearchEntry *self,
IdeSearchContext *context)
{
g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
g_assert (IDE_IS_SEARCH_CONTEXT (context));
if (ide_omni_search_display_get_count (self->display) == 0)
{
self->has_results = FALSE;
ide_omni_search_entry_hide_popover (self, FALSE);
}
else
{
self->has_results = TRUE;
gtk_widget_set_visible (GTK_WIDGET (self->popover), TRUE);
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self));
}
}
static gboolean
ide_omni_search_entry_delay_cb (gpointer user_data)
{
IdeOmniSearchEntry *self = user_data;
IdeSearchEngine *search_engine;
IdeSearchContext *context;
const gchar *search_text;
g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
self->delay_timeout = 0;
if (self->display)
{
context = ide_omni_search_display_get_context (self->display);
if (context != NULL)
ide_search_context_cancel (context);
search_engine = ide_omni_search_entry_get_search_engine (self);
search_text = gtk_entry_get_text (GTK_ENTRY (self));
if (search_engine == NULL || search_text == NULL)
return G_SOURCE_REMOVE;
context = ide_search_engine_search (search_engine, search_text);
g_signal_connect_object (context,
"completed",
G_CALLBACK (ide_omni_search_entry_completed),
self,
G_CONNECT_SWAPPED);
ide_omni_search_display_set_context (self->display, context);
ide_search_context_execute (context, search_text, RESULTS_PER_PROVIDER);
g_object_unref (context);
}
return G_SOURCE_REMOVE;
}
static void
ide_omni_search_entry_activate (IdeOmniSearchEntry *self)
{
g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
gtk_widget_activate (GTK_WIDGET (self->display));
ide_omni_search_entry_hide_popover (self, TRUE);
}
static void
ide_omni_search_entry_changed (IdeOmniSearchEntry *self)
{
const gchar *text;
gboolean had_focus;
guint position;
g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
text = gtk_entry_get_text (GTK_ENTRY (self));
had_focus = gtk_widget_has_focus (GTK_WIDGET (self));
position = gtk_editable_get_position (GTK_EDITABLE (self));
/*
* Showing the popover could steal focus, so reset the focus to the
* entry and reset the position which might get mucked up by focus
* changes.
*/
if (had_focus)
{
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self));
gtk_editable_set_position (GTK_EDITABLE (self), position);
}
if (self->delay_timeout == 0)
{
guint delay_msec = SHORT_DELAY_TIMEOUT_MSEC;
if (text != NULL)
{
if (strlen (text) <= LONG_DELAY_MAX_CHARS)
delay_msec = LONG_DELAY_TIMEOUT_MSEC;
self->delay_timeout = g_timeout_add (delay_msec,
ide_omni_search_entry_delay_cb,
self);
}
}
}
static void
ide_omni_search_entry_display_result_activated (IdeOmniSearchEntry *self,
IdeSearchResult *result,
IdeOmniSearchDisplay *display)
{
g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (display));
ide_omni_search_entry_hide_popover (self, TRUE);
}
static gboolean
ide_omni_search_entry_popover_key_press_event (IdeOmniSearchEntry *self,
GdkEventKey *event,
GtkPopover *popover)
{
g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
g_assert (event != NULL);
g_assert (GTK_IS_POPOVER (popover));
return GTK_WIDGET_GET_CLASS (self)->key_press_event (GTK_WIDGET (self), event);
}
static void
ide_omni_search_entry_popover_hide (IdeOmniSearchEntry *self,
GtkPopover *popover)
{
g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
g_assert (GTK_IS_POPOVER (popover));
if (self->has_results)
ide_omni_search_entry_hide_popover (self, TRUE);
}
static void
ide_omni_search_entry_move_next_result (IdeOmniSearchEntry *self)
{
g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
ide_omni_search_display_move_next_result (self->display);
}
static void
ide_omni_search_entry_move_previous_result (IdeOmniSearchEntry *self)
{
g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
ide_omni_search_display_move_previous_result (self->display);
}
static void
ide_omni_search_entry_destroy (GtkWidget *widget)
{
IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)widget;
ide_clear_source (&self->delay_timeout);
g_clear_pointer ((GtkWidget **)&self->popover, gtk_widget_destroy);
GTK_WIDGET_CLASS (ide_omni_search_entry_parent_class)->destroy (widget);
}
static void
ide_omni_search_entry_class_init (IdeOmniSearchEntryClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkBindingSet *binding_set;
widget_class->destroy = ide_omni_search_entry_destroy;
g_signal_override_class_handler ("activate",
G_TYPE_FROM_CLASS (klass),
G_CALLBACK (ide_omni_search_entry_activate));
signals [CLEAR_SEARCH] =
g_signal_new_class_handler ("clear-search",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_CALLBACK (ide_omni_search_entry_clear_search),
NULL, NULL, NULL, G_TYPE_NONE, 0);
signals [MOVE_NEXT_RESULT] =
g_signal_new_class_handler ("move-next-result",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_CALLBACK (ide_omni_search_entry_move_next_result),
NULL, NULL, NULL, G_TYPE_NONE, 0);
signals [MOVE_PREVIOUS_RESULT] =
g_signal_new_class_handler ("move-previous-result",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_CALLBACK (ide_omni_search_entry_move_previous_result),
NULL, NULL, NULL, G_TYPE_NONE, 0);
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "clear-search", 0);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, "activate", 0);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, "activate", 0);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, 0, "move-next-result", 0);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, 0, "move-previous-result", 0);
}
static void
ide_omni_search_entry_init (IdeOmniSearchEntry *self)
{
g_object_set (self,
"max-width-chars", 30,
"primary-icon-name", "edit-find-symbolic",
"primary-icon-activatable", FALSE,
"primary-icon-sensitive", FALSE,
NULL);
self->popover = g_object_new (GTK_TYPE_POPOVER,
"modal", FALSE,
"position", GTK_POS_BOTTOM,
"relative-to", self,
"width-request", 400,
NULL);
g_signal_connect_object (self->popover,
"key-press-event",
G_CALLBACK (ide_omni_search_entry_popover_key_press_event),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (self->popover,
"hide",
G_CALLBACK (ide_omni_search_entry_popover_hide),
self,
G_CONNECT_SWAPPED);
self->display = g_object_new (IDE_TYPE_OMNI_SEARCH_DISPLAY,
"visible", TRUE,
NULL);
gtk_container_add (GTK_CONTAINER (self->popover), GTK_WIDGET (self->display));
g_signal_connect (self,
"changed",
G_CALLBACK (ide_omni_search_entry_changed),
NULL);
g_signal_connect_object (self->display,
"result-activated",
G_CALLBACK (ide_omni_search_entry_display_result_activated),
self,
G_CONNECT_SWAPPED);
}
/* ide-omni-search-entry.h
*
* Copyright (C) 2014 Christian Hergert <christian@hergert.me>
*
* 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/>.
*/
#ifndef IDE_OMNI_SEARCH_ENTRY_H
#define IDE_OMNI_SEARCH_ENTRY_H
#include <gtk/gtk.h>
#include "ide-search-engine.h"
G_BEGIN_DECLS
#define IDE_TYPE_OMNI_SEARCH_ENTRY (ide_omni_search_entry_get_type())
G_DECLARE_FINAL_TYPE (IdeOmniSearchEntry, ide_omni_search_entry, IDE, OMNI_SEARCH_ENTRY, GtkEntry)
GtkWidget *ide_omni_search_entry_new (void);
IdeSearchEngine *ide_omni_search_entry_get_search_engine (IdeOmniSearchEntry *self);
void ide_omni_search_entry_set_search_engine (IdeOmniSearchEntry *self,
IdeSearchEngine *search_engine);
G_END_DECLS
#endif /* IDE_OMNI_SEARCH_ENTRY_H */
/* ide-omni-search-display-group.c
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* 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/>.
*/
#include <glib/gi18n.h>
#include "ide-omni-search-group.h"
#include "ide-omni-search-row.h"
struct _IdeOmniSearchGroup
{
GtkBox parent_instance;
/* References owned by instance */
IdeSearchProvider *provider;
/* References owned by template */
GtkListBox *rows;
guint64 count;
};
G_DEFINE_TYPE (IdeOmniSearchGroup, ide_omni_search_group, GTK_TYPE_BOX)
enum {
PROP_0,
PROP_PROVIDER,
LAST_PROP
};
enum {
RESULT_ACTIVATED,
RESULT_SELECTED,
LAST_SIGNAL
};
static GQuark quarkRow;
static GParamSpec *properties [LAST_PROP];
static guint signals [LAST_SIGNAL];
static void
ide_omni_search_group_foreach_cb (GtkWidget *widget,
gpointer user_data)
{
GtkWidget **row = user_data;
if (*row == NULL)
*row = widget;
}
/**
* ide_omni_search_group_get_first:
*
* Returns: (transfer none) (nullable): An #IdeSearchResult or %NULL.
*/
IdeSearchResult *
ide_omni_search_group_get_first (IdeOmniSearchGroup *self)
{
GtkListBoxRow *row = NULL;
IdeSearchResult *ret = NULL;
g_return_val_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self), NULL);
gtk_container_foreach (GTK_CONTAINER (self->rows),
ide_omni_search_group_foreach_cb,
&row);
if (IDE_IS_OMNI_SEARCH_ROW (row))
ret = ide_omni_search_row_get_result (IDE_OMNI_SEARCH_ROW (row));
return ret;
}
/**
* ide_omni_search_group_get_provider:
*
* Returns: (transfer none): An #IdeSearchProvider
*/
IdeSearchProvider *
ide_omni_search_group_get_provider (IdeOmniSearchGroup *self)
{
g_return_val_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self), NULL);
return self->provider;
}
static void
ide_omni_search_group_set_provider (IdeOmniSearchGroup *self,
IdeSearchProvider *provider)
{
g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
g_return_if_fail (!provider || IDE_IS_SEARCH_PROVIDER (provider));
if (provider)
self->provider = g_object_ref (provider);
}
GtkWidget *
ide_omni_search_group_create_row (IdeSearchResult *result)
{
IdeSearchProvider *provider;
GtkWidget *row;
g_return_val_if_fail (IDE_IS_SEARCH_RESULT (result), NULL);
provider = ide_search_result_get_provider (result);
row = ide_search_provider_create_row (provider, result);
g_object_set_qdata (G_OBJECT (result), quarkRow, row);
return row;
}
void
ide_omni_search_group_remove_result (IdeOmniSearchGroup *self,
IdeSearchResult *result)
{
GtkWidget *row;
g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));