summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorBerke Viktor <berkeviktor@aol.com>2012-05-28 23:17:09 +0200
committerBerke Viktor <berkeviktor@aol.com>2012-05-28 23:17:09 +0200
commita6cc734b3802846f6ffd1cb1123bf4ca978e6056 (patch)
tree39558987581eff061bc36301e5a5dfa83c98a70c /src
parent1f62507fc0e5a771be07312a243aee8e35e210ea (diff)
Search window improvements (Richard Hitt)
Diffstat (limited to 'src')
-rw-r--r--src/common/cfgfiles.c8
-rw-r--r--src/common/xchat.h6
-rw-r--r--src/fe-gtk/maingui.c7
-rw-r--r--src/fe-gtk/menu.c47
-rw-r--r--src/fe-gtk/search.c120
-rw-r--r--src/fe-gtk/xtext.c800
-rw-r--r--src/fe-gtk/xtext.h19
7 files changed, 850 insertions, 157 deletions
diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c
index 443538d2..6bbdb3f7 100644
--- a/src/common/cfgfiles.c
+++ b/src/common/cfgfiles.c
@@ -451,6 +451,7 @@ const struct prefs vars[] = {
 #endif
 	{"gui_pane_left_size", P_OFFINT (gui_pane_left_size), TYPE_INT},
 	{"gui_pane_right_size", P_OFFINT (gui_pane_right_size), TYPE_INT},
+	{"gui_pane_right_size_min", P_OFFINT (gui_pane_right_size_min), TYPE_INT},
 	{"gui_quit_dialog", P_OFFINT (gui_quit_dialog), TYPE_BOOL},
 	{"gui_slist_fav", P_OFFINT (slist_fav), TYPE_INT},
 	{"gui_slist_select", P_OFFINT (slist_select), TYPE_INT},
@@ -589,6 +590,11 @@ const struct prefs vars[] = {
 	{"text_max_indent", P_OFFINT (max_auto_indent), TYPE_INT},
 	{"text_max_lines", P_OFFINT (max_lines), TYPE_INT},
 	{"text_replay", P_OFFINT (text_replay), TYPE_BOOL},
+	{"text_search_case_match", P_OFFINT (text_search_case_match), TYPE_BOOL},
+	{"text_search_backward", P_OFFINT (text_search_backward), TYPE_BOOL},
+	{"text_search_highlight_all", P_OFFINT (text_search_highlight_all), TYPE_BOOL},
+	{"text_search_follow", P_OFFINT (text_search_follow), TYPE_BOOL},
+	{"text_search_regexp", P_OFFINT (text_search_regexp), TYPE_BOOL},
 	{"text_show_marker", P_OFFINT (show_marker), TYPE_BOOL},
 	{"text_show_sep", P_OFFINT (show_separator), TYPE_BOOL},
 	{"text_spell_langs", P_OFFSET (spell_langs), TYPE_STR},
@@ -715,6 +721,7 @@ load_config (void)
 	prefs.gui_tray = 1;
 	prefs.gui_pane_left_size = 100;
 	prefs.gui_pane_right_size = 100;
+	prefs.gui_pane_right_size_min = 80;
 	prefs.mainwindow_save = 1;
 	prefs.bantype = 2;
 	prefs.input_balloon_time = 20;
@@ -723,6 +730,7 @@ load_config (void)
 	prefs.autodccsend = 2;	/* browse mode */
 	prefs.url_grabber = 1;
 	prefs.url_grabber_limit = 0; /* 0 means unlimited for backcompat */
+	prefs.text_search_follow = 1;
 #ifdef WIN32
 	prefs.identd = 1;
 #endif
diff --git a/src/common/xchat.h b/src/common/xchat.h
index 95fb1324..9db44c3f 100644
--- a/src/common/xchat.h
+++ b/src/common/xchat.h
@@ -162,6 +162,7 @@ struct xchatprefs
 
 	int gui_pane_left_size;
 	int gui_pane_right_size;
+	int gui_pane_right_size_min;
 
 	int gui_ulist_pos;
 	int tab_pos;
@@ -327,6 +328,11 @@ struct xchatprefs
 		This is so that we continue using internal defaults (which can
 		change in the next release) until the user edits them. */
 	unsigned int save_pevents:1;
+	unsigned int text_search_case_match;
+	unsigned int text_search_backward;
+	unsigned int text_search_highlight_all;
+	unsigned int text_search_follow;
+	unsigned int text_search_regexp;
 };
 
 /* Session types */
diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c
index 31f9fbc5..0b7f7c43 100644
--- a/src/fe-gtk/maingui.c
+++ b/src/fe-gtk/maingui.c
@@ -800,6 +800,9 @@ mg_userlist_showhide (session *sess, int show)
 {
 	session_gui *gui = sess->gui;
 	int handle_size;
+	int right_size;
+
+	right_size = MAX (prefs.gui_pane_right_size, prefs.gui_pane_right_size_min);
 
 	if (show)
 	{
@@ -807,7 +810,7 @@ mg_userlist_showhide (session *sess, int show)
 		gui->ul_hidden = 0;
 
 		gtk_widget_style_get (GTK_WIDGET (gui->hpane_right), "handle-size", &handle_size, NULL);
-		gtk_paned_set_position (GTK_PANED (gui->hpane_right), GTK_WIDGET (gui->hpane_right)->allocation.width - (prefs.gui_pane_right_size + handle_size));
+		gtk_paned_set_position (GTK_PANED (gui->hpane_right), GTK_WIDGET (gui->hpane_right)->allocation.width - (right_size + handle_size));
 	}
 	else
 	{
@@ -2848,7 +2851,7 @@ mg_create_entry (session *sess, GtkWidget *box)
 #else
 	gui->input_box = entry = gtk_entry_new ();
 #endif
-	gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 2048);
+	gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 0);
 	g_signal_connect (G_OBJECT (entry), "activate",
 							G_CALLBACK (mg_inputbox_cb), gui);
 	gtk_container_add (GTK_CONTAINER (hbox), entry);
diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c
index e6a5eca0..4760ba93 100644
--- a/src/fe-gtk/menu.c
+++ b/src/fe-gtk/menu.c
@@ -1216,6 +1216,41 @@ menu_search ()
 }
 
 static void
