Index: uspace/app/bdsh/input.c
===================================================================
--- uspace/app/bdsh/input.c	(revision 65c1778021e5f1f53cacfbee289a65e382158f18)
+++ uspace/app/bdsh/input.c	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
@@ -36,5 +36,8 @@
 #include <io/keycode.h>
 #include <io/style.h>
+#include <io/color.h>
 #include <vfs/vfs.h>
+#include <clipboard.h>
+#include <macros.h>
 #include <errno.h>
 #include <assert.h>
@@ -50,16 +53,30 @@
 #define HISTORY_LEN 10
 
+/** Text input field. */
 typedef struct {
-	wchar_t buffer[INPUT_MAX];
+	/** Buffer holding text currently being edited */
+	wchar_t buffer[INPUT_MAX + 1];
+	/** Screen coordinates of the top-left corner of the text field */
 	int col0, row0;
+	/** Screen dimensions */
 	int con_cols, con_rows;
+	/** Number of characters in @c buffer */
 	int nc;
+	/** Caret position within buffer */
 	int pos;
-
+	/** Selection mark position within buffer */
+	int sel_start;
+
+	/** History (dynamically allocated strings) */
 	char *history[1 + HISTORY_LEN];
+	/** Number of entries in @c history, not counting [0] */
 	int hnum;
+	/** Current position in history */
 	int hpos;
+	/** Exit flag */
+	bool done;
 } tinput_t;
 
+/** Seek direction */
 typedef enum {
 	seek_backward = -1,
@@ -70,4 +87,15 @@
 
 static char *tinput_read(tinput_t *ti);
+static void tinput_insert_string(tinput_t *ti, const char *str);
+static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb);
+static bool tinput_sel_active(tinput_t *ti);
+static void tinput_sel_all(tinput_t *ti);
+static void tinput_sel_delete(tinput_t *ti);
+static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev);
+static void tinput_key_shift(tinput_t *ti, console_event_t *ev);
+static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev);
+static void tinput_key_unmod(tinput_t *ti, console_event_t *ev);
+static void tinput_pre_seek(tinput_t *ti, bool shift_held);
+static void tinput_post_seek(tinput_t *ti, bool shift_held);
 
 /* Tokenizes input from console, sees if the first word is a built-in, if so
@@ -123,9 +151,42 @@
 static void tinput_display_tail(tinput_t *ti, int start, int pad)
 {
-	int i;
+	static wchar_t dbuf[INPUT_MAX + 1];
+	int sa, sb;
+	int i, p;
+
+	tinput_sel_get_bounds(ti, &sa, &sb);
 
 	console_goto(fphone(stdout), (ti->col0 + start) % ti->con_cols,
 	    ti->row0 + (ti->col0 + start) / ti->con_cols);
-	printf("%ls", ti->buffer + start);
+	console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
+
+	p = start;
+	if (p < sa) {
+		memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
+		dbuf[sa - p] = '\0';
+		printf("%ls", dbuf);
+		p = sa;
+	}
+
+	if (p < sb) {
+		fflush(stdout);
+		console_set_color(fphone(stdout), COLOR_BLACK, COLOR_RED, 0);
+		memcpy(dbuf, ti->buffer + p,
+		    (sb - p) * sizeof(wchar_t));
+		dbuf[sb - p] = '\0';
+		printf("%ls", dbuf);
+		p = sb;
+	}
+
+	fflush(stdout);
+	console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
+
+	if (p < ti->nc) {
+		memcpy(dbuf, ti->buffer + p,
+		    (ti->nc - p) * sizeof(wchar_t));
+		dbuf[ti->nc - p] = '\0';
+		printf("%ls", dbuf);
+	}
+
 	for (i = 0; i < pad; ++i)
 		putchar(' ');
@@ -151,5 +212,5 @@
 	width = ti->col0 + ti->nc;
 	rows = (width / ti->con_cols) + 1;
- 
+
 	/* Update row0 if the screen scrolled. */
 	if (ti->row0 + rows > ti->con_rows)
@@ -180,4 +241,5 @@
 	ti->nc += 1;
 	ti->buffer[ti->nc] = '\0';
+	ti->sel_start = ti->pos;
 
 	tinput_display_tail(ti, ti->pos - 1, 0);
