Index: uspace/app/edit/Makefile
===================================================================
--- uspace/app/edit/Makefile	(revision 20dccf3f4f41fb6f49fd3071c60e29e100cdae1d)
+++ uspace/app/edit/Makefile	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
@@ -33,5 +33,6 @@
 SOURCES = \
 	edit.c \
-	sheet.c
+	sheet.c \
+	search.c
 
 include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/edit/edit.c
===================================================================
--- uspace/app/edit/edit.c	(revision 20dccf3f4f41fb6f49fd3071c60e29e100cdae1d)
+++ uspace/app/edit/edit.c	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2009 Jiri Svoboda
+ * Copyright (c) 2012 Martin Sucha
  * All rights reserved.
  *
@@ -49,4 +50,5 @@
 
 #include "sheet.h"
+#include "search.h"
 
 enum redraw_flags {
@@ -83,4 +85,6 @@
 	 */
 	int ideal_column;
+	
+	char *previous_search;
 } pane_t;
 
@@ -140,8 +144,9 @@
 static void delete_char_after(void);
 static void caret_update(void);
-static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
-static void caret_move_word_left(void);
-static void caret_move_word_right(void);
-static void caret_move_to_line(int row);
+static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir, bool select);
+static void caret_move_absolute(int row, int column, enum dir_spec align_dir, bool select);
+static void caret_move(spt_t spt, bool select, bool update_ideal_column);
+static void caret_move_word_left(bool select);
+static void caret_move_word_right(bool select);
 static void caret_go_to_line_ask(void);
 
@@ -149,10 +154,12 @@
 static void selection_sel_all(void);
 static void selection_sel_range(spt_t pa, spt_t pb);
-static void selection_sel_prev_word(void);
-static void selection_sel_next_word(void);
 static void selection_get_points(spt_t *pa, spt_t *pb);
 static void selection_delete(void);
 static void selection_copy(void);
 static void insert_clipboard_data(void);
+
+static void search(void);
+static void search_forward(char *pattern);
+static void search_repeat(void);
 
 static void pt_get_sof(spt_t *pt);
@@ -163,4 +170,7 @@
 static bool pt_is_delimiter(spt_t *pt);
 static bool pt_is_punctuation(spt_t *pt);
+static spt_t pt_find_word_left(spt_t spt);
+static spt_t pt_find_word_left(spt_t spt);
+
 static int tag_cmp(tag_t const *a, tag_t const *b);
 static int spt_cmp(spt_t const *a, spt_t const *b);
