Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision 309ede113b6ed9f76d3b2f6cd08386f96836a161)
+++ uspace/Makefile	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
@@ -53,4 +53,5 @@
 	srv/devmap \
 	srv/part/mbr_part \
+	app/edit \
 	app/tetris \
 	app/tester \
Index: uspace/app/edit/Makefile
===================================================================
--- uspace/app/edit/Makefile	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
+++ uspace/app/edit/Makefile	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
@@ -0,0 +1,77 @@
+#
+# Copyright (c) 2005 Martin Decky
+# 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.
+#
+
+## Setup toolchain
+#
+
+LIBC_PREFIX = ../../lib/libc
+SOFTINT_PREFIX = ../../lib/softint
+
+include $(LIBC_PREFIX)/Makefile.toolchain
+
+CFLAGS += -I../../..
+LIBS = $(LIBC_PREFIX)/libc.a
+
+## Sources
+#
+
+OUTPUT = edit
+SOURCES = \
+	edit.c \
+	sheet.c
+
+OBJECTS := $(addsuffix .o,$(basename $(SOURCES)))
+
+.PHONY: all clean depend disasm
+
+all: $(OUTPUT) $(OUTPUT).disasm
+
+-include Makefile.depend
+
+clean:
+	-rm -f $(OUTPUT) $(OUTPUT).map $(OUTPUT).disasm Makefile.depend $(OBJECTS)
+
+depend:
+	$(CC) $(DEFS) $(CFLAGS) -M $(SOURCES) > Makefile.depend
+
+$(OUTPUT): $(OBJECTS) $(LIBS)
+	$(LD) -T $(LIBC_PREFIX)/arch/$(UARCH)/_link.ld $(OBJECTS) $(LIBS) $(LFLAGS) -o $@ -Map $(OUTPUT).map
+
+disasm: $(OUTPUT).disasm
+
+$(OUTPUT).disasm: $(OUTPUT)
+	$(OBJDUMP) -d $< > $@
+
+%.o: %.S
+	$(CC) $(DEFS) $(AFLAGS) $(CFLAGS) -D__ASM__ -c $< -o $@
+
+%.o: %.s
+	$(AS) $(AFLAGS) $< -o $@
+
+%.o: %.c
+	$(CC) $(DEFS) $(CFLAGS) -c $< -o $@
Index: uspace/app/edit/edit.c
===================================================================
--- uspace/app/edit/edit.c	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
+++ uspace/app/edit/edit.c	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
@@ -0,0 +1,645 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * 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
+ * @brief Text editor.
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <vfs/vfs.h>
+#include <io/console.h>
+#include <io/color.h>
+#include <io/keycode.h>
+#include <errno.h>
+#include <align.h>
+#include <macros.h>
+#include <bool.h>
+
+#include "sheet.h"
+
+enum redraw_flags {
+	REDRAW_TEXT	= (1 << 0),
+	REDRAW_ROW	= (1 << 1),
+	REDRAW_STATUS	= (1 << 2),
+	REDRAW_CARET	= (1 << 3)
+};
+
+/** Pane
+ *
+ * A rectangular area of the screen used to edit a document. Different
+ * panes can be possibly used to edit the same document.
+ */
+typedef struct {
+	/* Pane dimensions */
+	int rows, columns;
+
+	/* Position of the visible area */
+	int sh_row, sh_column;
+
+	/** Bitmask of components that need redrawing */
+	enum redraw_flags rflags;
+
+	/** Current position of the caret */
+	tag_t caret_pos;
+} pane_t;
+
+/** Document
+ *
+ * Associates a sheet with a file where it can be saved to.
+ */
+typedef struct {
+	char *file_name;
+	sheet_t sh;
+} doc_t;
+
+static int con;
+static doc_t doc;
+static bool done;
+static pane_t pane;
+
+static ipcarg_t scr_rows, scr_columns;
+
+#define ROW_BUF_SIZE 4096
+#define BUF_SIZE 64
+#define TAB_WIDTH 8
+#define ED_INFTY 65536
+
+static void key_handle_unmod(console_event_t const *ev);
+static void key_handle_ctrl(console_event_t const *ev);
+static int file_save(char const *fname);
+static int file_insert(char *fname);
+static int file_save_range(char const *fname, spt_t const *spos,
+    spt_t const *epos);
+static void pane_text_display(void);
+static void pane_row_display(void);
+static void pane_row_range_display(int r0, int r1);
+static void pane_status_display(void);
+static void pane_caret_display(void);
+static void insert_char(wchar_t c);
+static void delete_char_before(void);
+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 pt_get_sof(spt_t *pt);
+static void pt_get_eof(spt_t *pt);
+static void status_display(char const *str);
+
+
+int main(int argc, char *argv[])
+{
+	console_event_t ev;
+	coord_t coord;
+	bool new_file;
+
+	spt_t pt;
+
+	con = fphone(stdout);
+	console_clear(con);
+
+	console_get_size(con, &scr_columns, &scr_rows);
+
+	pane.rows = scr_rows - 1;
+	pane.sh_row = 1;
+
+	/* Start with an empty sheet. */
+	sheet_init(&doc.sh);
+
+	/* 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);
+
+	if (argc == 2) {
+		doc.file_name = argv[1];
+	} else if (argc > 1) {
+		printf("Invalid arguments.\n");
+		return -2;
+	} else {
+		doc.file_name = "/edit.txt";
+	}
+
+	new_file = false;
+
+	if (file_insert(doc.file_name) != EOK)
+		new_file = true;
+
+	/* Move to beginning of file. */
+	caret_move(-ED_INFTY, -ED_INFTY, dir_before);
+
+	/* Initial display */
+	console_clear(con);
+	pane_text_display();
+	pane_status_display();
+	if (new_file)
+		status_display("File not found. Created empty file.");
+	pane_caret_display();
+
+
+	done = false;
+
+	while (!done) {
+		console_get_event(con, &ev);
+		pane.rflags = 0;
+
+		if (ev.type == KEY_PRESS) {
+			/* Handle key press. */
+			if (((ev.mods & KM_ALT) == 0) &&
+			     (ev.mods & KM_CTRL) != 0) {
+				key_handle_ctrl(&ev);
+			} else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
+				key_handle_unmod(&ev);
+			}
+		}
+
+		/* Redraw as necessary. */
+
+		if (pane.rflags & REDRAW_TEXT)
+			pane_text_display();
+		if (pane.rflags & REDRAW_ROW)
+			pane_row_display();
+		if (pane.rflags & REDRAW_STATUS)
+			pane_status_display();
+		if (pane.rflags & REDRAW_CARET)
+			pane_caret_display();
+			
+	}
+
+	console_clear(con);
+
+	return 0;
+}
+
+/** Handle key without modifier. */
+static void key_handle_unmod(console_event_t const *ev)
+{
+	switch (ev->key) {
+	case KC_ENTER:
+		insert_char('\n');
+		pane.rflags |= REDRAW_TEXT;
+		caret_update();
+		break;
+	case KC_LEFT:
+		caret_move(0, -1, dir_before);
+		break;
+	case KC_RIGHT:
+		caret_move(0, 0, dir_after);
+		break;
+	case KC_UP:
+		caret_move(-1, 0, dir_before);
+		break;
+	case KC_DOWN:
+		caret_move(+1, 0, dir_before);
+		break;
+	case KC_HOME:
+		caret_move(0, -ED_INFTY, dir_before);
+		break;
+	case KC_END:
+		caret_move(0, +ED_INFTY, dir_before);
+		break;
+	case KC_PAGE_UP:
+		caret_move(-pane.rows, 0, dir_before);
+		break;
+	case KC_PAGE_DOWN:
+		caret_move(+pane.rows, 0, dir_before);
+		break;
+	case KC_BACKSPACE:
+		delete_char_before();
+		pane.rflags |= REDRAW_TEXT;
+		caret_update();
+		break;
+	case KC_DELETE:
+		delete_char_after();
+		pane.rflags |= REDRAW_TEXT;
+		caret_update();
+		break;
+	default:
+		if (ev->c >= 32 || ev->c == '\t') {
+			insert_char(ev->c);
+			pane.rflags |= REDRAW_ROW;
+			caret_update();
+		}
+		break;
+	}
+}
+
+/** Handle Ctrl-key combination. */
+static void key_handle_ctrl(console_event_t const *ev)
+{
+	switch (ev->key) {
+	case KC_Q:
+		done = true;
+		break;
+	case KC_S:
+		(void) file_save(doc.file_name);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/** Save the document. */
+static int file_save(char const *fname)
+{
+	spt_t sp, ep;
+	int rc;
+
+	status_display("Saving...");
+	pt_get_sof(&sp);
+	pt_get_eof(&ep);
+
+	rc = file_save_range(fname, &sp, &ep);
+	status_display("File saved.");
+
+	return rc;
+}
+
+/** Insert file at caret position.
+ *
+ * Reads in the contents of a file and inserts them at the current position
+ * of the caret.
+ */
+static int file_insert(char *fname)
+{
+	FILE *f;
+	wchar_t c;
+	char buf[BUF_SIZE];
+	int bcnt;
+	int n_read;
+	size_t off;
+
+	f = fopen(fname, "rt");
+	if (f == NULL)
+		return EINVAL;
+
+	bcnt = 0;
+
+	while (true) {
+		if (bcnt < STR_BOUNDS(1)) {
+			n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
+			bcnt += n_read;
+		}
+
+		off = 0;
+		c = str_decode(buf, &off, bcnt);
+		if (c == '\0')
+			break;
+
+		bcnt -= off;
+		memcpy(buf, buf + off, bcnt);
+
+		insert_char(c);
+	}
+
+	fclose(f);
+
+	return EOK;
+}
+
+/** Save a range of text into a file. */
+static int file_save_range(char const *fname, spt_t const *spos,
+    spt_t const *epos)
+{
+	FILE *f;
+	char buf[BUF_SIZE];
+	spt_t sp, bep;
+	size_t bytes, n_written;
+
+	f = fopen(fname, "wt");
+	if (f == NULL)
+		return EINVAL;
+
+	sp = *spos;
+
+	do {
+		sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
+		bytes = str_size(buf);
+
+		n_written = fwrite(buf, 1, bytes, f);
+		if (n_written != bytes) {
+			return EIO;
+		}
+
+		sp = bep;
+	} while (!spt_equal(&bep, epos));
+
+	fclose(f);
+
+	return EOK;
+}
+
+static void pane_text_display(void)
+{
+	int sh_rows, rows;
+	int i;
+	unsigned j;
+
+	sheet_get_num_rows(&doc.sh, &sh_rows);
+	rows = min(sh_rows - pane.sh_row + 1, pane.rows);
+
+	/* Draw rows from the sheet. */
+
+	console_goto(con, 0, 0);
+	pane_row_range_display(0, rows);
+
+	/* Clear the remaining rows if file is short. */
+
+	for (i = rows; i < pane.rows; ++i) {
+		console_goto(con, 0, i);
+		for (j = 0; j < scr_columns; ++j)
+			putchar(' ');
+		fflush(stdout);
+	}
+
+	pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
+	pane.rflags &= ~REDRAW_ROW;
+}
+
+/** Display just the row where the caret is. */
+static void pane_row_display(void)
+{
+	spt_t caret_pt;
+	coord_t coord;
+	int ridx;
+
+	tag_get_pt(&pane.caret_pos, &caret_pt);
+	spt_get_coord(&caret_pt, &coord);
+
+	ridx = coord.row - pane.sh_row;
+	pane_row_range_display(ridx, ridx + 1);
+	pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
+}
+
+static void pane_row_range_display(int r0, int r1)
+{
+	int width;
+	int i, j, fill;
+	spt_t rb, re, dep;
+	coord_t rbc, rec;
+	char row_buf[ROW_BUF_SIZE];
+	wchar_t c;
+	size_t pos, size;
+	unsigned s_column;
+
+	/* Draw rows from the sheet. */
+
+	console_goto(con, 0, 0);
+	for (i = r0; i < r1; ++i) {
+		sheet_get_row_width(&doc.sh, pane.sh_row + i, &width);
+
+		/* Determine row starting point. */
+		rbc.row = pane.sh_row + i; rbc.column = 1;
+		sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
+
+		/* Determine row ending point. */
+		rec.row = pane.sh_row + i; rec.column = width + 1;
+		sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
+
+		/* Copy the text of the row to the buffer. */
+		sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
+
+		/* Display text from the buffer. */
+
+		console_goto(con, 0, i);
+		size = str_size(row_buf);
+		pos = 0;
+		s_column = 1;
+		while (pos < size) {
+			c = str_decode(row_buf, &pos, size);
+			if (c != '\t') {
+				printf("%lc", c);
+				s_column += 1;
+			} else {
+				fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
+				    - s_column;
+
+				for (j = 0; j < fill; ++j)
+					putchar(' ');
+				s_column += fill;
+			}
+		}
+
+		/* Fill until the end of display area. */
+
+		if (str_length(row_buf) < scr_columns)
+			fill = scr_columns - str_length(row_buf);
+		else
+			fill = 0;
+
+		for (j = 0; j < fill; ++j)
+			putchar(' ');
+		fflush(stdout);
+	}
+
+	pane.rflags |= REDRAW_CARET;
+}
+
+/** Display pane status in the status line. */
+static void pane_status_display(void)
+{
+	spt_t caret_pt;
+	coord_t coord;
+	int n;
+
+	tag_get_pt(&pane.caret_pos, &caret_pt);
+	spt_get_coord(&caret_pt, &coord);
+
+	console_goto(con, 0, scr_rows - 1);
+	console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
+	n = printf(" %d, %d: File '%s'. Ctrl-S Save  Ctrl-Q Quit",
+	    coord.row, coord.column, doc.file_name);
+	printf("%*s", scr_columns - 1 - n, "");
+	fflush(stdout);
+	console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
+
+	pane.rflags |= REDRAW_CARET;
+}
+
+/** Set cursor to reflect position of the caret. */
+static void pane_caret_display(void)
+{
+	spt_t caret_pt;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &caret_pt);
+
+	spt_get_coord(&caret_pt, &coord);
+	console_goto(con, coord.column - 1, coord.row - pane.sh_row);
+}
+
+/** Insert a character at caret position. */
+static void insert_char(wchar_t c)
+{
+	spt_t pt;
+	char cbuf[STR_BOUNDS(1) + 1];
+	size_t offs;
+
+	tag_get_pt(&pane.caret_pos, &pt);
+
+	offs = 0;
+	chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
+	cbuf[offs] = '\0';
+
+	(void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
+}
+
+/** Delete the character before the caret. */
+static void delete_char_before(void)
+{
+	spt_t sp, ep;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &ep);
+	spt_get_coord(&ep, &coord);
+
+	coord.column -= 1;
+	sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
+
+	(void) sheet_delete(&doc.sh, &sp, &ep);
+}
+
+/** Delete the character after the caret. */
+static void delete_char_after(void)
+{
+	spt_t sp, ep;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &sp);
+	spt_get_coord(&sp, &coord);
+
+	sheet_get_cell_pt(&doc.sh, &coord, dir_after, &ep);
+
+	(void) sheet_delete(&doc.sh, &sp, &ep);
+}
+
+/** Scroll pane after caret has moved.
+ *
+ * After modifying the position of the caret, this is called to scroll
+ * the pane to ensure that the caret is in the visible area.
+ */
+static void caret_update(void)
+{
+	spt_t pt;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_get_coord(&pt, &coord);
+
+	/* Scroll pane as necessary. */
+
+	if (coord.row < pane.sh_row) {
+		pane.sh_row = coord.row;
+		pane.rflags |= REDRAW_TEXT;
+	}
+	if (coord.row > pane.sh_row + pane.rows - 1) {
+		pane.sh_row = coord.row - pane.rows + 1;
+		pane.rflags |= REDRAW_TEXT;
+	}
+
+	pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
+
+}
+
+/** Change the caret position.
+ *
+ * Moves caret relatively to the current position. Looking at the first
+ * character cell after the caret and moving by @a drow and @a dcolumn, 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.
+ */
+static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
+{
+	spt_t pt;
+	coord_t coord;
+	int num_rows;
+
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_get_coord(&pt, &coord);
+	coord.row += drow; coord.column += dcolumn;
+
+	/* Clamp coordinates. */
+	if (drow < 0 && coord.row < 1) coord.row = 1;
+	if (dcolumn < 0 && coord.column < 1) coord.column = 1;
+	if (drow > 0) {
+		sheet_get_num_rows(&doc.sh, &num_rows);
+		if (coord.row > num_rows) coord.row = num_rows;
+	}
+
+	/*
+	 * Select the point before or after the character at the designated
+	 * coordinates. The character can be wider than one cell (e.g. tab).
+	 */
+	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);
+
+	caret_update();
+}
+
+
+/** Get start-of-file s-point. */
+static void pt_get_sof(spt_t *pt)
+{
+	coord_t coord;
+
+	coord.row = coord.column = 1;
+	sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
+}
+
+/** Get end-of-file s-point. */
+static void pt_get_eof(spt_t *pt)
+{
+	coord_t coord;
+	int num_rows;
+
+	sheet_get_num_rows(&doc.sh, &num_rows);
+	coord.row = num_rows;
+	coord.column = 1;
+
+	sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
+}
+
+/** Display text in the status line. */
+static void status_display(char const *str)
+{
+	console_goto(con, 0, scr_rows - 1);
+	console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
+	printf(" %*s ", -(scr_columns - 3), str);
+	fflush(stdout);
+	console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
+
+	pane.rflags |= REDRAW_CARET;
+}
+
+/** @}
+ */
Index: uspace/app/edit/sheet.c
===================================================================
--- uspace/app/edit/sheet.c	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
+++ uspace/app/edit/sheet.c	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * 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 Prototype implementation of Sheet data structure.
+ *
+ * The sheet is an abstract data structure representing a piece of text.
+ * On top of this data structure we can implement a text editor. It is
+ * possible to implement the sheet such that the editor can make small
+ * changes to large files or files containing long lines efficiently.
+ *
+ * The sheet structure allows basic operations of text insertion, deletion,
+ * retrieval and mapping of coordinates to position in the file and vice
+ * versa. The text that is inserted or deleted can contain tabs and newlines
+ * which are interpreted and properly acted upon.
+ *
+ * This is a trivial implementation with poor efficiency with O(N+n)
+ * insertion and deletion and O(N) mapping (in both directions), where
+ * N is the size of the file and n is the size of the inserted/deleted text.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <adt/list.h>
+#include <align.h>
+
+#include "sheet.h"
+
+enum {
+	TAB_WIDTH = 8
+};
+
+/** Initialize an empty sheet. */
+int sheet_init(sheet_t *sh)
+{
+	sh->dbuf_size = 65536;
+	sh->text_size = 0;
+
+	sh->data = malloc(sh->dbuf_size);
+	if (sh->data == NULL)
+		return ENOMEM;
+
+	list_initialize(&sh->tags_head);
+
+	return EOK;
+}
+
+/** Insert text into sheet.
+ *
+ * @param sh	Sheet to insert to.
+ * @param pos	Point where to insert.
+ * @param dir	Whether to insert before or after the point (affects tags).
+ * @param str	The text to insert (printable characters, tabs, newlines).
+ *
+ * @return	EOK on success or negative error code.
+ *
+ * @note	@a dir affects which way tags that were placed on @a pos will
+ * 		move. If @a dir is @c dir_before, the tags will move forward
+ *		and vice versa.
+ */
+int sheet_insert(sheet_t *sh, spt_t *pos, enum dir_spec dir, char *str)
+{
+	char *ipp;
+	size_t sz;
+	link_t *link;
+	tag_t *tag;
+
+	sz = str_size(str);
+	if (sh->text_size + sz > sh->dbuf_size)
+		return ELIMIT;
+
+	ipp = sh->data + pos->b_off;
+
+	memmove(ipp + sz, ipp, sh->text_size - pos->b_off);
+	memcpy(ipp, str, sz);
+	sh->text_size += sz;
+
+	/* Adjust tags */
+
+	link = sh->tags_head.next;
+	while (link != &sh->tags_head) {
+		tag = list_get_instance(link, tag_t, link);
+
+		if (tag->b_off > pos->b_off)
+			tag->b_off += sz;
+		else if (tag->b_off == pos->b_off && dir == dir_before)
+			tag->b_off += sz;
+
+		link = link->next;
+	}
+
+	return EOK;
+}
+
+/** Delete text from sheet.
+ *
+ * Deletes the range of text between two points from the sheet.
+ *
+ * @param sh	Sheet to delete from.
+ * @param spos	Starting point.
+ * @param epos	Ending point.
+ *
+ * @return	EOK on success or negative error code.
+ **/
+int sheet_delete(sheet_t *sh, spt_t *spos, spt_t *epos)
+{
+	char *spp;
+	size_t sz;
+	link_t *link;
+	tag_t *tag;
+
+	spp = sh->data + spos->b_off;
+	sz = epos->b_off - spos->b_off;
+
+	memmove(spp, spp + sz, sh->text_size - (spos->b_off + sz));
+	sh->text_size -= sz;
+
+	/* Adjust tags */
+	link = sh->tags_head.next;
+	while (link != &sh->tags_head) {
+		tag = list_get_instance(link, tag_t, link);
+
+		if (tag->b_off >= epos->b_off)
+			tag->b_off -= sz;
+		else if (tag->b_off >= spos->b_off)
+			tag->b_off = spos->b_off;
+
+		link = link->next;
+	}
+	
+	return EOK;
+}
+
+/** Read text from sheet. */
+void sheet_copy_out(sheet_t *sh, spt_t const *spos, spt_t const *epos,
+    char *buf, size_t bufsize, spt_t *fpos)
+{
+	char *spp;
+	size_t range_sz;
+	size_t copy_sz;
+	size_t off, prev;
+	wchar_t c;
+
+	spp = sh->data + spos->b_off;
+	range_sz = epos->b_off - spos->b_off;
+	copy_sz = (range_sz < bufsize - 1) ? range_sz : bufsize - 1;
+
+	prev = off = 0;
+	do {
+		prev = off;
+		c = str_decode(spp, &off, copy_sz);
+	} while (c != '\0');
+
+	/* Crop copy_sz down to the last full character. */
+	copy_sz = prev;
+
+	memcpy(buf, spp, copy_sz);
+	buf[copy_sz] = '\0';
+
+	fpos->b_off = spos->b_off + copy_sz;
+	fpos->sh = sh;
+}
+
+/** Get point preceding or following character cell. */
+void sheet_get_cell_pt(sheet_t *sh, coord_t const *coord, enum dir_spec dir,
+    spt_t *pt)
+{
+	size_t cur_pos, prev_pos;
+	wchar_t c;
+	coord_t cc;
+
+	cc.row = cc.column = 1;
+	cur_pos = prev_pos = 0;
+	while (true) {
+		if (prev_pos >= sh->text_size) {
+			/* Cannot advance any further. */
+			break;
+		}
+
+		if ((cc.row >= coord->row && cc.column > coord->column) ||
+		    cc.row > coord->row) {
+			/* We are past the requested coordinates. */
+			break;
+		}
+
+		prev_pos = cur_pos;
+
+		c = str_decode(sh->data, &cur_pos, sh->text_size);
+		if (c == '\n') {
+			++cc.row;
+			cc.column = 1;
+		} else if (c == '\t') {
+			cc.column = 1 + ALIGN_UP(cc.column, TAB_WIDTH);
+		} else {
+			++cc.column;
+		}
+	}
+
+	pt->sh = sh;
+	pt->b_off = (dir == dir_before) ? prev_pos : cur_pos;
+}
+
+/** Get the number of character cells a row occupies. */
+void sheet_get_row_width(sheet_t *sh, int row, int *length)
+{
+	coord_t coord;
+	spt_t pt;
+
+	/* Especially nasty hack */
+	coord.row = row;
+	coord.column = 65536;
+	
+	sheet_get_cell_pt(sh, &coord, dir_before, &pt);
+	spt_get_coord(&pt, &coord);
+	*length = coord.column - 1;
+}
+
+/** Get the number of rows in a sheet. */
+void sheet_get_num_rows(sheet_t *sh, int *rows)
+{
+	int cnt;
+	size_t bo;
+
+	cnt = 1;
+	for (bo = 0; bo < sh->dbuf_size; ++bo) {
+		if (sh->data[bo] == '\n')
+			cnt += 1;
+	}
+
+	*rows = cnt;
+}
+
+/** Get the coordinates of an s-point. */
+void spt_get_coord(spt_t const *pos, coord_t *coord)
+{
+	size_t off;
+	coord_t cc;
+	wchar_t c;
+	sheet_t *sh;
+
+	sh = pos->sh;
+	cc.row = cc.column = 1;
+
+	off = 0;
+	while (off < pos->b_off && off < sh->text_size) {
+		c = str_decode(sh->data, &off, sh->text_size);
+		if (c == '\n') {
+			++cc.row;
+			cc.column = 1;
+		} else if (c == '\t') {
+			cc.column = 1 + ALIGN_UP(cc.column, TAB_WIDTH);
+		} else {
+			++cc.column;
+		}
+	}
+
+	*coord = cc;
+}
+
+/** Test if two s-points are equal. */
+bool spt_equal(spt_t const *a, spt_t const *b)
+{
+	return a->b_off == b->b_off;
+}
+
+/** Place a tag on the specified s-point. */
+void sheet_place_tag(sheet_t *sh, spt_t const *pt, tag_t *tag)
+{
+	tag->b_off = pt->b_off;
+	tag->sh = sh;
+	list_append(&tag->link, &sh->tags_head);
+}
+
+/** Remove a tag from the sheet. */
+void sheet_remove_tag(sheet_t *sh, tag_t *tag)
+{
+	list_remove(&tag->link);
+}
+
+/** Get s-point on which the tag is located right now. */
+void tag_get_pt(tag_t const *tag, spt_t *pt)
+{
+	pt->b_off = tag->b_off;
+	pt->sh = tag->sh;
+}
+
+/** @}
+ */
Index: uspace/app/edit/sheet.h
===================================================================
--- uspace/app/edit/sheet.h	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
+++ uspace/app/edit/sheet.h	(revision 3052ff4d8cfe91bfe73946c0e1785fe4cfe44b07)
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2009 Jiri Svoboda
+ * 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 SHEET_H__
+#define SHEET_H__
+
+#include <adt/list.h>
+#include <sys/types.h>
+#include <bool.h>
+
+/** Direction (in linear space) */
+enum dir_spec {
+	/** Before specified point */
+	dir_before,
+	/** After specified point */
+	dir_after
+};
+
+/** Sheet */
+typedef struct {
+	/* Note: This structure is opaque for the user. */
+
+	size_t text_size;
+	size_t dbuf_size;
+	char *data;
+
+	link_t tags_head;
+} sheet_t;
+
+/** Character cell coordinates
+ *
+ * These specify a character cell. The first cell is (1,1).
+ */
+typedef struct {
+	int row;
+	int column;
+} coord_t;
+
+/** S-point
+ *
+ * An s-point specifies the boundary between two successive characters
+ * in the linear file space (including the beginning of file or the end
+ * of file. An s-point only remains valid as long as no modifications
+ * (insertions/deletions) are performed on the sheet.
+ */
+typedef struct {
+	/* Note: This structure is opaque for the user. */
+	sheet_t *sh;
+	size_t b_off;
+} spt_t;
+
+/** Tag
+ *
+ * A tag is similar to an s-point, but remains valid over modifications
+ * to the sheet. A tag tends to 'stay put'. Any tag must be properly
+ * removed from the sheet before it is deallocated by the user.
+ */
+typedef struct {
+	/* Note: This structure is opaque for the user. */
+
+	/** Link to list of all tags in the sheet (see sheet_t.tags_head) */
+	link_t link;
+	sheet_t *sh;
+	size_t b_off;
+} tag_t;
+
+extern int sheet_init(sheet_t *);
+extern int sheet_insert(sheet_t *, spt_t *, enum dir_spec, char *);
+extern int sheet_delete(sheet_t *, spt_t *, spt_t *);
+extern void sheet_copy_out(sheet_t *, spt_t const *, spt_t const *, char *,
+    size_t, spt_t *);
+extern void sheet_get_cell_pt(sheet_t *, coord_t const *, enum dir_spec,
+    spt_t *);
+extern void sheet_get_row_width(sheet_t *, int, int *);
+extern void sheet_get_num_rows(sheet_t *, int *);
+extern void spt_get_coord(spt_t const *, coord_t *);
+extern bool spt_equal(spt_t const *, spt_t const *);
+
+extern void sheet_place_tag(sheet_t *, spt_t const *, tag_t *);
+extern void sheet_remove_tag(sheet_t *, tag_t *);
+extern void tag_get_pt(tag_t const *, spt_t *);
+
+#endif
+
+/** @}
+ */