@@ -186,7 +248,56 @@
 }
 
+static void tinput_insert_string(tinput_t *ti, const char *str)
+{
+	int i;
+	int new_width, new_height;
+	int ilen;
+	wchar_t c;
+	size_t off;
+
+	ilen = min((ssize_t) str_length(str), INPUT_MAX - ti->nc);
+	if (ilen == 0)
+		return;
+
+	new_width = ti->col0 + ti->nc + ilen;
+	new_height = (new_width / ti->con_cols) + 1;
+	if (new_height >= ti->con_rows)
+		return; /* Disallow text longer than 1 page for now. */
+
+	for (i = ti->nc - 1; i >= ti->pos; --i)
+		ti->buffer[i + ilen] = ti->buffer[i];
+
+	off = 0; i = 0;
+	while (i < ilen) {
+		c = str_decode(str, &off, STR_NO_LIMIT);
+		if (c == '\0')
+			break;
+
+		/* Filter out non-printable chars. */
+		if (c < 32)
+			c = 32;
+
+		ti->buffer[ti->pos + i] = c;
+		++i;
+	}
+
+	ti->pos += ilen;
+	ti->nc += ilen;
+	ti->buffer[ti->nc] = '\0';
+	ti->sel_start = ti->pos;
+
+	tinput_display_tail(ti, ti->pos - ilen, 0);
+	tinput_update_origin(ti);
+	tinput_position_caret(ti);
+}
+
 static void tinput_backspace(tinput_t *ti)
 {
 	int i;
+
+	if (tinput_sel_active(ti)) {
+		tinput_sel_delete(ti);
+		return;
+	}
 
 	if (ti->pos == 0)
@@ -198,4 +309,5 @@
 	ti->nc -= 1;
 	ti->buffer[ti->nc] = '\0';
+	ti->sel_start = ti->pos;
 
 	tinput_display_tail(ti, ti->pos, 1);
@@ -205,13 +317,22 @@
 static void tinput_delete(tinput_t *ti)
 {
+	if (tinput_sel_active(ti)) {
+		tinput_sel_delete(ti);
+		return;
+	}
+
 	if (ti->pos == ti->nc)
 		return;
 
 	ti->pos += 1;
+	ti->sel_start = ti->pos;
+
 	tinput_backspace(ti);
 }
 
-static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir)
-{
+static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
 	if (dir == seek_forward) {
 		if (ti->pos < ti->nc)
@@ -222,9 +343,11 @@
 	}
 
-	tinput_position_caret(ti);
-}
-
-static void tinput_seek_word(tinput_t *ti, seek_dir_t dir)
-{
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
 	if (dir == seek_forward) {
 		if (ti->pos == ti->nc)
@@ -258,9 +381,11 @@
 	}
 
-	tinput_position_caret(ti);
-}
-
-static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir)
-{
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
 	if (dir == seek_forward) {
 		if (ti->pos + ti->con_cols <= ti->nc)
@@ -271,9 +396,11 @@
 	}
 
-	tinput_position_caret(ti);
-}
-
-static void tinput_seek_max(tinput_t *ti, seek_dir_t dir)
-{
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
+{
+	tinput_pre_seek(ti, shift_held);
+
 	if (dir == seek_backward)
 		ti->pos = 0;
@@ -281,4 +408,26 @@
 		ti->pos = ti->nc;
 
+	tinput_post_seek(ti, shift_held);
+}
+
+static void tinput_pre_seek(tinput_t *ti, bool shift_held)
+{
+	if (tinput_sel_active(ti) && !shift_held) {
+		/* Unselect and redraw. */
+		ti->sel_start = ti->pos;
+		tinput_display_tail(ti, 0, 0);
+		tinput_position_caret(ti);
+	}
+}
+
+static void tinput_post_seek(tinput_t *ti, bool shift_held)
+{
+	if (shift_held) {
+		/* Selecting text. Need redraw. */
+		tinput_display_tail(ti, 0, 0);
+	} else {
+		/* Shift not held. Keep selection empty. */
+		ti->sel_start = ti->pos;
+	}
 	tinput_position_caret(ti);
 }
@@ -311,4 +460,91 @@
 	ti->nc = wstr_length(ti->buffer);
 	ti->pos = ti->nc;
+	ti->sel_start = ti->pos;
+}
+
+static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb)
+{
+	if (ti->sel_start < ti->pos) {
+		*sa = ti->sel_start;
+		*sb = ti->pos;
+	} else {
+		*sa = ti->pos;
+		*sb = ti->sel_start;
+	}
+}
+
+static bool tinput_sel_active(tinput_t *ti)
+{
+	return ti->sel_start != ti->pos;
+}
+
+static void tinput_sel_all(tinput_t *ti)
+{
+	ti->sel_start = 0;
+	ti->pos = ti->nc;
+	tinput_display_tail(ti, 0, 0);
+	tinput_position_caret(ti);
+}
+
+static void tinput_sel_delete(tinput_t *ti)
+{
+	int sa, sb;
+
+	tinput_sel_get_bounds(ti, &sa, &sb);
+	if (sa == sb)
+		return;
+
+	memmove(ti->buffer + sa, ti->buffer + sb,
+	    (ti->nc - sb) * sizeof(wchar_t));
+	ti->pos = ti->sel_start = sa;
+	ti->nc -= (sb - sa);
+	ti->buffer[ti->nc] = '\0';
+
+	tinput_display_tail(ti, sa, sb - sa);
+	tinput_position_caret(ti);
+}
+
+static void tinput_sel_copy_to_cb(tinput_t *ti)
+{
+	int sa, sb;
+	wchar_t tmp_c;
+	char *str;
+
+	tinput_sel_get_bounds(ti, &sa, &sb);
+
+	if (sb < ti->nc) {
+		tmp_c = ti->buffer[sb];
+		ti->buffer[sb] = '\0';
+	}
+
+	str = wstr_to_astr(ti->buffer + sa);
+
+	if (sb < ti->nc)
+		ti->buffer[sb] = tmp_c;
+
+	if (str == NULL)
+		goto error;
+
+	if (clipboard_put_str(str) != EOK)
+		goto error;
+
+	free(str);
+	return;
+error:
+	return;
+	/* TODO: Give the user some warning. */
+}
+
+static void tinput_paste_from_cb(tinput_t *ti)
+{
+	char *str;
+	int rc;
+
+	rc = clipboard_get_str(&str);
+	if (rc != EOK || str == NULL)
+		return; /* TODO: Give the user some warning. */
+
+	tinput_insert_string(ti, str);
+	free(str);
 }
 