+menu_search_next ()
+{
+	GtkXText *xtext = GTK_XTEXT (current_sess->gui->xtext);
+	xtext_buffer *buf = xtext->buffer;
+
+	if (!gtk_xtext_search (xtext, buf->search_text,
+		(buf->search_flags & (case_match | follow | regexp)), NULL))
+	{
+		fe_message (_("Search hit end, not found."), FE_MSG_ERROR);
+	}
+}
+
+static void
+menu_search_prev ()
+{
+	GtkXText *xtext = GTK_XTEXT (current_sess->gui->xtext);
+	xtext_buffer *buf = xtext->buffer;
+
+	if (!gtk_xtext_search(xtext, buf->search_text,
+		(buf->search_flags & (case_match | follow | regexp) | backward), NULL))
+	{
+		fe_message (_("Search hit end, not found."), FE_MSG_ERROR);
+	}
+}
+
+static void
+menu_search_reset ()
+{
+	GtkXText *xtext = GTK_XTEXT (current_sess->gui->xtext);
+	xtext_buffer *buf = xtext->buffer;
+
+	gtk_xtext_search (xtext, "", 0, NULL);
+}
+
+static void
 menu_resetmarker (GtkWidget * wid, gpointer none)
 {
 	gtk_xtext_reset_marker_pos (GTK_XTEXT (current_sess->gui->xtext));
@@ -1675,11 +1710,17 @@ static struct mymenu mymenu[] = {
 	{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1, GDK_m},
 	{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1, GDK_C},
 	{N_("C_lear Text"), menu_flushbuffer, GTK_STOCK_CLEAR, M_MENUSTOCK, 0, 0, 1, GDK_l},
-#define SEARCH_OFFSET 68
-	{N_("Search Text..."), menu_search, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_f},
 	{N_("Save Text..."), menu_savebuffer, GTK_STOCK_SAVE, M_MENUSTOCK, 0, 0, 1},
+#define SEARCH_OFFSET 70
+	{N_("Search"), 0, GTK_STOCK_JUSTIFY_LEFT, M_MENUSUB, 0, 0, 1},
+		{N_("Search Text..."), menu_search, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_f},
+		{N_("Reset Search"), menu_search_reset, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_F},
+		{N_("Search Next"   ), menu_search_next, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_g},
+		{N_("Search Previous"   ), menu_search_prev, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_G},
+		{0, 0, 0, M_END, 0, 0, 0},
+
+	{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1},	/* 74 */
 
-	{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1},	/* 70 */
 	{N_("_Contents"), menu_docs, GTK_STOCK_HELP, M_MENUSTOCK, 0, 0, 1, GDK_F1},
 #if 0
 	{N_("Check for updates"), menu_update, 0, M_MENUITEM, 0, 1},
diff --git a/src/fe-gtk/search.c b/src/fe-gtk/search.c
index bbde9c5c..30ef266f 100644
--- a/src/fe-gtk/search.c
+++ b/src/fe-gtk/search.c
@@ -42,15 +42,20 @@
 #include "xtext.h"
 #include "maingui.h"
 
-
-static textentry *last;	/* our last search pos */
-static int case_match = 0;
-static int search_backward = 0;
-
+GtkWidget *searchwin;
 
 static void
 search_search (session * sess, const gchar *text)
 {
+	gtk_xtext_search_flags flags;
+	textentry *last;
+	GError *err = NULL;
+
+	flags = ((prefs.text_search_case_match == 1? case_match: 0) |
+				(prefs.text_search_backward == 1? backward: 0) |
+				(prefs.text_search_highlight_all == 1? highlight: 0) |
+				(prefs.text_search_follow == 1? follow: 0) |
+				(prefs.text_search_regexp == 1? regexp: 0));
 	if (!is_session (sess))
 	{
 		fe_message (_("The window you opened this Search "
@@ -58,10 +63,20 @@ search_search (session * sess, const gchar *text)
 		return;
 	}
 
-	last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text,
-									 last, case_match, search_backward);
-	if (!last)
+	last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text, flags, &err);
+	if (text == NULL || text[0] == 0)
+	{
+		return;
+	}
+	if (err)
+	{
+		fe_message (_(err->message), FE_MSG_ERROR);
+		g_error_free (err);
+	}
+	else if (!last)
+	{
 		fe_message (_("Search hit end, not found."), FE_MSG_ERROR);
+	}
 }
 
 static void
@@ -79,6 +94,23 @@ static void
 search_close_cb (GtkWidget * button, GtkWidget * win)
 {
 	gtk_widget_destroy (win);
+	searchwin = NULL;
+}
+
+static void
+search_reset_cb (GtkWidget * button, session * sess)
+{
+	search_search (sess, "");
+	if (searchwin)
+	{
+		search_close_cb (button, searchwin);
+	}
+}
+
+static void
+search_cleanup_cb (GtkWidget * button, GtkWidget * win)
+{
+	searchwin = NULL;
 }
 
 static void
@@ -98,13 +130,26 @@ search_key_cb (GtkWidget * window, GdkEventKey * key, gpointer userdata)
 static void
 search_caseign_cb (GtkToggleButton * but, session * sess)
 {
-	case_match = (but->active)? 1: 0;
+	prefs.text_search_case_match = (but->active)? 1: 0;
 }
 
 static void
 search_dirbwd_cb (GtkToggleButton * but, session * sess)
 {
-	search_backward = (but->active)? 1: 0;
+	prefs.text_search_backward = (but->active)? 1: 0;
+}
+
+static void
+search_regexp_cb (GtkToggleButton * but, session * sess)
+{
+	prefs.text_search_regexp = (but->active)? 1: 0;
+}
+
+static void
+search_highlight_cb (GtkToggleButton * but, session * sess)
+{
+	prefs.text_search_highlight_all = (but->active)? 1: 0;
+	search_search (sess, NULL);
 }
 
 void
@@ -112,18 +157,21 @@ search_open (session * sess)
 {
 	GtkWidget *win, *hbox, *vbox, *entry, *wid;
 
-	last = NULL;
+	if (searchwin)
+	{
+		gtk_widget_destroy (searchwin);
+		searchwin = NULL;
+	}
 	win = mg_create_generic_tab ("search", _("XChat: Search"), TRUE, FALSE,
-								 NULL, NULL, 0, 0, &vbox, 0);
+								 search_cleanup_cb, NULL, 0, 0, &vbox, 0);
 	gtk_container_set_border_width (GTK_CONTAINER (win), 12);
 	gtk_box_set_spacing (GTK_BOX (vbox), 4);
 
+	/* First line:  _____________________   _Find */
 	hbox = gtk_hbox_new (0, 10);
 	gtk_container_add (GTK_CONTAINER (vbox), hbox);
 	gtk_widget_show (hbox);
 
-	gtkutil_label_new (_("Find:"), hbox);
-
 	entry = gtk_entry_new ();
 	g_signal_connect (G_OBJECT (entry), "activate",
 							G_CALLBACK (search_entry_cb), sess);
@@ -131,29 +179,61 @@ search_open (session * sess)
 	gtk_widget_show (entry);
 	gtk_widget_grab_focus (entry);
 
+	wid = gtk_hbutton_box_new ();
+	gtk_container_add (GTK_CONTAINER (hbox), wid);
+	gtk_widget_show (wid);
+	wid = gtkutil_button (wid, GTK_STOCK_FIND, 0, search_find_cb, sess,
+								 _("_Find"));
+	g_object_set_data (G_OBJECT (wid), "e", entry);
+
+	/* Second line:  X Match case */
 	wid = gtk_check_button_new_with_mnemonic (_("_Match case"));
-	GTK_TOGGLE_BUTTON (wid)->active = case_match;
+	GTK_TOGGLE_BUTTON (wid)->active = prefs.text_search_case_match;
 	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_caseign_cb), sess);
 	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	add_tip (wid, "Perform a case-sensitive search.");
 	gtk_widget_show (wid);
 
