/* ide-terminal-search.c * * Copyright 2015-2019 Christian Hergert * * 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 . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-terminal-search" #define PCRE2_CODE_UNIT_WIDTH 0 #include "config.h" #include #include #include #include #include #include #include "ide-terminal-search.h" #include "ide-terminal-search-private.h" G_DEFINE_FINAL_TYPE (IdeTerminalSearch, ide_terminal_search, GTK_TYPE_BIN) enum { PROP_0, PROP_REGEX, PROP_WRAP_AROUND, LAST_PROP }; enum { SEARCH, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static GParamSpec *properties[LAST_PROP]; static void update_sensitivity (IdeTerminalSearch *self) { gboolean can_search; can_search = self->regex != NULL; gtk_widget_set_sensitive (GTK_WIDGET (self->search_prev_button), can_search); gtk_widget_set_sensitive (GTK_WIDGET (self->search_next_button), can_search); } static void perform_search (IdeTerminalSearch *self, gboolean backward) { g_assert (IDE_IS_TERMINAL_SEARCH (self)); if (self->regex == NULL) return; g_signal_emit (self, signals[SEARCH], 0, backward); } static void close_clicked_cb (GtkButton *button, IdeTerminalSearch *self) { g_assert (IDE_IS_TERMINAL_SEARCH (self)); gtk_revealer_set_reveal_child(self->search_revealer, FALSE); } static void search_button_clicked_cb (GtkButton *button, IdeTerminalSearch *self) { g_assert (IDE_IS_TERMINAL_SEARCH (self)); perform_search (self, button == self->search_prev_button); } static void update_regex (IdeTerminalSearch *self) { const char *search_text; gboolean caseless; g_autofree gchar *pattern = NULL; g_autoptr(GError) error = NULL; g_assert (IDE_IS_TERMINAL_SEARCH (self)); search_text = gtk_entry_get_text (GTK_ENTRY (self->search_entry)); caseless = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->match_case_checkbutton)); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->regex_checkbutton))) pattern = g_strdup (search_text); else pattern = g_regex_escape_string (search_text, -1); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->entire_word_checkbutton))) { char *new_pattern; new_pattern = g_strdup_printf ("\\b%s\\b", pattern); g_free (pattern); pattern = new_pattern; } if (self->regex_caseless == caseless && g_strcmp0 (self->regex_pattern, pattern) == 0) return; if (self->regex) { vte_regex_unref (self->regex); } g_clear_pointer (&self->regex_pattern, g_free); if (search_text[0] != '\0') { guint32 compile_flags; compile_flags = PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_MULTILINE; if (caseless) compile_flags |= PCRE2_CASELESS; self->regex = vte_regex_new_for_search (pattern, -1, compile_flags, &error); if (self->regex != NULL) self->regex_pattern = g_steal_pointer (&pattern); } else self->regex = NULL; update_sensitivity (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REGEX]); } static void search_text_changed_cb (IdeTaggedEntry *search_entry, IdeTerminalSearch *self) { update_regex (self); } static void search_parameters_changed_cb (GtkToggleButton *button, IdeTerminalSearch *self) { update_regex (self); } static void wrap_around_toggled_cb (GtkToggleButton *button, IdeTerminalSearch *self) { g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WRAP_AROUND]); } static void reveal_options_changed_cb (GtkToggleButton *button, IdeTerminalSearch *self) { g_assert (IDE_IS_TERMINAL_SEARCH (self)); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->reveal_button))) gtk_widget_set_visible (GTK_WIDGET (self->search_options), TRUE); else gtk_widget_set_visible (GTK_WIDGET (self->search_options), FALSE); } static void search_overlay_notify_regex_cb (VteTerminal *terminal, GParamSpec *pspec G_GNUC_UNUSED, IdeTerminalSearch *self) { VteRegex *regex; g_assert (IDE_IS_TERMINAL_SEARCH (self)); g_assert (VTE_IS_TERMINAL (terminal)); regex = ide_terminal_search_get_regex (self); vte_terminal_search_set_regex (VTE_TERMINAL (terminal), regex, 0); } static void search_overlay_notify_wrap_around_cb (VteTerminal *terminal, GParamSpec *pspec G_GNUC_UNUSED, IdeTerminalSearch *self) { gboolean wrap; g_assert (IDE_IS_TERMINAL_SEARCH (self)); g_assert (VTE_IS_TERMINAL (terminal)); wrap = ide_terminal_search_get_wrap_around (self); vte_terminal_search_set_wrap_around (terminal, wrap); } static void search_overlay_search_cb (VteTerminal *terminal, gboolean backward, IdeTerminalSearch *self) { g_assert (VTE_IS_TERMINAL (terminal)); if (backward) vte_terminal_search_find_previous (terminal); else vte_terminal_search_find_next (terminal); } static void search_revealer_cb (GtkRevealer *search_revealer, GParamSpec *pspec, IdeTerminalSearch *self) { g_assert (IDE_IS_TERMINAL_SEARCH (self)); if (gtk_revealer_get_child_revealed (search_revealer)) { if (vte_terminal_get_has_selection (self->terminal)) { vte_terminal_copy_primary (self->terminal); self->selected_text = gtk_clipboard_wait_for_text (self->clipboard); gtk_entry_set_text (GTK_ENTRY (self->search_entry), self->selected_text); } gtk_widget_grab_focus (GTK_WIDGET (self->search_entry)); } else { gtk_revealer_set_reveal_child (self->search_revealer, FALSE); } } static void ide_terminal_search_connect_terminal (IdeTerminalSearch *self) { g_signal_connect_object (self, "notify::regex", G_CALLBACK (search_overlay_notify_regex_cb), self->terminal, G_CONNECT_SWAPPED); g_signal_connect_object (self, "notify::wrap-around", G_CALLBACK (search_overlay_notify_wrap_around_cb), self->terminal, G_CONNECT_SWAPPED); g_signal_connect_object (self, "search", G_CALLBACK (search_overlay_search_cb), self->terminal, G_CONNECT_SWAPPED); } static void ide_terminal_search_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { IdeTerminalSearch *self = IDE_TERMINAL_SEARCH (object); switch (prop_id) { case PROP_REGEX: g_value_set_boxed (value, ide_terminal_search_get_regex (self)); break; case PROP_WRAP_AROUND: g_value_set_boolean (value, ide_terminal_search_get_wrap_around (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_terminal_search_class_init (IdeTerminalSearchClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = ide_terminal_search_get_property; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/libide-terminal/ui/ide-terminal-search.ui"); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_prev_button); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_next_button); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, close_button); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_entry); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, match_case_checkbutton); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, entire_word_checkbutton); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, regex_checkbutton); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, wrap_around_checkbutton); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, reveal_button); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_revealer); gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_options); signals[SEARCH] = g_signal_new ("search", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); properties[PROP_REGEX] = g_param_spec_boxed ("regex", NULL, NULL, VTE_TYPE_REGEX, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); properties[PROP_WRAP_AROUND] = g_param_spec_boolean ("wrap-around", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, LAST_PROP, properties); } static void ide_terminal_search_init (IdeTerminalSearch *self) { self->regex_caseless = FALSE; self->regex_pattern = 0; self->clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); gtk_widget_init_template (GTK_WIDGET (self)); g_signal_connect (self->search_prev_button, "clicked", G_CALLBACK (search_button_clicked_cb), self); g_signal_connect (self->search_next_button, "clicked", G_CALLBACK (search_button_clicked_cb), self); g_signal_connect (self->close_button, "clicked", G_CALLBACK (close_clicked_cb), self); g_signal_connect (self->search_entry, "search-changed", G_CALLBACK (search_text_changed_cb), self); g_signal_connect (self->match_case_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), self); g_signal_connect (self->entire_word_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), self); g_signal_connect (self->regex_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), self); g_signal_connect (self->reveal_button, "toggled", G_CALLBACK (reveal_options_changed_cb), self); g_signal_connect (self->wrap_around_checkbutton, "toggled", G_CALLBACK (wrap_around_toggled_cb), self); g_signal_connect (self->search_revealer, "notify::child-revealed", G_CALLBACK (search_revealer_cb), self); } /** * ide_terminal_search_set_terminal: * @self: a #IdeTerminalSearch * * Since: 3.32 */ void ide_terminal_search_set_terminal (IdeTerminalSearch *self, VteTerminal *terminal) { g_assert (IDE_IS_TERMINAL_SEARCH (self)); self->terminal = terminal; ide_terminal_search_connect_terminal (self); } /** * ide_terminal_search_get_regex: * @self: a #IdeTerminalSearch * * Returns: (transfer none) (nullable): a #VteRegex or %NULL. * * Since: 3.32 */ VteRegex * ide_terminal_search_get_regex (IdeTerminalSearch *self) { g_return_val_if_fail (IDE_IS_TERMINAL_SEARCH (self), NULL); return self->regex; } /** * ide_terminal_search_get_wrap_around: * @self: a #IdeTerminalSearch * * * Since: 3.32 */ gboolean ide_terminal_search_get_wrap_around (IdeTerminalSearch *self) { g_return_val_if_fail (IDE_IS_TERMINAL_SEARCH (self), FALSE); return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->wrap_around_checkbutton)); } /** * ide_terminal_search_get_revealer: * @self: a #IdeTerminalSearch * * Gets the revealer widget used for the terminal search. * * Returns: (transfer none): a #GtkRevealer * * Since: 3.32 */ GtkRevealer * ide_terminal_search_get_revealer (IdeTerminalSearch *self) { g_return_val_if_fail (IDE_IS_TERMINAL_SEARCH (self), FALSE); return self->search_revealer; }