@@ -337,4 +573,8 @@
 }
 
+/** Initialize text input field.
+ *
+ * Must be called before using the field. It clears the history.
+ */
 static void tinput_init(tinput_t *ti)
 {
@@ -344,4 +584,5 @@
 }
 
+/** Read in one line of input. */
 static char *tinput_read(tinput_t *ti)
 {
@@ -356,13 +597,14 @@
 		return NULL;
 
-	ti->pos = 0;
+	ti->pos = ti->sel_start = 0;
 	ti->nc = 0;
 	ti->buffer[0] = '\0';
-
-	while (true) {
+	ti->done = false;
+
+	while (!ti->done) {
 		fflush(stdout);
 		if (!console_get_event(fphone(stdin), &ev))
 			return NULL;
-		
+
 		if (ev.type != KEY_PRESS)
 			continue;
@@ -370,58 +612,28 @@
 		if ((ev.mods & KM_CTRL) != 0 &&
 		    (ev.mods & (KM_ALT | KM_SHIFT)) == 0) {
-			switch (ev.key) {
-			case KC_LEFT:
-				tinput_seek_word(ti, seek_backward);
-				break;
-			case KC_RIGHT:
-				tinput_seek_word(ti, seek_forward);
-				break;
-			case KC_UP:
-				tinput_seek_vertical(ti, seek_backward);
-				break;
-			case KC_DOWN:
-				tinput_seek_vertical(ti, seek_forward);
-				break;
-			}
+			tinput_key_ctrl(ti, &ev);
 		}
 
+		if ((ev.mods & KM_SHIFT) != 0 &&
+		    (ev.mods & (KM_CTRL | KM_ALT)) == 0) {
+			tinput_key_shift(ti, &ev);
+		}
+
+		if ((ev.mods & KM_CTRL) != 0 &&
+		    (ev.mods & KM_SHIFT) != 0 &&
+		    (ev.mods & KM_ALT) == 0) {
+			tinput_key_ctrl_shift(ti, &ev);
+		}
+
 		if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
-			switch (ev.key) {
-			case KC_ENTER:
-			case KC_NENTER:
-				goto done;
-			case KC_BACKSPACE:
-				tinput_backspace(ti);
-				break;
-			case KC_DELETE:
-				tinput_delete(ti);
-				break;
-			case KC_LEFT:
-				tinput_seek_cell(ti, seek_backward);
-				break;
-			case KC_RIGHT:
-				tinput_seek_cell(ti, seek_forward);
-				break;
-			case KC_HOME:
-				tinput_seek_max(ti, seek_backward);
-				break;
-			case KC_END:
-				tinput_seek_max(ti, seek_forward);
-				break;
-			case KC_UP:
-				tinput_history_seek(ti, +1);
-				break;
-			case KC_DOWN:
-				tinput_history_seek(ti, -1);
-				break;
-			}
+			tinput_key_unmod(ti, &ev);
 		}
 
 		if (ev.c >= ' ') {
+			tinput_sel_delete(ti);
 			tinput_insert_char(ti, ev.c);
 		}
 	}
 
-done:
 	ti->pos = ti->nc;
 	tinput_position_caret(ti);
@@ -435,4 +647,120 @@
 
 	return str;
+}
+
+static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+		tinput_seek_word(ti, seek_backward, false);
+		break;
+	case KC_RIGHT:
+		tinput_seek_word(ti, seek_forward, false);
+		break;
+	case KC_UP:
+		tinput_seek_vertical(ti, seek_backward, false);
+		break;
+	case KC_DOWN:
+		tinput_seek_vertical(ti, seek_forward, false);
+		break;
+	case KC_X:
+		tinput_sel_copy_to_cb(ti);
+		tinput_sel_delete(ti);
+		break;
+	case KC_C:
+		tinput_sel_copy_to_cb(ti);
+		break;
+	case KC_V:
+		tinput_sel_delete(ti);
+		tinput_paste_from_cb(ti);
+		break;
+	case KC_A:
+		tinput_sel_all(ti);
+		break;
+	default:
+		break;
+	}
+}
+
+static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+		tinput_seek_word(ti, seek_backward, true);
+		break;
+	case KC_RIGHT:
+		tinput_seek_word(ti, seek_forward, true);
+		break;
+	case KC_UP:
+		tinput_seek_vertical(ti, seek_backward, true);
+		break;
+	case KC_DOWN:
+		tinput_seek_vertical(ti, seek_forward, true);
+		break;
+	default:
+		break;
+	}
+}
+
+static void tinput_key_shift(tinput_t *ti, console_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+		tinput_seek_cell(ti, seek_backward, true);
+		break;
+	case KC_RIGHT:
+		tinput_seek_cell(ti, seek_forward, true);
+		break;
+	case KC_UP:
+		tinput_seek_vertical(ti, seek_backward, true);
+		break;
+	case KC_DOWN:
+		tinput_seek_vertical(ti, seek_forward, true);
+		break;
+	case KC_HOME:
+		tinput_seek_max(ti, seek_backward, true);
+		break;
+	case KC_END:
+		tinput_seek_max(ti, seek_forward, true);
+		break;
+	default:
+		break;
+	}
+}
+
+static void tinput_key_unmod(tinput_t *ti, console_event_t *ev)
+{
+	switch (ev->key) {
+	case KC_ENTER:
+	case KC_NENTER:
+		ti->done = true;
+		break;
+	case KC_BACKSPACE:
+		tinput_backspace(ti);
+		break;
+	case KC_DELETE:
+		tinput_delete(ti);
+		break;
+	case KC_LEFT:
+		tinput_seek_cell(ti, seek_backward, false);
+		break;
+	case KC_RIGHT:
+		tinput_seek_cell(ti, seek_forward, false);
+		break;
+	case KC_HOME:
+		tinput_seek_max(ti, seek_backward, false);
+		break;
+	case KC_END:
+		tinput_seek_max(ti, seek_forward, false);
+		break;
+	case KC_UP:
+		tinput_history_seek(ti, +1);
+		break;
+	case KC_DOWN:
+		tinput_history_seek(ti, -1);
+		break;
+	default:
+		break;
+	}
 }
 