+	/* Third line:  X Search backwards */
 	wid = gtk_check_button_new_with_mnemonic (_("Search _backwards"));
-	GTK_TOGGLE_BUTTON (wid)->active = search_backward;
+	GTK_TOGGLE_BUTTON (wid)->active = prefs.text_search_backward;
 	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_dirbwd_cb), sess);
 	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	add_tip (wid, "Search from the newest text line to the oldest.");
+	gtk_widget_show (wid);
+
+	/* Fourth line:  X Highlight all */
+	wid = gtk_check_button_new_with_mnemonic (_("_Highlight all"));
+	GTK_TOGGLE_BUTTON (wid)->active = prefs.text_search_highlight_all;
+	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_highlight_cb), sess);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	add_tip (wid, "Highlight all occurrences, and underline the current occurrence.");
 	gtk_widget_show (wid);
 
+	/* Fifth line:  X Regular expression */
+	wid = gtk_check_button_new_with_mnemonic (_("R_egular expression"));
+	GTK_TOGGLE_BUTTON (wid)->active = prefs.text_search_regexp;
+	g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_regexp_cb), sess);
+	gtk_container_add (GTK_CONTAINER (vbox), wid);
+	add_tip (wid, "Regard search string as a regular expression.");
+	gtk_widget_show (wid);
+
+	/* Sixth line:  _Close    Close and _Reset */
 	hbox = gtk_hbutton_box_new ();
 	gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 4);
 	gtk_widget_show (hbox);
 
-	gtkutil_button (hbox, GTK_STOCK_CLOSE, 0, search_close_cb, win,
+	wid = gtkutil_button (hbox, GTK_STOCK_CLOSE, 0, search_close_cb, win,
 						_("_Close"));
-	wid = gtkutil_button (hbox, GTK_STOCK_FIND, 0, search_find_cb, sess,
-								_("_Find"));
-	g_object_set_data (G_OBJECT (wid), "e", entry);
+	add_tip (wid, "Close this box, but continue searching new lines.");
+	wid = gtkutil_button (hbox, "gtk-reset", 0, search_reset_cb, sess,
+						_("Close and _Reset"));
+	add_tip (wid, "Close this box, reset highlighted search items, and stop searching new lines.");
 
+	/* Add recognition of the ESC key to close the box */
 	g_signal_connect (G_OBJECT (win), "key_press_event", G_CALLBACK (search_key_cb), win);
 
+	/* That's all, folks */
+	searchwin = win;
 	gtk_widget_show (win);
 }
diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c
index 3bc9b6a8..2899ba68 100644
--- a/src/fe-gtk/xtext.c
+++ b/src/fe-gtk/xtext.c
@@ -65,6 +65,11 @@
 #include "mmx_cmod.h"
 #endif
 
+#include "../common/xchat.h"
+#include "../common/fe.h"
+#include "../common/util.h"
+#include "../common/xchatc.h"
+#include "fe-gtk.h"
 #include "xtext.h"
 #include "../common/xchat.h"
 #include "../common/xchatc.h"
@@ -92,6 +97,20 @@
 
 static GtkWidgetClass *parent_class = NULL;
 
+/*
+ * offsets_t is used for retaining search information.
+ * It is stored in the 'data' member of a GList,
+ * as chained from ent->marks.  It saves starting and
+ * ending+1 offset of a found occurrence.
+ */
+typedef union offsets_u {
+	struct offsets_s {
+		guint16	start;
+		guint16	end;
+	} o;
+	guint32 u;
+} offsets_t;
+
 struct textentry
 {
 	struct textentry *next;
@@ -111,6 +130,7 @@ struct textentry
 	guchar tag;
 	guchar pad1;
 	guchar pad2;	/* 32-bit align : 44 bytes total */
+	GList *marks;	/* List of found strings */
 };
 
 enum
@@ -149,11 +169,25 @@ static void gtk_xtext_recalc_widths (xtext_buffer *buf, int);
 static void gtk_xtext_fix_indent (xtext_buffer *buf);
 static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line);
 static char *gtk_xtext_conv_color (unsigned char *text, int len, int *newlen);
+/* For use by gtk_xtext_strip_color() and its callers -- */
+typedef union offlen_u {
+   struct offlen_s {
+      guint16  off;
+      guint16  len;
+   } o;
+   guint32 u;
+} offlen_t;
 static unsigned char *
 gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
-							  int *newlen, int *mb_ret, int strip_hidden);
+							  int *newlen, int *mb_ret, GSList **slp, int strip_hidden);
 static gboolean gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add);
 static int gtk_xtext_render_page_timeout (GtkXText * xtext);
+static int gtk_xtext_search_offset (xtext_buffer *buf, textentry *ent, unsigned int off);
+static void gtk_xtext_search_textentry (xtext_buffer *, textentry *, int);
+static void gtk_xtext_search_textentry_del (xtext_buffer *, textentry *);
+static void gtk_xtext_search_textentry_fini (gpointer, gpointer);
+static void gtk_xtext_search_fini (xtext_buffer *);
+static gboolean gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gtk_xtext_search_flags flags, GError **perr);
 
 /* some utility functions first */
 
@@ -770,9 +804,8 @@ gtk_xtext_adjustment_set (xtext_buffer *buf, int fire_signal)
 		if (adj->upper == 0)
 			adj->upper = 1;
 
-		adj->page_size =
-			(GTK_WIDGET (buf->xtext)->allocation.height -
-			 buf->xtext->font->descent) / buf->xtext->fontsize;
+		adj->page_size = GTK_WIDGET (buf->xtext)->allocation.height /
+							  buf->xtext->fontsize;
 		adj->page_increment = adj->page_size;
 
 		if (adj->value > adj->upper - adj->page_size)
@@ -1100,6 +1133,7 @@ gtk_xtext_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
 
 	xtext->avoid_trans = FALSE;
 
+	allocation->height = allocation->height / xtext->fontsize * xtext->fontsize;
 	widget->allocation = *allocation;
 	if (GTK_WIDGET_REALIZED (widget))
 	{
@@ -1129,18 +1163,6 @@ gtk_xtext_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
 	}
 }
 