@@ -173,9 +183,6 @@
 {
 	kbd_event_t ev;
-	coord_t coord;
 	bool new_file;
 	int rc;
-
-	spt_t pt;
 
 	con = console_init(stdin, stdout);
@@ -197,8 +204,8 @@
 
 	/* Place caret at the beginning of file. */
-	coord.row = coord.column = 1;
-	sheet_get_cell_pt(doc.sh, &coord, dir_before, &pt);
-	sheet_place_tag(doc.sh, &pt, &pane.caret_pos);
-	pane.ideal_column = coord.column;
+	spt_t sof;
+	pt_get_sof(&sof);
+	sheet_place_tag(doc.sh, &sof, &pane.caret_pos);
+	pane.ideal_column = 1;
 
 	if (argc == 2) {
@@ -216,10 +223,10 @@
 		new_file = true;
 
+	/* Place selection start tag. */
+	sheet_place_tag(doc.sh, &sof, &pane.sel_start);
+
 	/* Move to beginning of file. */
-	caret_move(-ED_INFTY, -ED_INFTY, dir_before);
-
-	/* Place selection start tag. */
-	tag_get_pt(&pane.caret_pos, &pt);
-	sheet_place_tag(doc.sh, &pt, &pane.sel_start);
+	pt_get_sof(&sof);
+	caret_move(sof, true, true);
 
 	/* Initial display */
@@ -400,19 +407,19 @@
 		selection_sel_all();
 		break;
-	case KC_W:
-		if (selection_active())
-			break;
-		selection_sel_prev_word();
-		selection_delete();
-		break;
 	case KC_RIGHT:
-		caret_move_word_right();
+		caret_move_word_right(false);
 		break;
 	case KC_LEFT:
-		caret_move_word_left();
+		caret_move_word_left(false);
 		break;
 	case KC_L:
 		caret_go_to_line_ask();
 		break;
+	case KC_F:
+		search();
+		break;
+	case KC_N:
+		search_repeat();
+		break;
 	default:
 		break;
@@ -424,8 +431,8 @@
 	switch(ev->key) {
 	case KC_LEFT:
-		selection_sel_prev_word();
+		caret_move_word_left(true);
 		break;
 	case KC_RIGHT:
-		selection_sel_next_word();
+		caret_move_word_right(true);
 		break;
 	default:
@@ -435,30 +442,28 @@
 
 /** Move caret while preserving or resetting selection. */
-static void caret_movement(int drow, int dcolumn, enum dir_spec align_dir,
-    bool select)
-{
-	spt_t pt;
-	spt_t caret_pt;
+static void caret_move(spt_t new_caret_pt, bool select, bool update_ideal_column)
+{
+	spt_t old_caret_pt, old_sel_pt;
 	coord_t c_old, c_new;
 	bool had_sel;
 
 	/* Check if we had selection before. */
-	tag_get_pt(&pane.caret_pos, &caret_pt);
-	tag_get_pt(&pane.sel_start, &pt);
-	had_sel = !spt_equal(&caret_pt, &pt);
-
-	caret_move(drow, dcolumn, align_dir);
+	tag_get_pt(&pane.caret_pos, &old_caret_pt);
+	tag_get_pt(&pane.sel_start, &old_sel_pt);
+	had_sel = !spt_equal(&old_caret_pt, &old_sel_pt);
+
+	/* Place tag of the caret */
+	sheet_remove_tag(doc.sh, &pane.caret_pos);
+	sheet_place_tag(doc.sh, &new_caret_pt, &pane.caret_pos);
 
 	if (select == false) {
 		/* Move sel_start to the same point as caret. */
 		sheet_remove_tag(doc.sh, &pane.sel_start);
-		tag_get_pt(&pane.caret_pos, &pt);
-		sheet_place_tag(doc.sh, &pt, &pane.sel_start);
-	}
-
+		sheet_place_tag(doc.sh, &new_caret_pt, &pane.sel_start);
+	}
+
+	spt_get_coord(&new_caret_pt, &c_new);
 	if (select) {
-		tag_get_pt(&pane.caret_pos, &pt);
-		spt_get_coord(&caret_pt, &c_old);
-		spt_get_coord(&pt, &c_new);
+		spt_get_coord(&old_caret_pt, &c_old);
 
 		if (c_old.row == c_new.row)
@@ -471,4 +476,9 @@
 		pane.rflags |= REDRAW_TEXT;
 	}
+	
+	if (update_ideal_column)
+		pane.ideal_column = c_new.column;
+	
+	caret_update();
 }
 
@@ -477,26 +487,26 @@
 	switch (key) {
 	case KC_LEFT:
-		caret_movement(0, -1, dir_before, select);
+		caret_move_relative(0, -1, dir_before, select);
 		break;
 	case KC_RIGHT:
-		caret_movement(0, 0, dir_after, select);
+		caret_move_relative(0, 0, dir_after, select);
 		break;
 	case KC_UP:
-		caret_movement(-1, 0, dir_before, select);
+		caret_move_relative(-1, 0, dir_before, select);
 		break;
 	case KC_DOWN:
-		caret_movement(+1, 0, dir_before, select);
+		caret_move_relative(+1, 0, dir_before, select);
 		break;
 	case KC_HOME:
-		caret_movement(0, -ED_INFTY, dir_before, select);
+		caret_move_relative(0, -ED_INFTY, dir_after, select);
 		break;
 	case KC_END:
-		caret_movement(0, +ED_INFTY, dir_before, select);
+		caret_move_relative(0, +ED_INFTY, dir_before, select);
 		break;
 	case KC_PAGE_UP:
-		caret_movement(-pane.rows, 0, dir_before, select);
+		caret_move_relative(-pane.rows, 0, dir_before, select);
 		break;
 	case KC_PAGE_DOWN:
-		caret_movement(+pane.rows, 0, dir_before, select);
+		caret_move_relative(+pane.rows, 0, dir_before, select);
 		break;
 	default:
@@ -1012,5 +1022,5 @@
 }
 
-/** Change the caret position.
+/** Relatively move caret position.
  *
  * Moves caret relatively to the current position. Looking at the first
@@ -1018,6 +1028,9 @@
  * to a new character cell, and thus a new character. Then we either go to the
  * point before the the character or after it, depending on @a align_dir.
+ *
+ * @param select true if the selection tag should stay where it is
  */
-static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
+static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir,
+    bool select)
 {
 	spt_t pt;
@@ -1055,59 +1068,62 @@
 	 */
 	sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
-	sheet_remove_tag(doc.sh, &pane.caret_pos);
-	sheet_place_tag(doc.sh, &pt, &pane.caret_pos);
 
 	/* For non-vertical movement set the new value for @c ideal_column. */
-	if (!pure_vertical) {
-		spt_get_coord(&pt, &coord);
-		pane.ideal_column = coord.column;
-	}
-
-	caret_update();
-}
-
-static void caret_move_word_left(void) 
-{
+	caret_move(pt, select, !pure_vertical);
+}
+
+/** Absolutely move caret position.
+ *
+ * Moves caret to a specified position. We get to a new character cell, and
+ * thus a new character. Then we either go to the point before the the character
+ * or after it, depending on @a align_dir.
+ *
+ * @param select true if the selection tag should stay where it is
+ */
+static void caret_move_absolute(int row, int column, enum dir_spec align_dir,
+    bool select)
+{
+	coord_t coord;
+	coord.row = row;
+	coord.column = column;
+	
 	spt_t pt;
-
+	sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
+	
+	caret_move(pt, select, true);
+}
+
+/** Find beginning of a word to the left of spt */
+static spt_t pt_find_word_left(spt_t spt) 
+{
 	do {
-		caret_move(0, -1, dir_before);
-
-		tag_get_pt(&pane.caret_pos, &pt);
-
-		sheet_remove_tag(doc.sh, &pane.sel_start);
-		sheet_place_tag(doc.sh, &pt, &pane.sel_start);
-	} while (!pt_is_word_beginning(&pt));
-
-	pane.rflags |= REDRAW_TEXT;
-}
-
-static void caret_move_word_right(void) 
+		spt_prev_char(spt, &spt);
+	} while (!pt_is_word_beginning(&spt));
+	return spt;
+}
+
+/** Find beginning of a word to the right of spt */
+static spt_t pt_find_word_right(spt_t spt) 
+{
+	do {
+		spt_next_char(spt, &spt);
+	} while (!pt_is_word_beginning(&spt));
+	return spt;
+}
+
+static void caret_move_word_left(bool select) 
 {
 	spt_t pt;
-
-	do {
-		caret_move(0, 0, dir_after);
-
-		tag_get_pt(&pane.caret_pos, &pt);
-
-		sheet_remove_tag(doc.sh, &pane.sel_start);
-		sheet_place_tag(doc.sh, &pt, &pane.sel_start);
-	} while (!pt_is_word_beginning(&pt));
-
-	pane.rflags |= REDRAW_TEXT;
-}
-
-/** Change the caret position to a beginning of a given line
- */
-static void caret_move_to_line(int row)
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_t word_left = pt_find_word_left(pt);
+	caret_move(word_left, select, true);
+}
+
+static void caret_move_word_right(bool select) 
 {
 	spt_t pt;
-	coord_t coord;
-
 	tag_get_pt(&pane.caret_pos, &pt);
-	spt_get_coord(&pt, &coord);
-
-	caret_movement(row - coord.row, 0, dir_before, false);
+	spt_t word_right = pt_find_word_right(pt);
+	caret_move(word_right, select, true);
 }
 