Index: uspace/app/edit/edit.c
===================================================================
--- uspace/app/edit/edit.c	(revision 65c1778021e5f1f53cacfbee289a65e382158f18)
+++ uspace/app/edit/edit.c	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
@@ -45,4 +45,5 @@
 #include <align.h>
 #include <macros.h>
+#include <clipboard.h>
 #include <bool.h>
 
@@ -74,4 +75,7 @@
 	tag_t caret_pos;
 
+	/** Start of selection */
+	tag_t sel_start;
+
 	/** 
 	 * Ideal column where the caret should try to get. This is used
@@ -107,4 +111,7 @@
 static void key_handle_unmod(console_event_t const *ev);
 static void key_handle_ctrl(console_event_t const *ev);
+static void key_handle_shift(console_event_t const *ev);
+static void key_handle_movement(unsigned int key, bool shift);
+
 static int file_save(char const *fname);
 static void file_save_as(void);
@@ -113,4 +120,6 @@
     spt_t const *epos);
 static char *filename_prompt(char const *prompt, char const *init_value);
+static char *range_get_str(spt_t const *spos, spt_t const *epos);
+
 static void pane_text_display(void);
 static void pane_row_display(void);
@@ -118,4 +127,5 @@
 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);
@@ -123,6 +133,18 @@
 static void caret_update(void);
 static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
+
+static bool selection_active(void);
+static void selection_sel_all(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 pt_get_sof(spt_t *pt);
 static void pt_get_eof(spt_t *pt);
+static int tag_cmp(tag_t const *a, tag_t const *b);
+static int spt_cmp(spt_t const *a, spt_t const *b);
+static int coord_cmp(coord_t const *a, coord_t const *b);
+
 static void status_display(char const *str);
 
@@ -172,4 +194,8 @@
 	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);
+
 	/* Initial display */
 	console_clear(con);