-static void
-gtk_xtext_selection_clear_full (xtext_buffer *buf)
-{
-	textentry *ent = buf->text_first;
-	while (ent)
-	{
-		ent->mark_start = -1;
-		ent->mark_end = -1;
-		ent = ent->next;
-	}
-}
-
 static int
 gtk_xtext_selection_clear (xtext_buffer *buf)
 {
@@ -1724,13 +1746,6 @@ gtk_xtext_selection_draw (GtkXText * xtext, GdkEventMotion * event, gboolean ren
 		offset_end = tmp;
 	}
 
-	/* has the selection changed? Dont render unless necessary */
-	if (xtext->buffer->last_ent_start == ent_start &&
-		 xtext->buffer->last_ent_end == ent_end &&
-		 xtext->buffer->last_offset_start == offset_start &&
-		 xtext->buffer->last_offset_end == offset_end)
-		return;
-
 	/* set all the old mark_ fields to -1 */
 	gtk_xtext_selection_clear (xtext->buffer);
 
@@ -1760,10 +1775,24 @@ gtk_xtext_selection_draw (GtkXText * xtext, GdkEventMotion * event, gboolean ren
 		gtk_xtext_selection_render (xtext, ent_start, offset_start, ent_end, offset_end);
 }
 
+static int
+gtk_xtext_timeout_ms (GtkXText *xtext, int pixes)
+{
+	int apixes = abs(pixes);
+
+	if (apixes < 6) return 100;
+	if (apixes < 12) return 50;
+	if (apixes < 20) return 20;
+	return 10;
+}
+
 static gint
 gtk_xtext_scrolldown_timeout (GtkXText * xtext)
 {
 	int p_y, win_height;
+	xtext_buffer *buf = xtext->buffer;
+	GtkAdjustment *adj = xtext->adj;
+	textentry *ent;
 
 	gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0);
 	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height);
@@ -1771,13 +1800,30 @@ gtk_xtext_scrolldown_timeout (GtkXText * xtext)
 	if (p_y > win_height &&
 		 xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size))
 	{
-		xtext->adj->value++;
-		gtk_adjustment_changed (xtext->adj);
-		gtk_xtext_render_page (xtext);
-		return 1;
+		xtext->adj->value += buf->pagetop_ent->lines_taken;
+		ent = buf->last_ent_end->next;
+		if (ent)
+		{
+			gtk_adjustment_value_changed (xtext->adj);
+		}
+		else
+		{
+			buf->scrollbar_down = TRUE;
+		}
+		xtext->scroll_tag = g_timeout_add (gtk_xtext_timeout_ms (xtext, p_y - win_height),
+														(GSourceFunc)
+														gtk_xtext_scrolldown_timeout,
+														xtext);
+		xtext->select_start_y -= (adj->value - xtext->select_start_adj) * xtext->fontsize;
+		xtext->select_start_adj = adj->value;
+		gtk_xtext_selection_draw (xtext, NULL, TRUE);
+		gtk_xtext_render_ents (xtext, ent, buf->last_ent_end);
+	}
+	else
+	{
+		xtext->scroll_tag = 0;
 	}
 
-	xtext->scroll_tag = 0;
 	return 0;
 }
 
@@ -1785,18 +1831,35 @@ static gint
 gtk_xtext_scrollup_timeout (GtkXText * xtext)
 {
 	int p_y;
+	xtext_buffer *buf = xtext->buffer;
+	GtkAdjustment *adj = xtext->adj;
+	textentry *ent;
 
 	gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0);
 
-	if (p_y < 0 && xtext->adj->value > 0.0)
+	if (p_y < 0 && adj->value >= 0)
 	{
-		xtext->adj->value--;
-		gtk_adjustment_changed (xtext->adj);
-		gtk_xtext_render_page (xtext);
-		return 1;
+		buf->scrollbar_down = FALSE;
+		ent = buf->last_ent_start->prev;
+		if (ent)
+		{
+			adj->value -= ent->lines_taken;
+			gtk_adjustment_value_changed (adj);
+		}
+		xtext->select_start_y -= (adj->value - xtext->select_start_adj) * xtext->fontsize;
+		xtext->select_start_adj = adj->value;
+		gtk_xtext_selection_draw (xtext, NULL, TRUE);
+		gtk_xtext_render_ents (xtext, ent, buf->last_ent_end);
+		xtext->scroll_tag = g_timeout_add (gtk_xtext_timeout_ms (xtext, p_y),
+														(GSourceFunc)
+														gtk_xtext_scrollup_timeout,
+														xtext);
+	}
+	else
+	{
+		xtext->scroll_tag = 0;
 	}
 
-	xtext->scroll_tag = 0;
 	return 0;
 }
 
@@ -1806,35 +1869,32 @@ gtk_xtext_selection_update (GtkXText * xtext, GdkEventMotion * event, int p_y, g
 	int win_height;
 	int moved;
 
+	if (xtext->scroll_tag)
+	{
+		return;
+	}
+
 	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height);
 
 	/* selecting past top of window, scroll up! */
 	if (p_y < 0 && xtext->adj->value >= 0)
 	{
-		if (!xtext->scroll_tag)
-			xtext->scroll_tag = g_timeout_add (100,
-															(GSourceFunc)
-														 	gtk_xtext_scrollup_timeout,
-															xtext);
-		return;
+		gtk_xtext_scrollup_timeout (xtext);
 	}
 
 	/* selecting past bottom of window, scroll down! */
-	if (p_y > win_height &&
+	else if (p_y > win_height &&
 		 xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size))
 	{
-		if (!xtext->scroll_tag)
-			xtext->scroll_tag = g_timeout_add (100,
-															(GSourceFunc)
-															gtk_xtext_scrolldown_timeout,
-															xtext);
-		return;
+		gtk_xtext_scrolldown_timeout (xtext);
+	}
+	else
+	{
+		moved = (int)xtext->adj->value - xtext->select_start_adj;
+		xtext->select_start_y -= (moved * xtext->fontsize);
+		xtext->select_start_adj = xtext->adj->value;
+		gtk_xtext_selection_draw (xtext, event, render);
 	}
-
-	moved = (int)xtext->adj->value - xtext->select_start_adj;
-	xtext->select_start_y -= (moved * xtext->fontsize);
-	xtext->select_start_adj = xtext->adj->value;
-	gtk_xtext_selection_draw (xtext, event, render);
 }
 
 static char *
@@ -1890,7 +1950,7 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
 	if (ret_len)
 		*ret_len = str - word;
 
-	return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, FALSE);
+	return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, NULL, FALSE);
 }
 
 #ifdef MOTION_MONITOR
@@ -2190,6 +2250,11 @@ gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event)
 	if (event->button == 1)
 	{
 		xtext->button_down = FALSE;
+		if (xtext->scroll_tag)
+		{
+			g_source_remove (xtext->scroll_tag);
+			xtext->scroll_tag = 0;
+		}
 
 		gtk_grab_remove (widget);
 		/*gdk_pointer_ungrab (0);*/
@@ -2416,7 +2481,7 @@ gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret)
 		len = strlen (txt);
 	} else
 	{
-		stripped = gtk_xtext_strip_color (txt, strlen (txt), NULL, &len, 0, FALSE);
+		stripped = gtk_xtext_strip_color (txt, strlen (txt), NULL, &len, NULL, NULL, FALSE);
 		free (txt);
 	}
 