@@ -1126,11 +1142,122 @@
 	int line = strtol(sline, &endptr, 10);
 	if (*endptr != '\0') {
+		free(sline);
 		status_display("Invalid number entered.");
 		return;
 	}
-	
-	caret_move_to_line(line);
-}
-
+	free(sline);
+	
+	caret_move_absolute(line, pane.ideal_column, dir_before, false);
+}
+
+/* Search operations */
+static int search_spt_producer(void *data, wchar_t *ret)
+{
+	assert(data != NULL);
+	assert(ret != NULL);
+	spt_t *spt = data;
+	*ret = spt_next_char(*spt, spt);
+	return EOK;
+}
+
+static int search_spt_mark(void *data, void **mark)
+{
+	assert(data != NULL);
+	assert(mark != NULL);
+	spt_t *spt = data;
+	spt_t *new = calloc(1, sizeof(spt_t));
+	*mark = new;
+	if (new == NULL)
+		return ENOMEM;
+	*new = *spt;
+	return EOK;
+}
+
+static void search_spt_mark_free(void *data)
+{
+	free(data);
+}
+
+static search_ops_t search_spt_ops = {
+	.equals = char_exact_equals,
+	.producer = search_spt_producer,
+	.mark = search_spt_mark,
+	.mark_free = search_spt_mark_free,
+};
+
+/** Ask for line and go to it. */
+static void search(void)
+{
+	char *pattern;
+	
+	const char *default_value = "";
+	if (pane.previous_search)
+		default_value = pane.previous_search;
+	
+	pattern = prompt("Search for", default_value);
+	if (pattern == NULL) {
+		status_display("Search cancelled.");
+		return;
+	}
+	
+	if (pane.previous_search)
+		free(pane.previous_search);
+	pane.previous_search = pattern;
+	
+	search_forward(pattern);
+}
+
+static void search_repeat(void)
+{
+	if (pane.previous_search == NULL) {
+		status_display("No previous search to repeat.");
+		return;
+	}
+	
+	search_forward(pane.previous_search);
+}
+
+static void search_forward(char *pattern)
+{
+	status_display("Searching...");
+	
+	spt_t sp, producer_pos;
+	tag_get_pt(&pane.caret_pos, &sp);
+	
+	/* Start searching on the position after caret */
+	spt_next_char(sp, &sp);
+	producer_pos = sp;
+	
+	search_t *search = search_init(pattern, &producer_pos, search_spt_ops);
+	if (search == NULL) {
+		status_display("Failed initializing search.");
+		return;
+	}
+	
+	match_t match;
+	int rc = search_next_match(search, &match);
+	if (rc != EOK) {
+		status_display("Failed searching.");
+		search_fini(search);
+	}
+	
+	if (match.end) {
+		status_display("Match found.");
+		assert(match.end != NULL);
+		spt_t *end = match.end;
+		caret_move(*end, false, true);
+		while (match.length > 0) {
+			match.length--;
+			spt_prev_char(*end, end);
+		}
+		caret_move(*end, true, true);
+		free(end);
+	}
+	else {
+		status_display("Not found.");
+	}
+	
+	search_fini(search);
+}
 
 /** Check for non-empty selection. */