@@ -190,7 +216,12 @@
 			/* Handle key press. */
 			if (((ev.mods & KM_ALT) == 0) &&
+			    ((ev.mods & KM_SHIFT) == 0) &&
 			     (ev.mods & KM_CTRL) != 0) {
 				key_handle_ctrl(&ev);
-			} else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
+			} else if (((ev.mods & KM_ALT) == 0) &&
+			    ((ev.mods & KM_CTRL) == 0) &&
+			     (ev.mods & KM_SHIFT) != 0) {
+				key_handle_shift(&ev);
+			} else if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
 				key_handle_unmod(&ev);
 			}
@@ -207,5 +238,4 @@
 		if (pane.rflags & REDRAW_CARET)
 			pane_caret_display();
-			
 	}
 
@@ -220,41 +250,59 @@
 	switch (ev->key) {
 	case KC_ENTER:
+		selection_delete();
 		insert_char('\n');
 		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);
+		key_handle_movement(ev->key, false);
 		break;
 	case KC_BACKSPACE:
-		delete_char_before();
+		if (selection_active())
+			selection_delete();
+		else
+			delete_char_before();
 		caret_update();
 		break;
 	case KC_DELETE:
-		delete_char_after();
+		if (selection_active())
+			selection_delete();
+		else
+			delete_char_after();
 		caret_update();
 		break;
 	default:
 		if (ev->c >= 32 || ev->c == '\t') {
+			selection_delete();
+			insert_char(ev->c);
+			caret_update();
+		}
+		break;
+	}
+}
+
+/** Handle Shift-key combination. */
+static void key_handle_shift(console_event_t const *ev)
+{
+	switch (ev->key) {
+	case KC_LEFT:
+	case KC_RIGHT:
+	case KC_UP:
+	case KC_DOWN:
+	case KC_HOME:
+	case KC_END:
+	case KC_PAGE_UP:
+	case KC_PAGE_DOWN:
+		key_handle_movement(ev->key, true);
+		break;
+	default:
+		if (ev->c >= 32 || ev->c == '\t') {
+			selection_delete();
 			insert_char(ev->c);
 			caret_update();
@@ -280,6 +328,88 @@
 		file_save_as();
 		break;
+	case KC_C:
+		selection_copy();
+		break;
+	case KC_V:
+		selection_delete();
+		insert_clipboard_data();
+		pane.rflags |= REDRAW_TEXT;
+		caret_update();
+		break;
+	case KC_X:
+		selection_copy();
+		selection_delete();
+		pane.rflags |= REDRAW_TEXT;
+		caret_update();
+		break;
+	case KC_A:
+		selection_sel_all();
+		break;
 	default:
 		break;
+	}
+}
+
+static void key_handle_movement(unsigned int key, bool select)
+{
+	spt_t pt;
+	spt_t caret_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);
+
+	switch (key) {
+	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;
+	default:
+		break;
+	}
+
+	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);
+	}
+
+	if (select) {
+		tag_get_pt(&pane.caret_pos, &pt);
+		spt_get_coord(&caret_pt, &c_old);
+		spt_get_coord(&pt, &c_new);
+
+		if (c_old.row == c_new.row)
+			pane.rflags |= REDRAW_ROW;
+		else
+			pane.rflags |= REDRAW_TEXT;
+
+	} else if (had_sel == true) {
+		/* Redraw because text was unselected. */
+		pane.rflags |= REDRAW_TEXT;
 	}
 }