@@ -2456,12 +2521,14 @@ gtk_xtext_selection_get (GtkWidget * widget,
 
 #if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0)
 			gdk_string_to_compound_text (
+												stripped, &encoding, &format, &new_text,
+												&new_length);
 #else
 			gdk_string_to_compound_text_for_display (
 												gdk_drawable_get_display (widget->window),
-#endif
 												stripped, &encoding, &format, &new_text,
 												&new_length);
+#endif
 			gtk_selection_data_set (selection_data_ptr, encoding, format,
 											new_text, new_length);
 			gdk_free_compound_text (new_text);
@@ -2576,19 +2643,26 @@ gtk_xtext_get_type (void)
 
 static unsigned char *
 gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
-							  int *newlen, int *mb_ret, int strip_hidden)
+							  int *newlen, int *mb_ret, GSList **slp, int strip_hidden)
 {
 	int i = 0;
 	int rcol = 0, bgcol = 0;
 	int hidden = FALSE;
 	unsigned char *new_str;
 	int mb = FALSE;
+	GSList *sl = NULL;
+	unsigned char *text0 = text;
+	int off1, len1;
+	offlen_t data;
 
 	if (outbuf == NULL)
 		new_str = malloc (len + 2);
 	else
 		new_str = outbuf;
 
+	off1 = 0;
+	len1 = 0;
+	data.u = 0;
 	while (len > 0)
 	{
 		if (*text >= 128)
@@ -2622,12 +2696,32 @@ gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
 				break;
 			default:
 				if (!(hidden && strip_hidden))
+				{
+					if (text - text0 - off1 != len1)
+					{
+						if (len1)
+						{
+							data.o.off = off1;
+							data.o.len = len1;
+							sl = g_slist_append (sl, GUINT_TO_POINTER (data.u));
+							len1 = 0;
+						}
+						off1 = text - text0;
+					}
+					len1++;
 					new_str[i++] = *text;
+				}
 			}
 		}
 		text++;
 		len--;
 	}
+	if (len1)
+	{
+		data.o.off = off1;
+		data.o.len = len1;
+		sl = g_slist_append (sl, GUINT_TO_POINTER (data.u));
+	}
 
 	new_str[i] = 0;
 
@@ -2637,9 +2731,15 @@ gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
 	if (mb_ret != NULL)
 		*mb_ret = mb;
 
+	if (slp)
+		*slp = sl;
+	else
+		g_slist_free (sl);
+
 	return new_str;
 }
 
+
 /* GeEkMaN: converts mIRC control codes to literal control codes */
 
 static char *
@@ -2743,7 +2843,7 @@ gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len,
 	int new_len, mb;
 
 	new_buf = gtk_xtext_strip_color (text, len, xtext->scratch_buffer,
-												&new_len, &mb, !xtext->ignore_hidden);
+												&new_len, &mb, NULL, !xtext->ignore_hidden);
 
 	if (mb_ret)
 		*mb_ret = mb;
@@ -2909,6 +3009,63 @@ gtk_xtext_reset (GtkXText * xtext, int mark, int attribs)
 	xtext->nc = 0;
 }
 
+/*
+ * gtk_xtext_search_offset (buf, ent, off) --
+ * Look for arg offset in arg textentry
+ * Return one or more flags:
+ * 	GTK_MATCH_MID if we are in a match
+ * 	GTK_MATCH_START if we're at the first byte of it
+ * 	GTK_MATCH_END if we at the first byte past it
+ * 	GTK_MATCH_CUR if it is the current match
+ */
+#define GTK_MATCH_START	1
+#define GTK_MATCH_MID	2
+#define GTK_MATCH_END	4
+#define GTK_MATCH_CUR	8
+static int
+gtk_xtext_search_offset (xtext_buffer *buf, textentry *ent, unsigned int off)
+{
+	GList *gl;
+	offsets_t o;
+	int flags = 0;
+
+	for (gl = g_list_first (ent->marks); gl; gl = g_list_next (gl))
+	{
+		o.u = GPOINTER_TO_UINT (gl->data);
+		if (off < o.o.start || off > o.o.end)
+			continue;
+		flags = GTK_MATCH_MID;
+		if (off == o.o.start)
+			flags |= GTK_MATCH_START;
+		if (off == o.o.end)
+		{
+			gl = g_list_next (gl);
+			if (gl)
+			{
+				o.u = GPOINTER_TO_UINT (gl->data);
+				if (off ==  o.o.start)	/* If subseq match is adjacent */
+				{
+					flags |= (gl == buf->curmark)? GTK_MATCH_CUR: 0;
+				}
+				else		/* If subseq match is not adjacent */
+				{
+					flags |= GTK_MATCH_END;
+				}
+			}
+			else		/* If there is no subseq match */
+			{
+				flags |= GTK_MATCH_END;
+			}
+		}
+		else if (gl == buf->curmark)	/* If not yet at the end of this match */
+		{
+			flags |= GTK_MATCH_CUR;
+		}
+		break;
+	}
+	return flags;
+}
+
 /* render a single line, which WONT wrap, and parse mIRC colors */
 
 static int
@@ -2923,6 +3080,9 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent,
 	int offset;
 	int mark = FALSE;
 	int ret = 1;
+	int k;
+	int srch_underline = FALSE;
+	int srch_mark = FALSE;
 
 	xtext->in_hilight = FALSE;
 
@@ -3074,6 +3234,50 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent,
 				}
 			}
 
+			if (!left_only && !mark &&
+				 (k = gtk_xtext_search_offset (xtext->buffer, ent, offset + i)))
+			{
+				x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
+				pstr += j;
+				j = 0;
+				if (!(xtext->buffer->search_flags & highlight))
+				{
+					if (k & GTK_MATCH_CUR)
+					{
+						xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+						xtext_set_fg (xtext, gc, XTEXT_MARK_FG);
+						xtext->backcolor = TRUE;
+						srch_mark = TRUE;
+					} else
+					{
+						xtext_set_bg (xtext, gc, xtext->col_back);
+						xtext_set_fg (xtext, gc, xtext->col_fore);
+						xtext->backcolor = (xtext->col_back != XTEXT_BG)? TRUE: FALSE;
+						srch_mark = FALSE;
+					}
+				}
+				else
+				{
+					xtext->underline = (k & GTK_MATCH_CUR)? TRUE: FALSE;
+					if (k & (GTK_MATCH_START | GTK_MATCH_MID))
+					{
+						xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
+						xtext_set_fg (xtext, gc, XTEXT_MARK_FG);
+						xtext->backcolor = TRUE;
+						srch_mark = TRUE;
+					}
+					if (k & GTK_MATCH_END)
+					{
+						xtext_set_bg (xtext, gc, xtext->col_back);
+						xtext_set_fg (xtext, gc, xtext->col_fore);
+						xtext->backcolor = (xtext->col_back != XTEXT_BG)? TRUE: FALSE;
+						srch_mark = FALSE;
+						xtext->underline = FALSE;
+					}
+					srch_underline = xtext->underline;
+				}
+			}
+
 			switch (str[i])
 			{
 			case '\n':
@@ -3223,6 +3427,11 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent,
 			xtext_set_bg (xtext, gc, XTEXT_MARK_BG);
 			xtext_set_fg (xtext, gc, XTEXT_MARK_FG);
 			xtext->backcolor = TRUE;
+			if (srch_underline)
+			{
+				xtext->underline = FALSE;
+				srch_underline = FALSE;
+			}
 			mark = TRUE;
 		}
 