@@ -1202,38 +1329,4 @@
 	pane.rflags |= REDRAW_TEXT;
 	caret_update();
-}
-
-/** Add the previous word to the selection */
-static void selection_sel_prev_word(void)
-{
-	spt_t cpt, wpt, spt, ept;
-
-	selection_get_points(&spt, &ept);
-
-	tag_get_pt(&pane.caret_pos, &cpt);
-	caret_move_word_left();
-	tag_get_pt(&pane.caret_pos, &wpt);
-
-	if (spt_cmp(&spt, &cpt) == 0)
-		selection_sel_range(ept, wpt);
-	else
-		selection_sel_range(spt, wpt);
-}
-
-/** Add the next word to the selection */
-static void selection_sel_next_word(void)
-{
-	spt_t cpt, wpt, spt, ept;
-
-	selection_get_points(&spt, &ept);
-
-	tag_get_pt(&pane.caret_pos, &cpt);
-	caret_move_word_right();
-	tag_get_pt(&pane.caret_pos, &wpt);
-
-	if (spt_cmp(&ept, &cpt) == 0)
-		selection_sel_range(spt, wpt);
-	else
-		selection_sel_range(ept, wpt);
 }
 
Index: uspace/app/edit/search.c
===================================================================
--- uspace/app/edit/search.c	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
+++ uspace/app/edit/search.c	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2012 Martin Sucha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup edit
+ * @{
+ */
+/**
+ * @file
+ * @brief Simple searching facility.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "search.h"
+#include "search_impl.h"
+
+search_t *search_init(const char *pattern, void *client_data, search_ops_t ops)
+{
+	search_t *search = calloc(1, sizeof(search_t));
+	if (search == NULL)
+		return NULL;
+	
+	search->pattern = str_to_awstr(pattern);
+	if (search->pattern == NULL) {
+		free(search);
+		return NULL;
+	}
+	
+	search->client_data = client_data;
+	search->ops = ops;
+	search->pattern_length = wstr_length(search->pattern);
+	search->back_table = calloc(search->pattern_length, sizeof(ssize_t));
+	if (search->back_table == NULL) {
+		free(search->pattern);
+		free(search);
+		return NULL;
+	}
+	
+	search->pattern_pos = 0;
+	
+	search->back_table[0] = -1;
+	search->back_table[1] = 0;
+	size_t table_idx = 2;
+	size_t pattern_idx = 0;
+	while (table_idx < search->pattern_length) {
+		if (ops.equals(search->pattern[table_idx - 1],
+		    search->pattern[pattern_idx])) {
+			pattern_idx++;
+			search->back_table[table_idx] = pattern_idx;
+			table_idx++;
+		}
+		else if (pattern_idx > 0) {
+			pattern_idx = search->back_table[pattern_idx];
+		}
+		else {
+			pattern_idx = 0;
+			table_idx++;
+		}
+	}
+	
+	return search;
+}
+
+int search_next_match(search_t *s, match_t *match)
+{
+	search_equals_fn eq = s->ops.equals;
+	
+	wchar_t cur_char;
+	int rc = EOK;
+	while ((rc = s->ops.producer(s->client_data, &cur_char)) == EOK && cur_char > 0) {
+		/* Deal with mismatches */
+		while (s->pattern_pos > 0 && !eq(cur_char, s->pattern[s->pattern_pos])) {
+			s->pattern_pos = s->back_table[s->pattern_pos];
+		}
+		/* Check if the character matched */
+		if (eq(cur_char, s->pattern[s->pattern_pos])) {
+			s->pattern_pos++;
+			if (s->pattern_pos == s->pattern_length) {
+				s->pattern_pos = s->back_table[s->pattern_pos];
+				rc = s->ops.mark(s->client_data, &match->end);
+				if (rc != EOK)
+					return rc;
+				match->length = s->pattern_length;
+				return EOK;
+			}
+		}
+	}
+	
+	match->end = NULL;
+	match->length = 0;
+	
+	return rc;
+}
+
+void search_fini(search_t *search)
+{
+	free(search->pattern);
+	free(search->back_table);
+	
+}
+
+bool char_exact_equals(const wchar_t a, const wchar_t b)
+{
+	return a == b;
+}
+
+/** @}
+ */
Index: uspace/app/edit/search.h
===================================================================
--- uspace/app/edit/search.h	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
+++ uspace/app/edit/search.h	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2012 Martin Sucha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup edit
+ * @{
+ */
+/**
+ * @file
+ */
+
+#ifndef SEARCH_H__
+#define SEARCH_H__
+
+#include <str.h>
+
+struct search;
+typedef struct search search_t;
+typedef bool (*search_equals_fn)(const wchar_t, const wchar_t);
+typedef int (*search_producer_fn)(void *, wchar_t *);
+typedef int (*search_mark_fn)(void *, void **);
+typedef void (*search_mark_free_fn)(void *);
+
+typedef struct match {
+	aoff64_t length;
+	void *end;
+} match_t;
+
+typedef struct search_ops {
+	search_equals_fn equals;
+	search_producer_fn producer;
+	search_mark_fn mark;
+	search_mark_free_fn mark_free;
+} search_ops_t;
+
+extern bool char_exact_equals(const wchar_t, const wchar_t);
+extern search_t *search_init(const char *, void *, search_ops_t);
+extern int search_next_match(search_t *, match_t *);
+extern void search_fini(search_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/edit/search_impl.h
===================================================================
--- uspace/app/edit/search_impl.h	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
+++ uspace/app/edit/search_impl.h	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012 Martin Sucha
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup edit
+ * @{
+ */
+/**
+ * @file
+ */
+
+#ifndef SEARCH_IMPL_H__
+#define SEARCH_IMPL_H__
+
+#include "search.h"
+
+/** Search state */
+typedef struct search {
+	/* Note: This structure is opaque for the user. */
+
+	const wchar_t *pattern;
+	size_t pattern_length;
+	ssize_t *back_table;
+	size_t pattern_pos;
+	void *client_data;
+	search_ops_t ops;
+} search_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/edit/sheet.c
===================================================================
--- uspace/app/edit/sheet.c	(revision 20dccf3f4f41fb6f49fd3071c60e29e100cdae1d)
+++ uspace/app/edit/sheet.c	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
@@ -323,4 +323,21 @@
 }
 
+/** Get a character at spt and return next spt */
+wchar_t spt_next_char(spt_t spt, spt_t *next)
+{
+	wchar_t ch = str_decode(spt.sh->data, &spt.b_off, spt.sh->text_size);
+	if (next)
+		*next = spt;
+	return ch;
+}
+
+wchar_t spt_prev_char(spt_t spt, spt_t *prev)
+{
+	wchar_t ch = str_decode_reverse(spt.sh->data, &spt.b_off, spt.sh->text_size);
+	if (prev)
+		*prev = spt;
+	return ch;
+}
+
 /** Place a tag on the specified s-point. */
 void sheet_place_tag(sheet_t *sh, spt_t const *pt, tag_t *tag)
Index: uspace/app/edit/sheet.h
===================================================================
--- uspace/app/edit/sheet.h	(revision 20dccf3f4f41fb6f49fd3071c60e29e100cdae1d)
+++ uspace/app/edit/sheet.h	(revision 7feb86e6ab857c89a05076ceedd5635972e99f0a)
@@ -101,4 +101,6 @@
 extern void spt_get_coord(spt_t const *, coord_t *);
 extern bool spt_equal(spt_t const *, spt_t const *);
+extern wchar_t spt_next_char(spt_t, spt_t *);
+extern wchar_t spt_prev_char(spt_t, spt_t *);
 
 extern void sheet_place_tag(sheet_t *, spt_t const *, tag_t *);