@@ -473,4 +603,40 @@
 }
 
+/** Return contents of range as a new string. */
+static char *range_get_str(spt_t const *spos, spt_t const *epos)
+{
+	char *buf;
+	spt_t sp, bep;
+	size_t bytes;
+	size_t buf_size, bpos;
+
+	buf_size = 1;
+
+	buf = malloc(buf_size);
+	if (buf == NULL)
+		return NULL;
+
+	bpos = 0;
+	sp = *spos;
+
+	while (true) {
+		sheet_copy_out(&doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
+		    &bep);
+		bytes = str_size(&buf[bpos]);
+		bpos += bytes;
+		sp = bep;
+
+		if (spt_equal(&bep, epos))
+			break;
+
+		buf_size *= 2;
+		buf = realloc(buf, buf_size);
+		if (buf == NULL)
+			return NULL;
+	}
+
+	return buf;
+}
+
 static void pane_text_display(void)
 {
@@ -517,5 +683,5 @@
 {
 	int i, j, fill;
-	spt_t rb, re, dep;
+	spt_t rb, re, dep, pt;
 	coord_t rbc, rec;
 	char row_buf[ROW_BUF_SIZE];
@@ -523,4 +689,19 @@
 	size_t pos, size;
 	unsigned s_column;
+	coord_t csel_start, csel_end, ctmp;
+
+	/* Determine selection start and end. */
+
+	tag_get_pt(&pane.sel_start, &pt);
+	spt_get_coord(&pt, &csel_start);
+
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_get_coord(&pt, &csel_end);
+
+	if (coord_cmp(&csel_start, &csel_end) > 0) {
+		ctmp = csel_start;
+		csel_start = csel_end;
+		csel_end = ctmp;
+	}
 
 	/* Draw rows from the sheet. */
@@ -543,4 +724,11 @@
 		/* Display text from the buffer. */
 
+		if (coord_cmp(&csel_start, &rbc) <= 0 &&
+		    coord_cmp(&rbc, &csel_end) < 0) {
+			fflush(stdout);
+			console_set_color(con, COLOR_BLACK, COLOR_RED, 0);
+			fflush(stdout);
+		}
+
 		console_goto(con, 0, i);
 		size = str_size(row_buf);
@@ -548,4 +736,16 @@
 		s_column = 1;
 		while (pos < size) {
+			if (csel_start.row == rbc.row && csel_start.column == s_column) {
+				fflush(stdout);
+				console_set_color(con, COLOR_BLACK, COLOR_RED, 0);
+				fflush(stdout);
+			}
+	
+			if (csel_end.row == rbc.row && csel_end.column == s_column) {
+				fflush(stdout);
+				console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
+				fflush(stdout);
+			}
+	
 			c = str_decode(row_buf, &pos, size);
 			if (c != '\t') {
@@ -562,4 +762,10 @@
 		}
 
+		if (csel_end.row == rbc.row && csel_end.column == s_column) {
+			fflush(stdout);
+			console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
+			fflush(stdout);
+		}
+
 		/* Fill until the end of display area. */
 
@@ -572,4 +778,5 @@
 			putchar(' ');
 		fflush(stdout);
+		console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
 	}
 
@@ -760,4 +967,101 @@
 }
 
+/** Check for non-empty selection. */
+static bool selection_active(void)
+{
+	return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
+}
+
+static void selection_get_points(spt_t *pa, spt_t *pb)
+{
+	spt_t pt;
+
+	tag_get_pt(&pane.sel_start, pa);
+	tag_get_pt(&pane.caret_pos, pb);
+
+	if (spt_cmp(pa, pb) > 0) {
+		pt = *pa;
+		*pa = *pb;
+		*pb = pt;
+	}
+}
+
+/** Delete selected text. */
+static void selection_delete(void)
+{
+	spt_t pa, pb;
+	coord_t ca, cb;
+	int rel;
+
+	tag_get_pt(&pane.sel_start, &pa);
+	tag_get_pt(&pane.caret_pos, &pb);
+	spt_get_coord(&pa, &ca);
+	spt_get_coord(&pb, &cb);
+	rel = coord_cmp(&ca, &cb);
+
+	if (rel == 0)
+		return;
+
+	if (rel < 0)
+		sheet_delete(&doc.sh, &pa, &pb);
+	else
+		sheet_delete(&doc.sh, &pb, &pa);
+
+	if (ca.row == cb.row)
+		pane.rflags |= REDRAW_ROW;
+	else
+		pane.rflags |= REDRAW_TEXT;
+}
+
+static void selection_sel_all(void)
+{
+	spt_t spt, ept;
+
+	pt_get_sof(&spt);
+	pt_get_eof(&ept);
+	sheet_remove_tag(&doc.sh, &pane.sel_start);
+	sheet_place_tag(&doc.sh, &spt, &pane.sel_start);
+	sheet_remove_tag(&doc.sh, &pane.caret_pos);
+	sheet_place_tag(&doc.sh, &ept, &pane.caret_pos);
+
+	pane.rflags |= REDRAW_TEXT;
+}
+
+static void selection_copy(void)
+{
+	spt_t pa, pb;
+	char *str;
+
+	selection_get_points(&pa, &pb);
+	str = range_get_str(&pa, &pb);
+	if (str == NULL || clipboard_put_str(str) != EOK) {
+		status_display("Copying to clipboard failed!");
+	}
+	free(str);
+}
+
+static void insert_clipboard_data(void)
+{
+	char *str;
+	size_t off;
+	wchar_t c;
+	int rc;
+
+	rc = clipboard_get_str(&str);
+	if (rc != EOK || str == NULL)
+		return;
+
+	off = 0;
+
+	while (true) {
+		c = str_decode(str, &off, STR_NO_LIMIT);
+		if (c == '\0')
+			break;
+
+		insert_char(c);
+	}
+
+	free(str);
+}
 
 /** Get start-of-file s-point. */
@@ -781,4 +1085,35 @@
 
 	sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
+}
+
+/** Compare tags. */
+static int tag_cmp(tag_t const *a, tag_t const *b)
+{
+	spt_t pa, pb;
+
+	tag_get_pt(a, &pa);
+	tag_get_pt(b, &pb);
+
+	return spt_cmp(&pa, &pb);
+}
+
+/** Compare s-points. */
+static int spt_cmp(spt_t const *a, spt_t const *b)
+{
+	coord_t ca, cb;
+
+	spt_get_coord(a, &ca);
+	spt_get_coord(b, &cb);
+
+	return coord_cmp(&ca, &cb);
+}
+
+/** Compare coordinats. */
+static int coord_cmp(coord_t const *a, coord_t const *b)
+{
+	if (a->row - b->row != 0)
+		return a->row - b->row;
+
+	return a->column - b->column;
 }
 