@@ -3245,7 +3454,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent,
 	if (j)
 		x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb);
 
-	if (mark)
+	if (mark || srch_mark)
 	{
 		xtext_set_bg (xtext, gc, xtext->col_back);
 		xtext_set_fg (xtext, gc, xtext->col_fore);
@@ -4413,7 +4622,7 @@ gtk_xtext_save (GtkXText * xtext, int fh)
 	while (ent)
 	{
 		buf = gtk_xtext_strip_color (ent->str, ent->str_len, NULL,
-											  &newlen, NULL, FALSE);
+											  &newlen, NULL, NULL, FALSE);
 		write (fh, buf, newlen);
 		write (fh, "\n", 1);
 		free (buf);
@@ -4800,6 +5009,11 @@ gtk_xtext_kill_ent (xtext_buffer *buffer, textentry *ent)
 
 	if (buffer->marker_pos == ent) buffer->marker_pos = NULL;
 
+	if (ent->marks)
+	{
+		gtk_xtext_search_textentry_del (buffer, ent);
+	}
+
 	free (ent);
 	return visible;
 }
@@ -4864,7 +5078,23 @@ gtk_xtext_remove_bottom (xtext_buffer *buffer)
 	else
 		buffer->text_first = NULL;
 
-	gtk_xtext_kill_ent (buffer, ent);
+	if (gtk_xtext_kill_ent (buffer, ent))
+	{
+		if (!buffer->xtext->add_io_tag)
+		{
+			/* remove scrolling events */
+			if (buffer->xtext->io_tag)
+			{
+				g_source_remove (buffer->xtext->io_tag);
+				buffer->xtext->io_tag = 0;
+			}
+			buffer->xtext->force_render = TRUE;
+			buffer->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2,
+														(GSourceFunc)
+														gtk_xtext_render_page_timeout,
+														buffer->xtext);
+		}
+	}
 }
 
 /* If lines=0 => clear all */
@@ -4899,6 +5129,8 @@ gtk_xtext_clear (xtext_buffer *buf, int lines)
 	else
 	{
 		/* delete all */
+		if (buf->search_found)
+			gtk_xtext_search_fini (buf);
 		if (buf->xtext->auto_indent)
 			buf->indent = MARGIN;
 		buf->scrollbar_down = TRUE;
@@ -4930,21 +5162,37 @@ static gboolean
 gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add)
 {
 	textentry *ent;
-	int lines_max;
-	int line = 0;
+	int lines;
+	xtext_buffer *buf = xtext->buffer;
 	int width;
 	int height;
 
-	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
+	if (find_ent == NULL)
+	{
+		return FALSE;
+	}
 
-	lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + add;
-	ent = xtext->buffer->pagetop_ent;
+	gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height);
 
-	while (ent && line < lines_max)
+	ent = buf->pagetop_ent;
+	/* If top line not completely displayed return FALSE */
+	if (ent == find_ent && buf->pagetop_subline > 0)
 	{
-		if (find_ent == ent)
+		return FALSE;
+	}
+	/* Loop through line positions looking for find_ent */
+	lines = ((height + xtext->pixel_offset) / xtext->fontsize) + buf->pagetop_subline + add;
+	while (ent)	
+	{
+		lines -= ent->lines_taken;
+		if (lines <= 0)
+		{
+			return FALSE;
+		}
+		if (ent == find_ent)
+		{
 			return TRUE;
-		line += ent->lines_taken;
+		}
 		ent = ent->next;
 	}
 
@@ -4958,90 +5206,369 @@ gtk_xtext_check_marker_visibility (GtkXText * xtext)
 		xtext->buffer->marker_seen = TRUE;
 }
 
-textentry *
-gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward)
+static void
+gtk_xtext_unstrip_color (gint start, gint end, GSList *slp, GList **gl, gint maxo)
 {
-	textentry *ent, *fent;
-	int line;
-	gchar *str, *nee, *hay;	/* needle in haystack */
-
-	gtk_xtext_selection_clear_full (xtext->buffer);
-	xtext->buffer->last_ent_start = NULL;
-	xtext->buffer->last_ent_end = NULL;
+	gint off1, off2, curlen;
+	GSList *cursl;
+	offsets_t marks;
 
-	/* set up text comparand for Case Match or Ignore */
-	if (case_match)
-		nee = g_strdup (text);
-	else
-		nee = g_utf8_casefold (text, strlen (text));
+	off1 = 0;
+	curlen = 0;
+	cursl = slp;
+	while (cursl)
+	{
+		offlen_t ol;
+		ol.u = GPOINTER_TO_UINT(cursl->data);
+		if (start < ol.o.len)
+		{
+			off1 = ol.o.off + start;
+			break;
+		}
+		curlen += ol.o.len;
+		start -= ol.o.len;
+		end -= ol.o.len;
+		cursl = g_slist_next (cursl);
+	}
 
-	/* Validate that start gives a currently valid ent pointer */
-	ent = xtext->buffer->text_first;
-	while (ent)
+	off2 = off1;
+	while (cursl)
 	{
-		if (ent == start)
+		offlen_t ol;
+		ol.u = GPOINTER_TO_UINT(cursl->data);
+		if (end < ol.o.len)
+		{
+			off2 = ol.o.off + end;
 			break;
-		ent = ent->next;
+		}
+		curlen += ol.o.len;
+		end -= ol.o.len;
+		cursl = g_slist_next (cursl);
+	}
+	if (!cursl)
+	{
+		off2 = maxo;
 	}
-	if (!ent)
-		start = NULL;
 
-	/* Choose first ent to look at */
-	if (start)
-		ent = backward? start->prev: start->next;
-	else
-		ent = backward? xtext->buffer->text_last: xtext->buffer->text_first;
+	marks.o.start = off1;
+	marks.o.end = off2;
+	*gl = g_list_append (*gl, GUINT_TO_POINTER (marks.u));
+}
 
-	/* Search from there to one end or the other until found */
-	while (ent)
+/* Search a single textentry for occurrence(s) of search arg string */
+static void
+gtk_xtext_search_textentry (xtext_buffer *buf, textentry *ent, int pre)
+{
+	gchar *str;								/* text string to be searched */
+	GList *gl = NULL;
+	GSList *slp;
+	gint lstr;
+
+	if (buf->search_text == NULL)
 	{
-		/* If Case Ignore, fold before & free after calling strstr */
-		if (case_match)
-			hay = g_strdup (ent->str);
-		else
-			hay = g_utf8_casefold (ent->str, strlen (ent->str));
-		/* Try to find the needle in this haystack */
-		str = g_strstr_len (hay, strlen (hay), nee);
+		return;
+	}
+
+	str = gtk_xtext_strip_color (ent->str, ent->str_len, buf->xtext->scratch_buffer,
+										  &lstr, NULL, &slp, !buf->xtext->ignore_hidden);
+
+	/* Regular-expression matching --- */
+	if (buf->search_flags & regexp)
+	{
+		GMatchInfo *gmi;
+		gint start, end;
+
+		if (buf->search_re == NULL)
+		{
+			return;
+		}
+		g_regex_match (buf->search_re, str, 0, &gmi);
+		while (g_match_info_matches (gmi))
+		{
+			g_match_info_fetch_pos (gmi, 0,  &start, &end);
+			gtk_xtext_unstrip_color (start, end, slp, &gl, ent->str_len);
+			g_match_info_next (gmi, NULL);
+		}
+		g_match_info_free (gmi);
+
+	/* Non-regular-expression matching --- */
+	} else {
+		gchar *hay, *pos;
+		gint lhay, off, len;
+		gint match = buf->search_flags & case_match;
+
+		hay = match? g_strdup (str): g_utf8_casefold (str, lstr);
+		lhay = strlen (hay);
+		off = 0;
+
+		for (pos = hay, len = lhay; len;
+			  off += buf->search_lnee, pos = hay + off, len = lhay - off)
+		{
+			str = g_strstr_len (pos, len, buf->search_nee);
+			if (str == NULL)
+			{
+				break;
+			}
+			off = str - hay;
+			gtk_xtext_unstrip_color (off, off + buf->search_lnee,
+											 slp, &gl, ent->str_len);
+		}
+
 		g_free (hay);
-		if (str)
-			break;
-		ent = backward? ent->prev: ent->next;
 	}
-	fent = ent;
 
-	/* Save distance to start, end of found string */
-	if (ent)
+	/* Common processing --- */
+	g_slist_free (slp);
+	ent->marks = gl;
+	if (gl)
+	{
+		buf->search_found = (pre? g_list_prepend: g_list_append) (buf->search_found, ent);
+		if (pre == FALSE && buf->hintsearch == NULL)
+		{
+			buf->hintsearch = ent;
+		}
+	}
+	return;
+}
+
+/* Free all search information for a textentry */
+static void
+gtk_xtext_search_textentry_del (xtext_buffer *buf, textentry *ent)
+{
+	g_list_free (ent->marks);
+	ent->marks = NULL;
+	if (buf->cursearch && buf->cursearch->data == ent)
+	{
+		buf->cursearch = NULL;
+		buf->curmark = NULL;
+	}
+	if (buf->pagetop_ent == ent)
+	{
+		buf->pagetop_ent = NULL;
+	}
+	if (buf->hintsearch == ent)
+	{
+		buf->hintsearch = NULL;
+	}
+	buf->search_found = g_list_remove (buf->search_found, ent);
+}
+
+/* Used only by glist_foreach */
+static void
+gtk_xtext_search_textentry_fini (gpointer entp, gpointer dummy)
+{
+	textentry *ent = entp;
+
+	g_list_free (ent->marks);
+	ent->marks = NULL;
+}
+
+/* Free all search information for all textentrys and the xtext_buffer */
+static void
+gtk_xtext_search_fini (xtext_buffer *buf)
+{
+	g_list_foreach (buf->search_found, gtk_xtext_search_textentry_fini, 0);
+	g_list_free (buf->search_found);
+	buf->search_found = NULL;
+	g_free (buf->search_text);
+	buf->search_text = NULL;
+	g_free (buf->search_nee);
+	buf->search_nee = NULL;
+	buf->search_flags = 0;
+	buf->cursearch = NULL;
+	buf->curmark = NULL;
+	if (buf->search_re)
+	{
+		g_regex_unref (buf->search_re);
+		buf->search_re = NULL;
+	}
+}
+
+/* Returns TRUE if the base search information exists and is still okay to use */
+static gboolean
+gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gtk_xtext_search_flags flags, GError **perr)
+{
+	/* Of the five flags, backward and highlight_all do not need a new search */
+	if (buf->search_found &&
+		 strcmp (buf->search_text, text) == 0 &&
+		 (buf->search_flags & case_match) == (flags & case_match) &&
+		 (buf->search_flags & follow) == (flags & follow) &&
+		 (buf->search_flags & regexp) == (flags & regexp))
+	{
+		return TRUE;
+	}
+	buf->hintsearch = buf->cursearch? buf->cursearch->data: NULL;
+	gtk_xtext_search_fini (buf);
+	buf->search_text = g_strdup (text);
+	if (flags & regexp)
+	{
+		buf->search_re = g_regex_new (text, (flags & case_match)? 0: G_REGEX_CASELESS, 0, perr);
+		if (perr && *perr)
+		{
+			return FALSE;
+		}
+	}
+	else
+	{
+		if (flags & case_match)
+		{
+			buf->search_nee = g_strdup (text);
+		}
+		else
+		{
+			buf->search_nee = g_utf8_casefold (text, strlen (text));
+		}
+		buf->search_lnee = strlen (buf->search_nee);
+	}
+	buf->search_flags = flags;
+	buf->cursearch = NULL;
+	buf->curmark = NULL;
+	return FALSE;
+}
+
+#define BACKWARD (flags & backward)
+#define FIRSTLAST(lp)  (BACKWARD? g_list_last(lp): g_list_first(lp))
+#define NEXTPREVIOUS(lp) (BACKWARD? g_list_previous(lp): g_list_next(lp))
+textentry *
+gtk_xtext_search (GtkXText * xtext, const gchar *text, gtk_xtext_search_flags flags, GError **perr)
+{
+	textentry *ent = NULL;
+	xtext_buffer *buf = xtext->buffer;
+	GList *gl;
+
+	if (buf->text_first == NULL)
+	{
+		return NULL;
+	}
+
+	/* If the text arg is NULL, one of these has been toggled: highlight follow */
+	if (text == NULL)		/* Here on highlight or follow toggle */
 	{
-		ent->mark_start = str - hay;
-		ent->mark_end = ent->mark_start + strlen (nee);
+		gint oldfollow = buf->search_flags & follow;
+		gint newfollow = flags & follow;
 
-		/* is the match visible? Might need to scroll */
-		if (!gtk_xtext_check_ent_visibility (xtext, ent, 0))
+		/* If "Follow" has just been checked, search possible new textentries --- */
+		if (newfollow && (newfollow != oldfollow))
 		{
-			ent = xtext->buffer->text_first;
-			line = 0;
-			while (ent)
+			gl = g_list_last (buf->search_found);
+			ent = gl? gl->data: buf->text_first;
+			for (; ent; ent = ent->next)
 			{
-				line += ent->lines_taken;
-				ent = ent->next;
-				if (ent == fent)
-					break;
+				gtk_xtext_search_textentry (buf, ent, FALSE);
+			}
+		}
+		buf->search_flags = flags;
+		ent = buf->pagetop_ent;
+	}
+
+	/* if the text arg is "", the reset button has been clicked or Control-Shift-F has been hit */
+	else if (text[0] == 0)		/* Let a null string do a reset. */
+	{
+		gtk_xtext_search_fini (buf);
+	}
+
+	/* If the text arg is neither NULL nor "", it's the search string */
+	else
+	{
+		if (gtk_xtext_search_init (buf, text, flags, perr) == FALSE)	/* If a new search: */
+		{
+			if (perr && *perr)
+			{
+				return NULL;
+			}
+			for (ent = buf->text_first; ent; ent = ent->next)
+			{
+				gtk_xtext_search_textentry (buf, ent, TRUE);
+			}
+			buf->search_found = g_list_reverse (buf->search_found);
+		}
+
+		/* Now base search results are in place. */
+
+		if (buf->search_found)
+		{
+			/* If we're in the midst of moving among found items */
+			if (buf->cursearch)
+			{
+				ent = buf->cursearch->data;
+				buf->curmark = NEXTPREVIOUS (buf->curmark);
+				if (buf->curmark == NULL)
+				{
+					/* We've returned all the matches for this textentry. */
+					buf->cursearch = NEXTPREVIOUS (buf->cursearch);
+					if (buf->cursearch)
+					{
+						ent = buf->cursearch->data;
+						buf->curmark = FIRSTLAST (ent->marks);
+					}
+					else	/* We've returned all the matches for all textentries */
+					{
+						ent = NULL;
+					}
+				}
+			}
+#if 0
+			/* If user changed the search, let's look starting where he was */
+			else if (buf->hintsearch)
+			{
+				for (ent = buf->hintsearch; ent; ent = BACKWARD? ent->prev: ent->next)
+					if (ent->marks)
+						break;
+				if (ent == NULL)
+					for (ent = buf->hintsearch; ent; ent = BACKWARD? ent->next: ent->prev)
+						if (ent->marks)
+							break;
+				if (ent)
+				{
+					buf->cursearch = g_list_find (buf->search_found, ent);
+					buf->curmark = FIRSTLAST (ent->marks);
+				}
+			}
+#endif
+			/* This is a fresh search */
+			else
+			{
+				buf->cursearch = FIRSTLAST (buf->search_found);
+				ent = buf->cursearch->data;
+				buf->curmark = FIRSTLAST (ent->marks);
+			}
+		}
+	}
+	buf->hintsearch = ent;
+
+	if (!gtk_xtext_check_ent_visibility (xtext, ent, 1))
+	{
+		GtkAdjustment *adj = xtext->adj;
+		float value;
+
+		buf->pagetop_ent = NULL;
+		for (value = 0, ent = buf->text_first;
+			  ent && ent != buf->hintsearch; ent = ent->next)
+		{
+			value += ent->lines_taken;
+		}
+		if (value > adj->upper - adj->page_size)
+		{
+			value = adj->upper - adj->page_size;
+		}
+		else if ((flags & backward)  && ent)
+		{
+			value -= adj->page_size - ent->lines_taken;
+			if (value < 0)
+			{
+				value = 0;
 			}
-			while (line > xtext->adj->upper - xtext->adj->page_size)
-				line--;
-			if (backward)
-				line -= xtext->adj->page_size - ent->lines_taken;
-			xtext->adj->value = line;
-			xtext->buffer->scrollbar_down = FALSE;
-			gtk_adjustment_changed (xtext->adj);
 		}
+		gtk_adjustment_set_value (adj, value);
 	}
 
-	g_free (nee);
 	gtk_widget_queue_draw (GTK_WIDGET (xtext));
 
-	return fent;
+	return buf->hintsearch;
 }