Index: uspace/lib/libc/Makefile.build
===================================================================
--- uspace/lib/libc/Makefile.build	(revision 65c1778021e5f1f53cacfbee289a65e382158f18)
+++ uspace/lib/libc/Makefile.build	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
@@ -50,4 +50,5 @@
 	generic/as.c \
 	generic/cap.c \
+	generic/clipboard.c \
 	generic/devmap.c \
 	generic/event.c \
Index: uspace/lib/libc/generic/clipboard.c
===================================================================
--- uspace/lib/libc/generic/clipboard.c	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
+++ uspace/lib/libc/generic/clipboard.c	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
@@ -0,0 +1,151 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ * @brief System clipboard API.
+ *
+ * The clipboard data is stored in a file and it is shared by the entire
+ * system.
+ *
+ * @note Synchronization is missing. File locks would be ideal for the job.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <macros.h>
+#include <errno.h>
+#include <clipboard.h>
+
+/** File used to store clipboard data */
+#define CLIPBOARD_FILE "/data/clip"
+
+#define CHUNK_SIZE	4096
+
+/** Copy string to clipboard.
+ *
+ * Sets the clipboard contents to @a str. Passing an empty string or NULL
+ * makes the clipboard empty. 
+ *
+ * @param str	String to put to clipboard or NULL.
+ * @return	Zero on success or negative error code.
+ */
+int clipboard_put_str(const char *str)
+{
+	int fd;
+	const char *sp;
+	ssize_t to_write, written, left;
+
+	fd = open(CLIPBOARD_FILE, O_CREAT | O_TRUNC | O_WRONLY, 0666);
+	if (fd < 0)
+		return EIO;
+
+	left = str_size(str);
+	sp = str;
+
+	while (left > 0) {
+		to_write = min(left, CHUNK_SIZE);
+		written = write(fd, sp, to_write);
+
+		if (written < 0) {
+			close(fd);
+			unlink(CLIPBOARD_FILE);
+			return EIO;
+		}
+
+		sp += written;
+		left -= written;
+	}
+
+	if (close(fd) != EOK) {
+		unlink(CLIPBOARD_FILE);
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** Get a copy of clipboard contents.
+ *
+ * Returns a new string that can be deallocated with free().
+ *
+ * @param str	Here pointer to the newly allocated string is stored.
+ * @return	Zero on success or negative error code.
+ */
+int clipboard_get_str(char **str)
+{
+	int fd;
+	char *sbuf, *sp;
+	ssize_t to_read, n_read, left;
+	struct stat cbs;
+
+	if (stat(CLIPBOARD_FILE, &cbs) != EOK)
+		return EIO;
+
+	sbuf = malloc(cbs.size + 1);
+	if (sbuf == NULL)
+		return ENOMEM;
+
+	fd = open(CLIPBOARD_FILE, O_RDONLY);
+	if (fd < 0) {
+		free(sbuf);
+		return EIO;
+	}
+
+	sp = sbuf;
+	left = cbs.size;
+
+	while (left > 0) {
+		to_read = min(left, CHUNK_SIZE);
+		n_read = read(fd, sp, to_read);
+
+		if (n_read < 0) {
+			close(fd);
+			free(sbuf);
+			return EIO;
+		}
+
+		sp += n_read;
+		left -= n_read;
+	}
+
+	close(fd);
+
+	*sp = '\0';
+	*str = sbuf;
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/lib/libc/generic/io/io.c
===================================================================
--- uspace/lib/libc/generic/io/io.c	(revision 65c1778021e5f1f53cacfbee289a65e382158f18)
+++ uspace/lib/libc/generic/io/io.c	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
@@ -494,5 +494,5 @@
 	
 	if (chr_encode(c, buf, &sz, STR_BOUNDS(1)) == EOK) {
-		size_t wr = fwrite(buf, sz, 1, stream);
+		size_t wr = fwrite(buf, 1, sz, stream);
 		
 		if (wr < sz)
Index: uspace/lib/libc/include/clipboard.h
===================================================================
--- uspace/lib/libc/include/clipboard.h	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
+++ uspace/lib/libc/include/clipboard.h	(revision 034bf0e27f6439c8ee9425abd454c308f7f0ea84)
@@ -0,0 +1,44 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBC_CLIPBOARD_H_
+#define LIBC_CLIPBOARD_H_
+
+extern int clipboard_put_str(const char *);
+extern int clipboard_get_str(char **);
+
+#endif
+
+/** @}
+ */