+#undef BACKWARD
+#undef FIRSTLAST
+#undef NEXTPREVIOUS
 
 static int
 gtk_xtext_render_page_timeout (GtkXText * xtext)
@@ -5104,6 +5631,7 @@ gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp)
 	ent->mark_start = -1;
 	ent->mark_end = -1;
 	ent->next = NULL;
+	ent->marks = NULL;
 
 	if (ent->indent < MARGIN)
 		ent->indent = MARGIN;	  /* 2 pixels is the left margin */
@@ -5122,10 +5650,11 @@ gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp)
 	if (buf->reset_marker_pos || 
 		((buf->marker_pos == NULL || buf->marker_seen) && (buf->xtext->buffer != buf || 
 #if GTK_CHECK_VERSION(2,4,0)
-		!gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext)))))))
+		!gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))
 #else
-		!(GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))->has_focus)))
+		!(GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))->has_focus
 #endif
+	   )))
 	{
 		buf->marker_pos = ent;
 		dontscroll (buf); /* force scrolling off */
@@ -5165,6 +5694,10 @@ gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp)
 		if (buf->old_value < 0)
 			buf->old_value = 0;
 	}
+	if (buf->search_flags & follow)
+	{
+		gtk_xtext_search_textentry (buf, ent, FALSE);
+	}
 }
 
 /* the main two public functions */
@@ -5491,6 +6024,11 @@ gtk_xtext_buffer_free (xtext_buffer *buf)
 	if (buf->xtext->selection_buffer == buf)
 		buf->xtext->selection_buffer = NULL;
 
+	if (buf->search_found)
+	{
+		gtk_xtext_search_fini (buf);
+	}
+
 	ent = buf->text_first;
 	while (ent)
 	{
diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h
index 90fa1bca..6c126346 100644
--- a/src/fe-gtk/xtext.h
+++ b/src/fe-gtk/xtext.h
@@ -43,6 +43,13 @@
 typedef struct _GtkXText GtkXText;
 typedef struct _GtkXTextClass GtkXTextClass;
 typedef struct textentry textentry;
+typedef enum gtk_xtext_search_flags_e {
+	case_match = 1,
+	backward = 2,
+	highlight = 4,
+	follow = 8,
+	regexp = 16
+} gtk_xtext_search_flags;
 
 typedef struct {
 	GtkXText *xtext;					/* attached to this widget */
@@ -77,6 +84,16 @@ typedef struct {
 	unsigned int grid_dirty:1;
 	unsigned int marker_seen:1;
 	unsigned int reset_marker_pos:1;
+
+	GList *search_found;		/* list of textentries where search found strings */
+	gchar *search_text;		/* desired text to search for */
+	gchar *search_nee;		/* prepared needle to look in haystack for */
+	gint search_lnee;		/* its length */
+	gtk_xtext_search_flags search_flags;	/* match, bwd, highlight */
+	GList *cursearch;			/* GList whose 'data' pts to current textentry */
+	GList *curmark;			/* current item in ent->marks */
+	GRegex *search_re;		/* Compiled regular expression */
+	textentry *hintsearch;	/* textentry found for last search */
 } xtext_buffer;
 
 struct _GtkXText
@@ -247,7 +264,7 @@ void gtk_xtext_clear (xtext_buffer *buf, int lines);
 void gtk_xtext_save (GtkXText * xtext, int fh);
 void gtk_xtext_refresh (GtkXText * xtext, int do_trans);
 int gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, int (*cmp_func) (char *, void *userdata), void *userdata);
-textentry *gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward);
+textentry *gtk_xtext_search (GtkXText * xtext, const gchar *text, gtk_xtext_search_flags flags, GError **err);
 void gtk_xtext_reset_marker_pos (GtkXText *xtext);
 void gtk_xtext_check_marker_visibility(GtkXText *xtext);