Browse Source

ktp: Working on tinyrl and client ktp

Serj Kalichev 1 year ago
parent
commit
9b3144f306
9 changed files with 1788 additions and 1722 deletions
  1. 27 1
      bin/klish/klish.c
  2. 6 39
      klish/ktp/ktp_session.c
  3. 5 2
      tinyrl/Makefile.am
  4. 0 1657
      tinyrl/tinyrl.c
  5. 7 19
      tinyrl/tinyrl.h
  6. 3 0
      tinyrl/tinyrl/Makefile.am
  7. 14 4
      tinyrl/tinyrl/private.h
  8. 1473 0
      tinyrl/tinyrl/tinyrl.c
  9. 253 0
      tinyrl/tinyrl/utf8.c

+ 27 - 1
bin/klish/klish.c

@@ -17,6 +17,7 @@
 #include <faux/msg.h>
 #include <faux/list.h>
 #include <faux/file.h>
+#include <faux/eloop.h>
 
 #include <klish/ktp.h>
 #include <klish/ktp_session.h>
@@ -28,6 +29,8 @@ static bool_t stdout_cb(ktp_session_t *ktp, const char *line, size_t len,
 	void *user_data);
 static bool_t stderr_cb(ktp_session_t *ktp, const char *line, size_t len,
 	void *user_data);
+static bool_t stop_loop_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
+	void *associated_data, void *user_data);
 
 
 int main(int argc, char **argv)
@@ -37,6 +40,8 @@ int main(int argc, char **argv)
 	int unix_sock = -1;
 	ktp_session_t *ktp = NULL;
 	int retcode = 0;
+	faux_eloop_t *eloop = NULL;
+
 
 	// Parse command line options
 	opts = opts_init();
@@ -51,7 +56,15 @@ int main(int argc, char **argv)
 		fprintf(stderr, "Error: Can't connect to server\n");
 		goto err;
 	}
-	ktp = ktp_session_new(unix_sock);
+
+	// Eloop object
+	eloop = faux_eloop_new(NULL);
+	faux_eloop_add_signal(eloop, SIGINT, stop_loop_ev, NULL);
+	faux_eloop_add_signal(eloop, SIGTERM, stop_loop_ev, NULL);
+	faux_eloop_add_signal(eloop, SIGQUIT, stop_loop_ev, NULL);
+
+	// KTP session
+	ktp = ktp_session_new(unix_sock, eloop);
 	assert(ktp);
 	if (!ktp) {
 		fprintf(stderr, "Error: Can't create klish session\n");
@@ -166,3 +179,16 @@ static bool_t stderr_cb(ktp_session_t *ktp, const char *line, size_t len,
 
 	return BOOL_TRUE;
 }
+
+
+static bool_t stop_loop_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
+	void *associated_data, void *user_data)
+{
+	// Happy compiler
+	eloop = eloop;
+	type = type;
+	associated_data = associated_data;
+	user_data = user_data;
+
+	return BOOL_FALSE; // Stop Event Loop
+}

+ 6 - 39
klish/ktp/ktp_session.c

@@ -52,13 +52,16 @@ static bool_t ktp_session_read_cb(faux_async_t *async,
 	faux_buf_t *buf, size_t len, void *user_data);
 
 
-ktp_session_t *ktp_session_new(int sock)
+ktp_session_t *ktp_session_new(int sock, faux_eloop_t *eloop)
 {
 	ktp_session_t *ktp = NULL;
 
 	if (sock < 0)
 		return NULL;
 
+	if (!eloop)
+		return NULL;
+
 	ktp = faux_zmalloc(sizeof(*ktp));
 	assert(ktp);
 	if (!ktp)
@@ -67,9 +70,7 @@ ktp_session_t *ktp_session_new(int sock)
 	// Init
 	ktp->state = KTP_SESSION_STATE_IDLE;
 	ktp->done = BOOL_FALSE;
-
-	// Event loop
-	ktp->eloop = faux_eloop_new(NULL);
+	ktp->eloop = eloop;
 
 	// Async object
 	ktp->async = faux_async_new(sock);
@@ -82,9 +83,6 @@ ktp_session_t *ktp_session_new(int sock)
 	faux_async_set_stall_cb(ktp->async, ktp_stall_cb, ktp->eloop);
 
 	// Event loop handlers
-	faux_eloop_add_signal(ktp->eloop, SIGINT, stop_loop_ev, ktp);
-	faux_eloop_add_signal(ktp->eloop, SIGTERM, stop_loop_ev, ktp);
-	faux_eloop_add_signal(ktp->eloop, SIGQUIT, stop_loop_ev, ktp);
 	faux_eloop_add_fd(ktp->eloop, ktp_session_fd(ktp), POLLIN,
 		server_ev, ktp);
 
@@ -110,10 +108,10 @@ void ktp_session_free(ktp_session_t *ktp)
 	if (!ktp)
 		return;
 
+	faux_eloop_del_fd(ktp->eloop, ktp_session_fd(ktp));
 	faux_free(ktp->hdr);
 	close(ktp_session_fd(ktp));
 	faux_async_free(ktp->async);
-	faux_eloop_free(ktp->eloop);
 	faux_free(ktp);
 }
 
@@ -210,37 +208,6 @@ int ktp_session_fd(const ktp_session_t *ktp)
 }
 
 
-#if 0
-static void ktp_session_bad_socket(ktp_session_t *ktp)
-{
-	assert(ktp);
-	if (!ktp)
-		return;
-
-	ktp->state = KTP_SESSION_STATE_DISCONNECTED;
-}
-#endif
-
-
-static bool_t stop_loop_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
-	void *associated_data, void *user_data)
-{
-	ktp_session_t *ktp = (ktp_session_t *)user_data;
-
-	if (!ktp)
-		return BOOL_FALSE;
-
-	ktp_session_set_done(ktp, BOOL_TRUE);
-
-	// Happy compiler
-	eloop = eloop;
-	type = type;
-	associated_data = associated_data;
-
-	return BOOL_FALSE; // Stop Event Loop
-}
-
-
 static bool_t server_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 	void *associated_data, void *user_data)
 {

+ 5 - 2
tinyrl/Makefile.am

@@ -4,11 +4,14 @@ libtinyrl_la_LDFLAGS = $(AM_LDFLAGS) $(VERSION_INFO)
 
 nobase_include_HEADERS += \
 	tinyrl/vt100.h \
-	tinyrl/hist.h
+	tinyrl/hist.h \
+	tinyrl/tinyrl.h
 
 EXTRA_DIST += \
 	tinyrl/vt100/Makefile.am \
-	tinyrl/hist/Makefile.am
+	tinyrl/hist/Makefile.am \
+	tinyrl/tinyrl/Makefile.am
 
 include $(top_srcdir)/tinyrl/vt100/Makefile.am
 include $(top_srcdir)/tinyrl/hist/Makefile.am
+include $(top_srcdir)/tinyrl/tinyrl/Makefile.am

+ 0 - 1657
tinyrl/tinyrl.c

@@ -1,1657 +0,0 @@
-/*
- * tinyrl.c
- */
-
-/* make sure we can get fileno() */
-#undef __STRICT_ANSI__
-
-/* LIBC HEADERS */
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <errno.h>
-
-/* POSIX HEADERS */
-#include <unistd.h>
-
-#include "lub/string.h"
-
-#include "private.h"
-
-/*-------------------------------------------------------- */
-static int utf8_wchar(const char *sp, unsigned long *sym_out)
-{
-	int i = 0;
-	int l = 0; /* Number of 0x10 UTF sequence bytes */
-	unsigned long sym = 0;
-	const unsigned char *p = (const unsigned char *)sp;
-
-	if (sym_out)
-		*sym_out = *p;
-
-	if (!*p)
-		return 0;
-
-	/* Check for first byte of UTF-8 */
-	if (!(*p & 0xc0))
-		return 1;
-
-	/* Analyze first byte */
-	if ((*p & 0xe0) == 0xc0) {
-		l = 1;
-		sym = (*p & 0x1f);
-	} else if ((*p & 0xf0) == 0xe0) {
-		l = 2;
-		sym = (*p & 0xf);
-	} else if ((*p & 0xf8) == 0xf0) {
-		l = 3;
-		sym = (*p & 7);
-	} else if ((*p & 0xfc) == 0xf8) {
-		l = 4;
-		sym = (*p & 3);
-	} else if ((*p & 0xfe) == 0xfc) {
-		l = 5;
-		sym = (*p & 1);
-	} else {
-		return 1;
-	}
-	p++;
-
-	/* Analyze next UTF-8 bytes */
-	for (i = 0; i < l; i++) {
-		sym <<= 6;
-		/* Check if it's really UTF-8 bytes */
-		if ((*p & 0xc0) != 0x80)
-			return 1;
-		sym |= (*p & 0x3f);
-		p++;
-	}
-
-	if (sym_out)
-		*sym_out = sym;
-	return (l + 1);
-}
-
-/*-------------------------------------------------------- */
-static int utf8_is_cjk(unsigned long sym)
-{
-	if (sym < 0x1100) /* Speed up for non-CJK chars */
-		return 0;
-
-	if (sym >= 0x1100 && sym <= 0x11FF) /* Hangul Jamo */
-		return 1;
-#if 0
-	if (sym >=0x2E80 && sym <= 0x2EFF) /* CJK Radicals Supplement */
-		return 1;
-	if (sym >=0x2F00 && sym <= 0x2FDF) /* Kangxi Radicals */
-		return 1;
-	if (sym >= 0x2FF0 && sym <= 0x2FFF) /* Ideographic Description Characters */
-		return 1;
-	if (sym >= 0x3000 && sym < 0x303F) /* CJK Symbols and Punctuation. The U+303f is half space */
-		return 1;
-	if (sym >= 0x3040 && sym <= 0x309F) /* Hiragana */
-		return 1;
-	if (sym >= 0x30A0 && sym <=0x30FF) /* Katakana */
-		return 1;
-	if (sym >= 0x3100 && sym <=0x312F) /* Bopomofo */
-		return 1;
-	if (sym >= 0x3130 && sym <= 0x318F) /* Hangul Compatibility Jamo */
-		return 1;
-	if (sym >= 0x3190 && sym <= 0x319F) /* Kanbun */
-		return 1;
-	if (sym >= 0x31A0 && sym <= 0x31BF) /* Bopomofo Extended */
-		return 1;
-	if (sym >= 0x31C0 && sym <= 0x31EF) /* CJK strokes */
-		return 1;
-	if (sym >= 0x31F0 && sym <= 0x31FF) /* Katakana Phonetic Extensions */
-		return 1;
-	if (sym >= 0x3200 && sym <= 0x32FF) /* Enclosed CJK Letters and Months */
-		return 1;
-	if (sym >= 0x3300 && sym <= 0x33FF) /* CJK Compatibility */
-		return 1;
-	if (sym >= 0x3400 && sym <= 0x4DBF) /* CJK Unified Ideographs Extension A */
-		return 1;
-	if (sym >= 0x4DC0 && sym <= 0x4DFF) /* Yijing Hexagram Symbols */
-		return 1;
-	if (sym >= 0x4E00 && sym <= 0x9FFF) /* CJK Unified Ideographs */
-		return 1;
-	if (sym >= 0xA000 && sym <= 0xA48F) /* Yi Syllables */
-		return 1;
-	if (sym >= 0xA490 && sym <= 0xA4CF) /* Yi Radicals */
-		return 1;
-#endif
-	/* Speed up previous block */
-	if (sym >= 0x2E80 && sym <= 0xA4CF && sym != 0x303F)
-		return 1;
-
-	if (sym >= 0xAC00 && sym <= 0xD7AF) /* Hangul Syllables */
-		return 1;
-	if (sym >= 0xF900 && sym <= 0xFAFF) /* CJK Compatibility Ideographs */
-		return 1;
-	if (sym >= 0xFE10 && sym <= 0xFE1F) /* Vertical Forms */
-		return 1;
-
-#if 0
-	if (sym >= 0xFE30 && sym <= 0xFE4F) /* CJK Compatibility Forms */
-		return 1;
-	if (sym >= 0xFE50 && sym <= 0xFE6F) /* Small Form Variants */
-		return 1;
-#endif
-	/* Speed up previous block */
-	if (sym >= 0xFE30 && sym <= 0xFE6F)
-		return 1;
-
-	if ((sym >= 0xFF00 && sym <= 0xFF60) ||
-		(sym >= 0xFFE0 && sym <= 0xFFE6)) /* Fullwidth Forms */
-		return 1;
-
-	if (sym >= 0x1D300 && sym <= 0x1D35F) /* Tai Xuan Jing Symbols */
-		return 1;
-	if (sym >= 0x20000 && sym <= 0x2B81F) /* CJK Unified Ideographs Extensions B, C, D */
-		return 1;
-	if (sym >= 0x2F800 && sym <= 0x2FA1F) /* CJK Compatibility Ideographs Supplement */
-		return 1;
-
-	return 0;
-}
-
-/*-------------------------------------------------------- */
-static void utf8_point_left(tinyrl_t * this)
-{
-	if (!this->utf8)
-		return;
-	while (this->point &&
-		(UTF8_10 == (this->line[this->point] & UTF8_MASK)))
-		this->point--;
-}
-
-/*-------------------------------------------------------- */
-static void utf8_point_right(tinyrl_t * this)
-{
-	if (!this->utf8)
-		return;
-	while ((this->point < this->end) &&
-		(UTF8_10 == (this->line[this->point] & UTF8_MASK)))
-		this->point++;
-}
-
-/*-------------------------------------------------------- */
-static unsigned int utf8_nsyms(const tinyrl_t *this, const char *str,
-	unsigned int num)
-{
-	unsigned int nsym = 0;
-	unsigned long sym = 0;
-	unsigned int i = 0;
-
-	if (!this->utf8)
-		return num;
-
-	while (i < num) {
-		if ('\0' == str[i])
-			break;
-		/* ASCII char */
-		if (!(UTF8_7BIT_MASK & str[i])) {
-			i++;
-			nsym++;
-			continue;
-		}
-		/* Multibyte */
-		i += utf8_wchar(&str[i], &sym);
-		if (utf8_is_cjk(sym)) /* CJK chars have double-width */
-			nsym += 2;
-		else
-			nsym += 1;
-	}
-
-	return nsym;
-}
-
-/*----------------------------------------------------------------------- */
-static void tty_set_raw_mode(tinyrl_t * this)
-{
-	struct termios new_termios;
-	int fd;
-
-	if (!tinyrl_vt100__get_istream(this->term))
-		return;
-	fd = fileno(tinyrl_vt100__get_istream(this->term));
-	if (tcgetattr(fd, &new_termios) < 0)
-		return;
-	new_termios.c_iflag = 0;
-	new_termios.c_oflag = OPOST | ONLCR;
-	new_termios.c_lflag = 0;
-	new_termios.c_cc[VMIN] = 1;
-	new_termios.c_cc[VTIME] = 0;
-	/* Do the mode switch */
-	(void)tcsetattr(fd, TCSADRAIN, &new_termios);
-}
-
-/*----------------------------------------------------------------------- */
-static void tty_restore_mode(const tinyrl_t * this)
-{
-	int fd;
-
-	if (!tinyrl_vt100__get_istream(this->term))
-		return;
-	fd = fileno(tinyrl_vt100__get_istream(this->term));
-	/* Do the mode switch */
-	(void)tcsetattr(fd, TCSADRAIN, &this->default_termios);
-}
-
-/*----------------------------------------------------------------------- */
-/*
-This is called whenever a line is edited in any way.
-It signals that if we are currently viewing a history line we should transfer it
-to the current buffer
-*/
-static void changed_line(tinyrl_t * this)
-{
-	/* if the current line is not our buffer then make it so */
-	if (this->line != this->buffer) {
-		/* replace the current buffer with the new details */
-		free(this->buffer);
-		this->line = this->buffer = lub_string_dup(this->line);
-		this->buffer_size = strlen(this->buffer);
-		assert(this->line);
-	}
-}
-
-/*----------------------------------------------------------------------- */
-static int tinyrl_timeout_default(tinyrl_t *this)
-{
-	this = this; /* Happy compiler */
-
-	/* Return -1 to close session on timeout */
-	return -1;
-}
-
-/*----------------------------------------------------------------------- */
-static bool_t tinyrl_key_default(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	if (key > 31) {
-		char tmp[2];
-		tmp[0] = (key & 0xFF), tmp[1] = '\0';
-		/* inject this text into the buffer */
-		result = tinyrl_insert_text(this, tmp);
-	} else {
-		/* Call the external hotkey analyzer */
-		if (this->hotkey_fn)
-			this->hotkey_fn(this, key);
-	}
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_interrupt(tinyrl_t * this, int key)
-{
-	tinyrl_crlf(this);
-	tinyrl_delete_text(this, 0, this->end);
-	this->done = BOOL_TRUE;
-	/* keep the compiler happy */
-	key = key;
-
-	return BOOL_TRUE;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_start_of_line(tinyrl_t * this, int key)
-{
-	/* set the insertion point to the start of the line */
-	this->point = 0;
-	/* keep the compiler happy */
-	key = key;
-	return BOOL_TRUE;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_end_of_line(tinyrl_t * this, int key)
-{
-	/* set the insertion point to the end of the line */
-	this->point = this->end;
-	/* keep the compiler happy */
-	key = key;
-	return BOOL_TRUE;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_kill(tinyrl_t * this, int key)
-{
-	/* release any old kill string */
-	lub_string_free(this->kill_string);
-
-	/* store the killed string */
-	this->kill_string = lub_string_dup(&this->buffer[this->point]);
-
-	/* delete the text to the end of the line */
-	tinyrl_delete_text(this, this->point, this->end);
-	/* keep the compiler happy */
-	key = key;
-	return BOOL_TRUE;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_yank(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	if (this->kill_string) {
-		/* insert the kill string at the current insertion point */
-		result = tinyrl_insert_text(this, this->kill_string);
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_crlf(tinyrl_t * this, int key)
-{
-	tinyrl_crlf(this);
-	this->done = BOOL_TRUE;
-	/* keep the compiler happy */
-	key = key;
-	return BOOL_TRUE;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_up(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	tinyrl_history_entry_t *entry = NULL;
-	if (this->line == this->buffer) {
-		/* go to the last history entry */
-		entry = tinyrl_history_getlast(this->history, &this->hist_iter);
-	} else {
-		/* already traversing the history list so get previous */
-		entry = tinyrl_history_getprevious(&this->hist_iter);
-	}
-	if (entry) {
-		/* display the entry moving the insertion point
-		 * to the end of the line 
-		 */
-		this->line = tinyrl_history_entry__get_line(entry);
-		this->point = this->end = strlen(this->line);
-		result = BOOL_TRUE;
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_down(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	if (this->line != this->buffer) {
-		/* we are not already at the bottom */
-		/* the iterator will have been set up by the key_up() function */
-		tinyrl_history_entry_t *entry =
-		    tinyrl_history_getnext(&this->hist_iter);
-		if (!entry) {
-			/* nothing more in the history list */
-			this->line = this->buffer;
-		} else {
-			this->line = tinyrl_history_entry__get_line(entry);
-		}
-		/* display the entry moving the insertion point
-		 * to the end of the line 
-		 */
-		this->point = this->end = strlen(this->line);
-		result = BOOL_TRUE;
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_left(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	if (this->point > 0) {
-		this->point--;
-		utf8_point_left(this);
-		result = BOOL_TRUE;
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_right(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	if (this->point < this->end) {
-		this->point++;
-		utf8_point_right(this);
-		result = BOOL_TRUE;
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_backspace(tinyrl_t *this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	if (this->point) {
-		unsigned int end = --this->point;
-		utf8_point_left(this);
-		tinyrl_delete_text(this, this->point, end);
-		result = BOOL_TRUE;
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_backword(tinyrl_t *this, int key)
-{
-	bool_t result = BOOL_FALSE;
-
-    /* remove current whitespace before cursor */
-	while (this->point > 0 && isspace(this->line[this->point - 1]))
-        tinyrl_key_backspace(this, KEY_BS);
-
-    /* delete word before cusor */
-	while (this->point > 0 && !isspace(this->line[this->point - 1]))
-        tinyrl_key_backspace(this, KEY_BS);
-
-	result = BOOL_TRUE;
-
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_delete(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	if (this->point < this->end) {
-		unsigned int begin = this->point++;
-		utf8_point_right(this);
-		tinyrl_delete_text(this, begin, this->point - 1);
-		result = BOOL_TRUE;
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_clear_screen(tinyrl_t * this, int key)
-{
-	tinyrl_vt100_clear_screen(this->term);
-	tinyrl_vt100_cursor_home(this->term);
-	tinyrl_reset_line_state(this);
-
-	/* keep the compiler happy */
-	key = key;
-	this = this;
-	return BOOL_TRUE;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_erase_line(tinyrl_t * this, int key)
-{
-	unsigned int end;
-
-	/* release any old kill string */
-	lub_string_free(this->kill_string);
-
-	if (!this->point) {
-		this->kill_string = NULL;
-		return BOOL_TRUE;
-	}
-
-	end = this->point - 1;
-
-	/* store the killed string */
-	this->kill_string = malloc(this->point + 1);
-	memcpy(this->kill_string, this->buffer, this->point);
-	this->kill_string[this->point] = '\0';
-
-	/* delete the text from the start of the line */
-	tinyrl_delete_text(this, 0, end);
-	this->point = 0;
-
-	/* keep the compiler happy */
-	key = key;
-	this = this;
-
-	return BOOL_TRUE;
-}/*-------------------------------------------------------- */
-
-static bool_t tinyrl_escape_seq(tinyrl_t *this, const char *esc_seq)
-{
-	int key = 0;
-	bool_t result = BOOL_FALSE;
-
-	switch (tinyrl_vt100_escape_decode(this->term, esc_seq)) {
-	case tinyrl_vt100_CURSOR_UP:
-		result = tinyrl_key_up(this, key);
-		break;
-	case tinyrl_vt100_CURSOR_DOWN:
-		result = tinyrl_key_down(this, key);
-		break;
-	case tinyrl_vt100_CURSOR_LEFT:
-		result = tinyrl_key_left(this, key);
-		break;
-	case tinyrl_vt100_CURSOR_RIGHT:
-		result = tinyrl_key_right(this, key);
-		break;
-	case tinyrl_vt100_HOME:
-		result = tinyrl_key_start_of_line(this,key);
-		break;
-	case tinyrl_vt100_END:
-		result = tinyrl_key_end_of_line(this,key);
-		break;
-	case tinyrl_vt100_DELETE:
-		result = tinyrl_key_delete(this,key);
-		break;
-	case tinyrl_vt100_INSERT:
-	case tinyrl_vt100_PGDOWN:
-	case tinyrl_vt100_PGUP:
-	case tinyrl_vt100_UNKNOWN:
-		break;
-	}
-
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static bool_t tinyrl_key_tab(tinyrl_t * this, int key)
-{
-	bool_t result = BOOL_FALSE;
-	tinyrl_match_e status = tinyrl_complete_with_extensions(this);
-
-	switch (status) {
-	case TINYRL_COMPLETED_MATCH:
-	case TINYRL_MATCH:
-		/* everything is OK with the world... */
-		result = tinyrl_insert_text(this, " ");
-		break;
-	case TINYRL_NO_MATCH:
-	case TINYRL_MATCH_WITH_EXTENSIONS:
-	case TINYRL_AMBIGUOUS:
-	case TINYRL_COMPLETED_AMBIGUOUS:
-		/* oops don't change the result and let the bell ring */
-		break;
-	}
-	/* keep the compiler happy */
-	key = key;
-	return result;
-}
-
-/*-------------------------------------------------------- */
-static void tinyrl_fini(tinyrl_t * this)
-{
-	/* delete the history session */
-	tinyrl_history_delete(this->history);
-
-	/* delete the terminal session */
-	tinyrl_vt100_delete(this->term);
-
-	/* free up any dynamic strings */
-	lub_string_free(this->buffer);
-	lub_string_free(this->kill_string);
-	lub_string_free(this->last_buffer);
-	lub_string_free(this->prompt);
-}
-
-/*-------------------------------------------------------- */
-static void tinyrl_init(tinyrl_t * this, FILE * istream, FILE * ostream,
-	unsigned int stifle, tinyrl_completion_func_t * complete_fn)
-{
-	int i;
-
-	for (i = 0; i < NUM_HANDLERS; i++) {
-		this->handlers[i] = tinyrl_key_default;
-	}
-	/* Default handlers */
-	this->handlers[KEY_CR] = tinyrl_key_crlf;
-	this->handlers[KEY_LF] = tinyrl_key_crlf;
-	this->handlers[KEY_ETX] = tinyrl_key_interrupt;
-	this->handlers[KEY_DEL] = tinyrl_key_backspace;
-	this->handlers[KEY_BS] = tinyrl_key_backspace;
-	this->handlers[KEY_EOT] = tinyrl_key_delete;
-	this->handlers[KEY_FF] = tinyrl_key_clear_screen;
-	this->handlers[KEY_NAK] = tinyrl_key_erase_line;
-	this->handlers[KEY_SOH] = tinyrl_key_start_of_line;
-	this->handlers[KEY_ENQ] = tinyrl_key_end_of_line;
-	this->handlers[KEY_VT] = tinyrl_key_kill;
-	this->handlers[KEY_EM] = tinyrl_key_yank;
-	this->handlers[KEY_HT] = tinyrl_key_tab;
-	this->handlers[KEY_ETB] = tinyrl_key_backword;
-
-	this->line = NULL;
-	this->max_line_length = 0;
-	this->prompt = NULL;
-	this->prompt_size = 0;
-	this->buffer = NULL;
-	this->buffer_size = 0;
-	this->done = BOOL_FALSE;
-	this->completion_over = BOOL_FALSE;
-	this->point = 0;
-	this->end = 0;
-	this->attempted_completion_function = complete_fn;
-	this->timeout_fn = tinyrl_timeout_default;
-	this->keypress_fn = NULL;
-	this->hotkey_fn = NULL;
-	this->state = 0;
-	this->kill_string = NULL;
-	this->echo_char = '\0';
-	this->echo_enabled = BOOL_TRUE;
-	this->last_buffer = NULL;
-	this->last_point = 0;
-	this->last_line_size = 0;
-	this->utf8 = BOOL_FALSE;
-
-	/* create the vt100 terminal */
-	this->term = tinyrl_vt100_new(NULL, ostream);
-	tinyrl__set_istream(this, istream);
-	this->last_width = tinyrl_vt100__get_width(this->term);
-
-	/* create the history */
-	this->history = tinyrl_history_new(stifle);
-}
-
-/*-------------------------------------------------------- */
-int tinyrl_printf(const tinyrl_t * this, const char *fmt, ...)
-{
-	va_list args;
-	int len;
-
-	va_start(args, fmt);
-	len = tinyrl_vt100_vprintf(this->term, fmt, args);
-	va_end(args);
-
-	return len;
-}
-
-/*-------------------------------------------------------- */
-void tinyrl_delete(tinyrl_t * this)
-{
-	assert(this);
-	if (this) {
-		/* let the object tidy itself up */
-		tinyrl_fini(this);
-
-		/* release the memory associate with this instance */
-		free(this);
-	}
-}
-
-/*-------------------------------------------------------- */
-
-/*#####################################
- * EXPORTED INTERFACE
- *##################################### */
-/*----------------------------------------------------------------------- */
-int tinyrl_getchar(const tinyrl_t * this)
-{
-	return tinyrl_vt100_getchar(this->term);
-}
-
-/*----------------------------------------------------------------------- */
-static void tinyrl_internal_print(const tinyrl_t * this, const char *text)
-{
-	if (this->echo_enabled) {
-		/* simply echo the line */
-		tinyrl_vt100_printf(this->term, "%s", text);
-	} else {
-		/* replace the line with echo char if defined */
-		if (this->echo_char) {
-			unsigned int i = strlen(text);
-			while (i--) {
-				tinyrl_vt100_printf(this->term, "%c",
-					this->echo_char);
-			}
-		}
-	}
-}
-
-/*----------------------------------------------------------------------- */
-static void tinyrl_internal_position(const tinyrl_t *this, int prompt_len,
-	int line_len, unsigned int width)
-{
-	int rows, cols;
-
-	rows = ((line_len + prompt_len) / width) - (prompt_len / width);
-	cols = ((line_len + prompt_len) % width) - (prompt_len % width);
-	if (cols > 0)
-		tinyrl_vt100_cursor_back(this->term, cols);
-	else if (cols < 0)
-		tinyrl_vt100_cursor_forward(this->term, -cols);
-	if (rows > 0)
-		tinyrl_vt100_cursor_up(this->term, rows);
-	else if (rows < 0)
-		tinyrl_vt100_cursor_down(this->term, -rows);
-}
-
-/*-------------------------------------------------------- */
-/* Jump to first free line after current multiline input   */
-void tinyrl_multi_crlf(const tinyrl_t * this)
-{
-	unsigned int line_size = strlen(this->last_buffer);
-	unsigned int line_len = utf8_nsyms(this, this->last_buffer, line_size);
-	unsigned int count = utf8_nsyms(this, this->last_buffer, this->last_point);
-
-	tinyrl_internal_position(this, this->prompt_len + line_len,
-		- (line_len - count), this->last_width);
-	tinyrl_crlf(this);
-	tinyrl_vt100_oflush(this->term);
-}
-
-/*----------------------------------------------------------------------- */
-void tinyrl_redisplay(tinyrl_t * this)
-{
-	unsigned int line_size = strlen(this->line);
-	unsigned int line_len = utf8_nsyms(this, this->line, line_size);
-	unsigned int width = tinyrl_vt100__get_width(this->term);
-	unsigned int count, eq_chars = 0;
-	int cols;
-
-	/* Prepare print position */
-	if (this->last_buffer && (width == this->last_width)) {
-		unsigned int eq_len = 0;
-		/* If line and last line have the equal chars at begining */
-		eq_chars = lub_string_equal_part(this->line, this->last_buffer,
-			this->utf8);
-		eq_len = utf8_nsyms(this, this->last_buffer, eq_chars);
-		count = utf8_nsyms(this, this->last_buffer, this->last_point);
-		tinyrl_internal_position(this, this->prompt_len + eq_len,
-			count - eq_len, width);
-	} else {
-		/* Prepare to resize */
-		if (width != this->last_width) {
-			tinyrl_vt100_next_line(this->term);
-			tinyrl_vt100_erase_down(this->term);
-		}
-		tinyrl_vt100_printf(this->term, "%s", this->prompt);
-	}
-
-	/* Print current line */
-	tinyrl_internal_print(this, this->line + eq_chars);
-	cols = (this->prompt_len + line_len) % width;
-	if (!cols && (line_size - eq_chars))
-		tinyrl_vt100_next_line(this->term);
-	/* Erase down if current line is shorter than previous one */
-	if (this->last_line_size > line_size)
-		tinyrl_vt100_erase_down(this->term);
-	/* Move the cursor to the insertion point */
-	if (this->point < line_size) {
-		unsigned int pre_len = utf8_nsyms(this,
-			this->line, this->point);
-		count = utf8_nsyms(this, this->line + this->point,
-			line_size - this->point);
-		tinyrl_internal_position(this, this->prompt_len + pre_len,
-			count, width);
-	}
-
-	/* Update the display */
-	tinyrl_vt100_oflush(this->term);
-
-	/* Save the last line buffer */
-	lub_string_free(this->last_buffer);
-	this->last_buffer = lub_string_dup(this->line);
-	this->last_point = this->point;
-	this->last_width = width;
-	this->last_line_size = line_size;
-}
-
-/*----------------------------------------------------------------------- */
-tinyrl_t *tinyrl_new(FILE * istream, FILE * ostream,
-	unsigned int stifle, tinyrl_completion_func_t * complete_fn)
-{
-	tinyrl_t *this = NULL;
-
-	this = malloc(sizeof(tinyrl_t));
-	if (this)
-		tinyrl_init(this, istream, ostream, stifle, complete_fn);
-
-	return this;
-}
-
-/*----------------------------------------------------------------------- */
-static char *internal_insertline(tinyrl_t * this, char *buffer)
-{
-	char *p;
-	char *s = buffer;
-
-	/* strip any spurious '\r' or '\n' */
-	if ((p = strchr(buffer, '\r')))
-		*p = '\0';
-	if ((p = strchr(buffer, '\n')))
-		*p = '\0';
-	/* skip any whitespace at the beginning of the line */
-	if (0 == this->point) {
-		while (*s && isspace(*s))
-			s++;
-	}
-	if (*s) {
-		/* append this string to the input buffer */
-		(void)tinyrl_insert_text(this, s);
-	}
-	/* echo the command to the output stream */
-	tinyrl_redisplay(this);
-
-	return s;
-}
-
-/*----------------------------------------------------------------------- */
-static char *internal_readline(tinyrl_t * this,
-	void *context, const char *str)
-{
-	FILE *istream = tinyrl_vt100__get_istream(this->term);
-	char *result = NULL;
-	int lerrno = 0;
-
-	this->done = BOOL_FALSE;
-	this->point = 0;
-	this->end = 0;
-	this->buffer = lub_string_dup("");
-	this->buffer_size = strlen(this->buffer);
-	this->line = this->buffer;
-	this->context = context;
-
-	/* Interactive session */
-	if (this->isatty && !str) {
-		unsigned int utf8_cont = 0; /* UTF-8 continue bytes */
-		unsigned int esc_cont = 0; /* Escape sequence continues */
-		char esc_seq[10]; /* Buffer for ESC sequence */
-		char *esc_p = esc_seq;
-
-		/* Set the terminal into raw mode */
-		tty_set_raw_mode(this);
-		tinyrl_reset_line_state(this);
-
-		while (!this->done) {
-			int key;
-
-			key = tinyrl_getchar(this);
-
-			/* Error || EOF || Timeout */
-			if (key < 0) {
-				if ((VT100_TIMEOUT == key) &&
-					!this->timeout_fn(this))
-					continue;
-				/* It's time to finish the session */
-				this->done = BOOL_TRUE;
-				this->line = NULL;
-				lerrno = ENOENT;
-				continue;
-			}
-
-			/* Real key pressed */
-			/* Common callback for any key */
-			if (this->keypress_fn)
-				this->keypress_fn(this, key);
-
-			/* Check for ESC sequence. It's a special case. */
-			if (!esc_cont && (key == KEY_ESC)) {
-				esc_cont = 1; /* Start ESC sequence */
-				esc_p = esc_seq;
-				continue;
-			}
-			if (esc_cont) {
-				/* Broken sequence */
-				if (esc_p >= (esc_seq + sizeof(esc_seq) - 1)) {
-					esc_cont = 0;
-					continue;
-				}
-				/* Dump the control sequence into sequence buffer
-				   ANSI standard control sequences will end
-				   with a character between 64 - 126 */
-				*esc_p = key & 0xff;
-				esc_p++;
-				/* This is an ANSI control sequence terminator code */
-				if ((key != '[') && (key > 63)) {
-					*esc_p = '\0';
-					tinyrl_escape_seq(this, esc_seq);
-					esc_cont = 0;
-					tinyrl_redisplay(this);
-				}
-				continue;
-			}
-
-			/* Call the handler for this key */
-			if (!this->handlers[key](this, key))
-				tinyrl_ding(this);
-			if (this->done) /* Some handler set the done flag */
-				continue; /* It will break the loop */
-
-			if (this->utf8) {
-				if (!(UTF8_7BIT_MASK & key)) /* ASCII char */
-					utf8_cont = 0;
-				else if (utf8_cont && (UTF8_10 == (key & UTF8_MASK))) /* Continue byte */
-					utf8_cont--;
-				else if (UTF8_11 == (key & UTF8_MASK)) { /* First byte of multibyte char */
-					/* Find out number of char's bytes */
-					int b = key;
-					utf8_cont = 0;
-					while ((utf8_cont < 6) && (UTF8_10 != (b & UTF8_MASK))) {
-						utf8_cont++;
-						b = b << 1;
-					}
-				}
-			}
-			/* For non UTF-8 encoding the utf8_cont is always 0.
-			   For UTF-8 it's 0 when one-byte symbol or we get
-			   all bytes for the current multibyte character. */
-			if (!utf8_cont)
-				tinyrl_redisplay(this);
-		}
-		/* If the last character in the line (other than NULL)
-		   is a space remove it. */
-		if (this->end && this->line && isspace(this->line[this->end - 1]))
-			tinyrl_delete_text(this, this->end - 1, this->end);
-		/* Restores the terminal mode */
-		tty_restore_mode(this);
-
-	/* Non-interactive session */
-	} else {
-		char *s = NULL, buffer[80];
-		size_t len = sizeof(buffer);
-		char *tmp = NULL;
-
-		/* manually reset the line state without redisplaying */
-		lub_string_free(this->last_buffer);
-		this->last_buffer = NULL;
-
-		if (str) {
-			tmp = lub_string_dup(str);
-			internal_insertline(this, tmp);
-		} else {
-			while (istream && (sizeof(buffer) == len) &&
-				(s = fgets(buffer, sizeof(buffer), istream))) {
-				s = internal_insertline(this, buffer);
-				len = strlen(buffer) + 1; /* account for the '\0' */
-			}
-			if (!s || ((this->line[0] == '\0') && feof(istream))) {
-				/* time to finish the session */
-				this->line = NULL;
-				lerrno = ENOENT;
-			}
-		}
-
-		/*
-		 * check against fgets returning null as either error or end of file.
-		 * This is a measure to stop potential task spin on encountering an
-		 * error from fgets.
-		 */
-		if (this->line && !this->handlers[KEY_LF](this, KEY_LF)) {
-			/* an issue has occured */
-			this->line = NULL;
-			lerrno = ENOEXEC;
-		}
-		if (str)
-			lub_string_free(tmp);
-	}
-	/*
-	 * duplicate the string for return to the client 
-	 * we have to duplicate as we may be referencing a
-	 * history entry or our internal buffer
-	 */
-	result = this->line ? lub_string_dup(this->line) : NULL;
-
-	/* free our internal buffer */
-	free(this->buffer);
-	this->buffer = NULL;
-
-	if (!result)
-		errno = lerrno; /* get saved errno */
-	return result;
-}
-
-/*----------------------------------------------------------------------- */
-char *tinyrl_readline(tinyrl_t * this, void *context)
-{
-	return internal_readline(this, context, NULL);
-}
-
-/*----------------------------------------------------------------------- */
-char *tinyrl_forceline(tinyrl_t * this, void *context, const char *line)
-{
-	return internal_readline(this, context, line);
-}
-
-/*----------------------------------------------------------------------- */
-/*
- * Ensure that buffer has enough space to hold len characters,
- * possibly reallocating it if necessary. The function returns BOOL_TRUE
- * if the line is successfully extended, BOOL_FALSE if not.
- */
-bool_t tinyrl_extend_line_buffer(tinyrl_t * this, unsigned int len)
-{
-	bool_t result = BOOL_TRUE;
-	char *new_buffer;
-	size_t new_len = len;
-
-	if (this->buffer_size >= len)
-		return result;
-
-	/*
-	 * What we do depends on whether we are limited by
-	 * memory or a user imposed limit.
-	 */
-	if (this->max_line_length == 0) {
-		/* make sure we don't realloc too often */
-		if (new_len < this->buffer_size + 10)
-			new_len = this->buffer_size + 10;
-		/* leave space for terminator */
-		new_buffer = realloc(this->buffer, new_len + 1);
-
-		if (!new_buffer) {
-			tinyrl_ding(this);
-			result = BOOL_FALSE;
-		} else {
-			this->buffer_size = new_len;
-			this->line = this->buffer = new_buffer;
-		}
-	} else {
-		if (new_len < this->max_line_length) {
-
-			/* Just reallocate once to the max size */
-			new_buffer = realloc(this->buffer,
-				this->max_line_length);
-
-			if (!new_buffer) {
-				tinyrl_ding(this);
-				result = BOOL_FALSE;
-			} else {
-				this->buffer_size =
-					this->max_line_length - 1;
-				this->line = this->buffer = new_buffer;
-			}
-		} else {
-			tinyrl_ding(this);
-			result = BOOL_FALSE;
-		}
-	}
-
-	return result;
-}
-
-/*----------------------------------------------------------------------- */
-/*
- * Insert text into the line at the current cursor position.
- */
-bool_t tinyrl_insert_text(tinyrl_t * this, const char *text)
-{
-	unsigned int delta = strlen(text);
-
-	/*
-	 * If the client wants to change the line ensure that the line and buffer
-	 * references are in sync
-	 */
-	changed_line(this);
-
-	if ((delta + this->end) > (this->buffer_size)) {
-		/* extend the current buffer */
-		if (BOOL_FALSE ==
-			tinyrl_extend_line_buffer(this, this->end + delta))
-			return BOOL_FALSE;
-	}
-
-	if (this->point < this->end) {
-		/* move the current text to the right (including the terminator) */
-		memmove(&this->buffer[this->point + delta],
-			&this->buffer[this->point],
-			(this->end - this->point) + 1);
-	} else {
-		/* terminate the string */
-		this->buffer[this->end + delta] = '\0';
-	}
-
-	/* insert the new text */
-	strncpy(&this->buffer[this->point], text, delta);
-
-	/* now update the indexes */
-	this->point += delta;
-	this->end += delta;
-
-	return BOOL_TRUE;
-}
-
-/*----------------------------------------------------------------------- */
-/* 
- * A convenience function for displaying a list of strings in columnar
- * format on Readline's output stream. matches is the list of strings,
- * in argv format, such as a list of completion matches. len is the number
- * of strings in matches, and max is the length of the longest string in matches.
- * This function uses the setting of print-completions-horizontally to select
- * how the matches are displayed
- */
-void tinyrl_display_matches(const tinyrl_t *this,
-	char *const *matches, unsigned int len, size_t max)
-{
-	unsigned int width = tinyrl_vt100__get_width(this->term);
-	unsigned int cols, rows;
-
-	/* Find out column and rows number */
-	if (max < width)
-		cols = (width + 1) / (max + 1); /* allow for a space between words */
-	else
-		cols = 1;
-	rows = len / cols + 1;
-
-	assert(matches);
-	if (matches) {
-		unsigned int r, c;
-		len--, matches++; /* skip the subtitution string */
-		/* Print out a table of completions */
-		for (r = 0; r < rows && len; r++) {
-			for (c = 0; c < cols && len; c++) {
-				const char *match = *matches++;
-				len--;
-				if ((c + 1) == cols) /* Last str in row */
-					tinyrl_vt100_printf(this->term, "%s",
-						match);
-				else
-					tinyrl_vt100_printf(this->term, "%-*s ",
-						max, match);
-			}
-			tinyrl_crlf(this);
-		}
-	}
-}
-
-/*----------------------------------------------------------------------- */
-/*
- * Delete the text between start and end in the current line. (inclusive)
- * This adjusts the rl_point and rl_end indexes appropriately.
- */
-void tinyrl_delete_text(tinyrl_t * this, unsigned int start, unsigned int end)
-{
-	unsigned int delta;
-
-	/*
-	 * If the client wants to change the line ensure that the line and buffer
-	 * references are in sync
-	 */
-	changed_line(this);
-
-	/* make sure we play it safe */
-	if (start > end) {
-		unsigned int tmp = end;
-		start = end;
-		end = tmp;
-	}
-	if (end > this->end)
-		end = this->end;
-
-	delta = (end - start) + 1;
-
-	/* move any text which is left */
-	memmove(&this->buffer[start],
-		&this->buffer[start + delta], this->end - end);
-
-	/* now adjust the indexs */
-	if (this->point >= start) {
-		if (this->point > end) {
-			/* move the insertion point back appropriately */
-			this->point -= delta;
-		} else {
-			/* move the insertion point to the start */
-			this->point = start;
-		}
-	}
-	if (this->end > end)
-		this->end -= delta;
-	else
-		this->end = start;
-	/* put a terminator at the end of the buffer */
-	this->buffer[this->end] = '\0';
-}
-
-/*----------------------------------------------------------------------- */
-bool_t tinyrl_bind_key(tinyrl_t * this, int key, tinyrl_key_func_t * fn)
-{
-	bool_t result = BOOL_FALSE;
-
-	if ((key >= 0) && (key < 256)) {
-		/* set the key handling function */
-		this->handlers[key] = fn;
-		result = BOOL_TRUE;
-	}
-
-	return result;
-}
-
-/*-------------------------------------------------------- */
-/*
- * Returns an array of strings which is a list of completions for text.
- * If there are no completions, returns NULL. The first entry in the
- * returned array is the substitution for text. The remaining entries
- * are the possible completions. The array is terminated with a NULL pointer.
- *
- * entry_func is a function of two args, and returns a char *. 
- * The first argument is text. The second is a state argument;
- * it is zero on the first call, and non-zero on subsequent calls.
- * entry_func returns a NULL pointer to the caller when there are no 
- * more matches. 
- */
-char **tinyrl_completion(tinyrl_t * this,
-	const char *line, unsigned int start, unsigned int end,
-	tinyrl_compentry_func_t * entry_func)
-{
-	unsigned int state = 0;
-	size_t size = 1;
-	unsigned int offset = 1; /* Need at least one entry for the substitution */
-	char **matches = NULL;
-	char *match;
-	/* duplicate the string upto the insertion point */
-	char *text = lub_string_dupn(line, end);
-
-	/* now try and find possible completions */
-	while ((match = entry_func(this, text, start, state++))) {
-		if (size == offset) {
-			/* resize the buffer if needed - the +1 is for the NULL terminator */
-			size += 10;
-			matches =
-			    realloc(matches, (sizeof(char *) * (size + 1)));
-		}
-		/* not much we can do... */
-		if (!matches)
-			break;
-		matches[offset] = match;
-
-		/*
-		 * augment the substitute string with this entry
-		 */
-		if (1 == offset) {
-			/* let's be optimistic */
-			matches[0] = lub_string_dup(match);
-		} else {
-			char *p = matches[0];
-			size_t match_len = strlen(p);
-			/* identify the common prefix */
-			while ((tolower(*p) == tolower(*match)) && match_len--) {
-				p++, match++;
-			}
-			/* terminate the prefix string */
-			*p = '\0';
-		}
-		offset++;
-	}
-	/* be a good memory citizen */
-	lub_string_free(text);
-
-	if (matches)
-		matches[offset] = NULL;
-	return matches;
-}
-
-/*-------------------------------------------------------- */
-void tinyrl_delete_matches(char **this)
-{
-	char **matches = this;
-	while (*matches) {
-		/* release the memory for each contained string */
-		free(*matches++);
-	}
-	/* release the memory for the array */
-	free(this);
-}
-
-/*-------------------------------------------------------- */
-void tinyrl_crlf(const tinyrl_t * this)
-{
-	tinyrl_vt100_printf(this->term, "\n");
-}
-
-/*-------------------------------------------------------- */
-/*
- * Ring the terminal bell, obeying the setting of bell-style.
- */
-void tinyrl_ding(const tinyrl_t * this)
-{
-	tinyrl_vt100_ding(this->term);
-}
-
-/*-------------------------------------------------------- */
-void tinyrl_reset_line_state(tinyrl_t * this)
-{
-	lub_string_free(this->last_buffer);
-	this->last_buffer = NULL;
-	this->last_line_size = 0;
-
-	tinyrl_redisplay(this);
-}
-
-/*-------------------------------------------------------- */
-void tinyrl_replace_line(tinyrl_t * this, const char *text, int clear_undo)
-{
-	size_t new_len = strlen(text);
-
-	/* ignored for now */
-	clear_undo = clear_undo;
-
-	/* ensure there is sufficient space */
-	if (tinyrl_extend_line_buffer(this, new_len)) {
-
-		/* overwrite the current contents of the buffer */
-		strcpy(this->buffer, text);
-
-		/* set the insert point and end point */
-		this->point = this->end = new_len;
-	}
-	tinyrl_redisplay(this);
-}
-
-/*-------------------------------------------------------- */
-static tinyrl_match_e
-tinyrl_do_complete(tinyrl_t * this, bool_t with_extensions)
-{
-	tinyrl_match_e result = TINYRL_NO_MATCH;
-	char **matches = NULL;
-	unsigned int start, end;
-	bool_t completion = BOOL_FALSE;
-	bool_t prefix = BOOL_FALSE;
-	int i = 0;
-
-	/* find the start and end of the current word */
-	start = end = this->point;
-	while (start && !isspace(this->line[start - 1]))
-		start--;
-
-	if (this->attempted_completion_function) {
-		this->completion_over = BOOL_FALSE;
-		this->completion_error_over = BOOL_FALSE;
-		/* try and complete the current line buffer */
-		matches = this->attempted_completion_function(this,
-			this->line, start, end);
-	}
-	if (!matches && (BOOL_FALSE == this->completion_over)) {
-		/* insert default completion call here... */
-	}
-	if (!matches)
-		return result;
-
-	/* identify and insert a common prefix if there is one */
-	if (0 != strncmp(matches[0], &this->line[start],
-		strlen(matches[0]))) {
-		/*
-		 * delete the original text not including
-		 * the current insertion point character
-		 */
-		if (this->end != end)
-			end--;
-		tinyrl_delete_text(this, start, end);
-		if (BOOL_FALSE == tinyrl_insert_text(this, matches[0]))
-			return TINYRL_NO_MATCH;
-		completion = BOOL_TRUE;
-	}
-	for (i = 1; matches[i]; i++) {
-		/* this is just a prefix string */
-		if (0 == lub_string_nocasecmp(matches[0], matches[i]))
-			prefix = BOOL_TRUE;
-	}
-	/* is there more than one completion? */
-	if (matches[2]) {
-		char **tmp = matches;
-		unsigned int max, len;
-		max = len = 0;
-		while (*tmp) {
-			size_t size = strlen(*tmp++);
-			len++;
-			if (size > max)
-				max = size;
-		}
-		if (completion)
-			result = TINYRL_COMPLETED_AMBIGUOUS;
-		else if (prefix)
-			result = TINYRL_MATCH_WITH_EXTENSIONS;
-		else
-			result = TINYRL_AMBIGUOUS;
-		if (with_extensions || !prefix) {
-			/* Either we always want to show extensions or
-			 * we haven't been able to complete the current line
-			 * and there is just a prefix, so let the user see the options
-			 */
-			tinyrl_crlf(this);
-			tinyrl_display_matches(this, matches, len, max);
-			tinyrl_reset_line_state(this);
-		}
-	} else {
-		result = completion ?
-			TINYRL_COMPLETED_MATCH : TINYRL_MATCH;
-	}
-	/* free the memory */
-	tinyrl_delete_matches(matches);
-	/* redisplay the line */
-	tinyrl_redisplay(this);
-
-	return result;
-}
-
-/*-------------------------------------------------------- */
-tinyrl_match_e tinyrl_complete_with_extensions(tinyrl_t * this)
-{
-	return tinyrl_do_complete(this, BOOL_TRUE);
-}
-
-/*-------------------------------------------------------- */
-tinyrl_match_e tinyrl_complete(tinyrl_t * this)
-{
-	return tinyrl_do_complete(this, BOOL_FALSE);
-}
-
-/*-------------------------------------------------------- */
-void *tinyrl__get_context(const tinyrl_t * this)
-{
-	return this->context;
-}
-
-/*--------------------------------------------------------- */
-const char *tinyrl__get_line(const tinyrl_t * this)
-{
-	return this->line;
-}
-
-/*--------------------------------------------------------- */
-tinyrl_history_t *tinyrl__get_history(const tinyrl_t * this)
-{
-	return this->history;
-}
-
-/*--------------------------------------------------------- */
-void tinyrl_completion_over(tinyrl_t * this)
-{
-	this->completion_over = BOOL_TRUE;
-}
-
-/*--------------------------------------------------------- */
-void tinyrl_completion_error_over(tinyrl_t * this)
-{
-	this->completion_error_over = BOOL_TRUE;
-}
-
-/*--------------------------------------------------------- */
-bool_t tinyrl_is_completion_error_over(const tinyrl_t * this)
-{
-	return this->completion_error_over;
-}
-
-/*--------------------------------------------------------- */
-void tinyrl_done(tinyrl_t * this)
-{
-	this->done = BOOL_TRUE;
-}
-
-/*--------------------------------------------------------- */
-void tinyrl_enable_echo(tinyrl_t * this)
-{
-	this->echo_enabled = BOOL_TRUE;
-}
-
-/*--------------------------------------------------------- */
-void tinyrl_disable_echo(tinyrl_t * this, char echo_char)
-{
-	this->echo_enabled = BOOL_FALSE;
-	this->echo_char = echo_char;
-}
-
-/*--------------------------------------------------------- */
-void tinyrl__set_istream(tinyrl_t * this, FILE * istream)
-{
-	tinyrl_vt100__set_istream(this->term, istream);
-	if (istream) {
-		int fd;
-		this->isatty = isatty(fileno(istream)) ? BOOL_TRUE : BOOL_FALSE;
-		/* Save terminal settings to restore on exit */
-		fd = fileno(istream);
-		tcgetattr(fd, &this->default_termios);
-	} else
-		this->isatty = BOOL_FALSE;
-}
-
-/*-------------------------------------------------------- */
-bool_t tinyrl__get_isatty(const tinyrl_t * this)
-{
-	return this->isatty;
-}
-
-/*-------------------------------------------------------- */
-FILE *tinyrl__get_istream(const tinyrl_t * this)
-{
-	return tinyrl_vt100__get_istream(this->term);
-}
-
-/*-------------------------------------------------------- */
-FILE *tinyrl__get_ostream(const tinyrl_t * this)
-{
-	return tinyrl_vt100__get_ostream(this->term);
-}
-
-/*-------------------------------------------------------- */
-const char *tinyrl__get_prompt(const tinyrl_t * this)
-{
-	return this->prompt;
-}
-
-/*-------------------------------------------------------- */
-void tinyrl__set_prompt(tinyrl_t *this, const char *prompt)
-{
-	if (this->prompt) {
-		lub_string_free(this->prompt);
-		this->prompt_size = 0;
-		this->prompt_len = 0;
-	}
-	this->prompt = lub_string_dup(prompt);
-	if (this->prompt) {
-		this->prompt_size = strlen(this->prompt);
-		this->prompt_len = utf8_nsyms(this, this->prompt,
-			this->prompt_size);
-	}
-}
-
-/*-------------------------------------------------------- */
-bool_t tinyrl__get_utf8(const tinyrl_t * this)
-{
-	return this->utf8;
-}
-
-/*-------------------------------------------------------- */
-void tinyrl__set_utf8(tinyrl_t * this, bool_t utf8)
-{
-	this->utf8 = utf8;
-}
-
-/*-------------------------------------------------------- */
-void tinyrl__set_timeout(tinyrl_t *this, int timeout)
-{
-	tinyrl_vt100__set_timeout(this->term, timeout);
-}
-
-/*-------------------------------------------------------- */
-void tinyrl__set_timeout_fn(tinyrl_t *this,
-	tinyrl_timeout_fn_t *fn)
-{
-	this->timeout_fn = fn;
-}
-
-/*-------------------------------------------------------- */
-void tinyrl__set_keypress_fn(tinyrl_t *this,
-	tinyrl_keypress_fn_t *fn)
-{
-	this->keypress_fn = fn;
-}
-
-/*-------------------------------------------------------- */
-void tinyrl__set_hotkey_fn(tinyrl_t *this,
-	tinyrl_key_func_t *fn)
-{
-	this->hotkey_fn = fn;
-}
-
-/*-------------------------------------------------------- */
-bool_t tinyrl_is_quoting(const tinyrl_t * this)
-{
-	bool_t result = BOOL_FALSE;
-	/* count the quotes upto the current insertion point */
-	unsigned int i = 0;
-	while (i < this->point) {
-		if (result && (this->line[i] == '\\')) {
-			i++;
-			if (i >= this->point)
-				break;
-			i++;
-			continue;
-		}
-		if (this->line[i++] == '"') {
-			result = result ? BOOL_FALSE : BOOL_TRUE;
-		}
-	}
-	return result;
-}
-
-/*-------------------------------------------------------- */
-bool_t tinyrl_is_empty(const tinyrl_t *this)
-{
-	return (this->point == 0) ? BOOL_TRUE : BOOL_FALSE;
-}
-
-/*--------------------------------------------------------- */
-void tinyrl_limit_line_length(tinyrl_t * this, unsigned int length)
-{
-	this->max_line_length = length;
-}
-
-/*--------------------------------------------------------- */
-extern unsigned int tinyrl__get_width(const tinyrl_t *this)
-{
-	return tinyrl_vt100__get_width(this->term);
-}
-
-/*--------------------------------------------------------- */
-extern unsigned int tinyrl__get_height(const tinyrl_t *this)
-{
-	return tinyrl_vt100__get_height(this->term);
-}
-
-/*----------------------------------------------------------*/
-int tinyrl__save_history(const tinyrl_t *this, const char *fname)
-{
-	return tinyrl_history_save(this->history, fname);
-}
-
-/*----------------------------------------------------------*/
-int tinyrl__restore_history(tinyrl_t *this, const char *fname)
-{
-	return tinyrl_history_restore(this->history, fname);
-}
-
-/*----------------------------------------------------------*/
-void tinyrl__stifle_history(tinyrl_t *this, unsigned int stifle)
-{
-	tinyrl_history_stifle(this->history, stifle);
-}
-/*--------------------------------------------------------- */

+ 7 - 19
tinyrl/tinyrl.h

@@ -1,21 +1,13 @@
- /**
-\ingroup tinyrl
-\defgroup tinyrl_class tinyrl
-@{
-
-\brief This class provides instances which are capable of handling user input
-from a CLI in a "readline" like fashion.
-
-*/
 #ifndef _tinyrl_tinyrl_h
 #define _tinyrl_tinyrl_h
 
 #include <stdio.h>
-#include "lub/types.h"
-#include "lub/c_decl.h"
-#include "tinyrl/history.h"
 
-_BEGIN_C_DECL typedef struct _tinyrl tinyrl_t;
+#include <faux/faux.h>
+
+C_DECL_BEGIN
+
+ typedef struct _tinyrl tinyrl_t;
 typedef enum {
     /**
      * no possible completions were found
@@ -78,7 +70,6 @@ extern int tinyrl_printf(const tinyrl_t * instance, const char *fmt, ...);
 
 extern void tinyrl_delete(tinyrl_t * instance);
 
-extern tinyrl_history_t *tinyrl__get_history(const tinyrl_t * instance);
 
 extern const char *tinyrl__get_prompt(const tinyrl_t * instance);
 extern void tinyrl__set_prompt(tinyrl_t *instance, const char *prompt);
@@ -219,10 +210,7 @@ extern void tinyrl_limit_line_length(
 
 extern unsigned tinyrl__get_width(const tinyrl_t *instance);
 extern unsigned tinyrl__get_height(const tinyrl_t *instance);
-extern int tinyrl__save_history(const tinyrl_t *instance, const char *fname);
-extern int tinyrl__restore_history(tinyrl_t *instance, const char *fname);
-extern void tinyrl__stifle_history(tinyrl_t *instance, unsigned int stifle);
 
-_END_C_DECL
+C_DECL_END
+
 #endif				/* _tinyrl_tinyrl_h */
-/** @} tinyrl_tinyrl */

+ 3 - 0
tinyrl/tinyrl/Makefile.am

@@ -0,0 +1,3 @@
+libtinyrl_la_SOURCES += \
+	tinyrl/tinyrl/utf8.c
+	tinyrl/tinyrl/tinyrl.c

+ 14 - 4
tinyrl/private.h → tinyrl/tinyrl/private.h

@@ -1,7 +1,17 @@
 #include <termios.h>
 
-#include "tinyrl/tinyrl.h"
+#include <faux/faux.h>
+
 #include "tinyrl/vt100.h"
+#include "tinyrl/hist.h"
+#include "tinyrl/tinyrl.h"
+
+
+ssize_t utf8_to_wchar(const char *sp, unsigned long *sym_out);
+bool_t utf8_wchar_is_cjk(unsigned long sym);
+char *utf8_move_left(const char *line, char *cur_pos);
+char *utf8_move_right(const char *line, char *cur_pos);
+
 
 /* define the class member data and virtual methods */
 struct _tinyrl {
@@ -27,9 +37,9 @@ struct _tinyrl {
 	tinyrl_key_func_t *handlers[NUM_HANDLERS];
 	tinyrl_key_func_t *hotkey_fn;
 
-	tinyrl_history_t *history;
-	tinyrl_history_iterator_t hist_iter;
-	tinyrl_vt100_t *term;
+//	tinyrl_history_t *history;
+//	tinyrl_history_iterator_t hist_iter;
+	vt100_t *term;
 	void *context;		/* context supplied by caller
 				 * to tinyrl_readline()
 				 */

+ 1473 - 0
tinyrl/tinyrl/tinyrl.c

@@ -0,0 +1,1473 @@
+/*
+ * tinyrl.c
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <faux/str.h>
+
+#include "private.h"
+
+
+static void tty_set_raw_mode(tinyrl_t *tinyrl)
+{
+	struct termios new_termios = {};
+	FILE *istream = NULL;
+	int fd = -1;
+
+	if (!tinyrl)
+		return;
+	istream = vt100_istream(tinyrl->term);
+	if (!istream)
+		return;
+	fd = fileno(istream);
+	if (tcgetattr(fd, &new_termios) < 0)
+		return;
+	new_termios.c_iflag = 0;
+	new_termios.c_oflag = OPOST | ONLCR;
+	new_termios.c_lflag = 0;
+	new_termios.c_cc[VMIN] = 1;
+	new_termios.c_cc[VTIME] = 0;
+	// Mode switch
+	tcsetattr(fd, TCSADRAIN, &new_termios);
+}
+
+
+static void tty_restore_mode(tinyrl_t *tinyrl)
+{
+	FILE *istream = NULL;
+	int fd = -1;
+
+	istream = vt100_istream(tinyrl->term);
+	if (!istream)
+		return;
+	fd = fileno(istream);
+	// Do the mode switch
+	tcsetattr(fd, TCSADRAIN, &tinyrl->default_termios);
+}
+
+#if 0
+
+/*----------------------------------------------------------------------- */
+/*
+tinyrl is called whenever a line is edited in any way.
+It signals that if we are currently viewing a history line we should transfer it
+to the current buffer
+*/
+static void changed_line(tinyrl_t * tinyrl)
+{
+	/* if the current line is not our buffer then make it so */
+	if (tinyrl->line != tinyrl->buffer) {
+		/* replace the current buffer with the new details */
+		free(tinyrl->buffer);
+		tinyrl->line = tinyrl->buffer = lub_string_dup(tinyrl->line);
+		tinyrl->buffer_size = strlen(tinyrl->buffer);
+		assert(tinyrl->line);
+	}
+}
+
+/*----------------------------------------------------------------------- */
+static int tinyrl_timeout_default(tinyrl_t *tinyrl)
+{
+	tinyrl = tinyrl; /* Happy compiler */
+
+	/* Return -1 to close session on timeout */
+	return -1;
+}
+
+/*----------------------------------------------------------------------- */
+static bool_t tinyrl_key_default(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	if (key > 31) {
+		char tmp[2];
+		tmp[0] = (key & 0xFF), tmp[1] = '\0';
+		/* inject tinyrl text into the buffer */
+		result = tinyrl_insert_text(tinyrl, tmp);
+	} else {
+		/* Call the external hotkey analyzer */
+		if (tinyrl->hotkey_fn)
+			tinyrl->hotkey_fn(tinyrl, key);
+	}
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_interrupt(tinyrl_t * tinyrl, int key)
+{
+	tinyrl_crlf(tinyrl);
+	tinyrl_delete_text(tinyrl, 0, tinyrl->end);
+	tinyrl->done = BOOL_TRUE;
+	/* keep the compiler happy */
+	key = key;
+
+	return BOOL_TRUE;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_start_of_line(tinyrl_t * tinyrl, int key)
+{
+	/* set the insertion point to the start of the line */
+	tinyrl->point = 0;
+	/* keep the compiler happy */
+	key = key;
+	return BOOL_TRUE;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_end_of_line(tinyrl_t * tinyrl, int key)
+{
+	/* set the insertion point to the end of the line */
+	tinyrl->point = tinyrl->end;
+	/* keep the compiler happy */
+	key = key;
+	return BOOL_TRUE;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_kill(tinyrl_t * tinyrl, int key)
+{
+	/* release any old kill string */
+	lub_string_free(tinyrl->kill_string);
+
+	/* store the killed string */
+	tinyrl->kill_string = lub_string_dup(&tinyrl->buffer[tinyrl->point]);
+
+	/* delete the text to the end of the line */
+	tinyrl_delete_text(tinyrl, tinyrl->point, tinyrl->end);
+	/* keep the compiler happy */
+	key = key;
+	return BOOL_TRUE;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_yank(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	if (tinyrl->kill_string) {
+		/* insert the kill string at the current insertion point */
+		result = tinyrl_insert_text(tinyrl, tinyrl->kill_string);
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_crlf(tinyrl_t * tinyrl, int key)
+{
+	tinyrl_crlf(tinyrl);
+	tinyrl->done = BOOL_TRUE;
+	/* keep the compiler happy */
+	key = key;
+	return BOOL_TRUE;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_up(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	tinyrl_history_entry_t *entry = NULL;
+	if (tinyrl->line == tinyrl->buffer) {
+		/* go to the last history entry */
+		entry = tinyrl_history_getlast(tinyrl->history, &tinyrl->hist_iter);
+	} else {
+		/* already traversing the history list so get previous */
+		entry = tinyrl_history_getprevious(&tinyrl->hist_iter);
+	}
+	if (entry) {
+		/* display the entry moving the insertion point
+		 * to the end of the line 
+		 */
+		tinyrl->line = tinyrl_history_entry__get_line(entry);
+		tinyrl->point = tinyrl->end = strlen(tinyrl->line);
+		result = BOOL_TRUE;
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_down(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	if (tinyrl->line != tinyrl->buffer) {
+		/* we are not already at the bottom */
+		/* the iterator will have been set up by the key_up() function */
+		tinyrl_history_entry_t *entry =
+		    tinyrl_history_getnext(&tinyrl->hist_iter);
+		if (!entry) {
+			/* nothing more in the history list */
+			tinyrl->line = tinyrl->buffer;
+		} else {
+			tinyrl->line = tinyrl_history_entry__get_line(entry);
+		}
+		/* display the entry moving the insertion point
+		 * to the end of the line 
+		 */
+		tinyrl->point = tinyrl->end = strlen(tinyrl->line);
+		result = BOOL_TRUE;
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_left(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	if (tinyrl->point > 0) {
+		tinyrl->point--;
+		utf8_point_left(tinyrl);
+		result = BOOL_TRUE;
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_right(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	if (tinyrl->point < tinyrl->end) {
+		tinyrl->point++;
+		utf8_point_right(tinyrl);
+		result = BOOL_TRUE;
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_backspace(tinyrl_t *tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	if (tinyrl->point) {
+		unsigned int end = --tinyrl->point;
+		utf8_point_left(tinyrl);
+		tinyrl_delete_text(tinyrl, tinyrl->point, end);
+		result = BOOL_TRUE;
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_backword(tinyrl_t *tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+
+    /* remove current whitespace before cursor */
+	while (tinyrl->point > 0 && isspace(tinyrl->line[tinyrl->point - 1]))
+        tinyrl_key_backspace(tinyrl, KEY_BS);
+
+    /* delete word before cusor */
+	while (tinyrl->point > 0 && !isspace(tinyrl->line[tinyrl->point - 1]))
+        tinyrl_key_backspace(tinyrl, KEY_BS);
+
+	result = BOOL_TRUE;
+
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_delete(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	if (tinyrl->point < tinyrl->end) {
+		unsigned int begin = tinyrl->point++;
+		utf8_point_right(tinyrl);
+		tinyrl_delete_text(tinyrl, begin, tinyrl->point - 1);
+		result = BOOL_TRUE;
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_clear_screen(tinyrl_t * tinyrl, int key)
+{
+	tinyrl_vt100_clear_screen(tinyrl->term);
+	tinyrl_vt100_cursor_home(tinyrl->term);
+	tinyrl_reset_line_state(tinyrl);
+
+	/* keep the compiler happy */
+	key = key;
+	tinyrl = tinyrl;
+	return BOOL_TRUE;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_erase_line(tinyrl_t * tinyrl, int key)
+{
+	unsigned int end;
+
+	/* release any old kill string */
+	lub_string_free(tinyrl->kill_string);
+
+	if (!tinyrl->point) {
+		tinyrl->kill_string = NULL;
+		return BOOL_TRUE;
+	}
+
+	end = tinyrl->point - 1;
+
+	/* store the killed string */
+	tinyrl->kill_string = malloc(tinyrl->point + 1);
+	memcpy(tinyrl->kill_string, tinyrl->buffer, tinyrl->point);
+	tinyrl->kill_string[tinyrl->point] = '\0';
+
+	/* delete the text from the start of the line */
+	tinyrl_delete_text(tinyrl, 0, end);
+	tinyrl->point = 0;
+
+	/* keep the compiler happy */
+	key = key;
+	tinyrl = tinyrl;
+
+	return BOOL_TRUE;
+}/*-------------------------------------------------------- */
+
+static bool_t tinyrl_escape_seq(tinyrl_t *tinyrl, const char *esc_seq)
+{
+	int key = 0;
+	bool_t result = BOOL_FALSE;
+
+	switch (tinyrl_vt100_escape_decode(tinyrl->term, esc_seq)) {
+	case tinyrl_vt100_CURSOR_UP:
+		result = tinyrl_key_up(tinyrl, key);
+		break;
+	case tinyrl_vt100_CURSOR_DOWN:
+		result = tinyrl_key_down(tinyrl, key);
+		break;
+	case tinyrl_vt100_CURSOR_LEFT:
+		result = tinyrl_key_left(tinyrl, key);
+		break;
+	case tinyrl_vt100_CURSOR_RIGHT:
+		result = tinyrl_key_right(tinyrl, key);
+		break;
+	case tinyrl_vt100_HOME:
+		result = tinyrl_key_start_of_line(tinyrl,key);
+		break;
+	case tinyrl_vt100_END:
+		result = tinyrl_key_end_of_line(tinyrl,key);
+		break;
+	case tinyrl_vt100_DELETE:
+		result = tinyrl_key_delete(tinyrl,key);
+		break;
+	case tinyrl_vt100_INSERT:
+	case tinyrl_vt100_PGDOWN:
+	case tinyrl_vt100_PGUP:
+	case tinyrl_vt100_UNKNOWN:
+		break;
+	}
+
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static bool_t tinyrl_key_tab(tinyrl_t * tinyrl, int key)
+{
+	bool_t result = BOOL_FALSE;
+	tinyrl_match_e status = tinyrl_complete_with_extensions(tinyrl);
+
+	switch (status) {
+	case TINYRL_COMPLETED_MATCH:
+	case TINYRL_MATCH:
+		/* everything is OK with the world... */
+		result = tinyrl_insert_text(tinyrl, " ");
+		break;
+	case TINYRL_NO_MATCH:
+	case TINYRL_MATCH_WITH_EXTENSIONS:
+	case TINYRL_AMBIGUOUS:
+	case TINYRL_COMPLETED_AMBIGUOUS:
+		/* oops don't change the result and let the bell ring */
+		break;
+	}
+	/* keep the compiler happy */
+	key = key;
+	return result;
+}
+
+/*-------------------------------------------------------- */
+static void tinyrl_fini(tinyrl_t * tinyrl)
+{
+	/* delete the history session */
+	tinyrl_history_delete(tinyrl->history);
+
+	/* delete the terminal session */
+	tinyrl_vt100_delete(tinyrl->term);
+
+	/* free up any dynamic strings */
+	lub_string_free(tinyrl->buffer);
+	lub_string_free(tinyrl->kill_string);
+	lub_string_free(tinyrl->last_buffer);
+	lub_string_free(tinyrl->prompt);
+}
+
+/*-------------------------------------------------------- */
+static void tinyrl_init(tinyrl_t * tinyrl, FILE * istream, FILE * ostream,
+	unsigned int stifle, tinyrl_completion_func_t * complete_fn)
+{
+	int i;
+
+	for (i = 0; i < NUM_HANDLERS; i++) {
+		tinyrl->handlers[i] = tinyrl_key_default;
+	}
+	/* Default handlers */
+	tinyrl->handlers[KEY_CR] = tinyrl_key_crlf;
+	tinyrl->handlers[KEY_LF] = tinyrl_key_crlf;
+	tinyrl->handlers[KEY_ETX] = tinyrl_key_interrupt;
+	tinyrl->handlers[KEY_DEL] = tinyrl_key_backspace;
+	tinyrl->handlers[KEY_BS] = tinyrl_key_backspace;
+	tinyrl->handlers[KEY_EOT] = tinyrl_key_delete;
+	tinyrl->handlers[KEY_FF] = tinyrl_key_clear_screen;
+	tinyrl->handlers[KEY_NAK] = tinyrl_key_erase_line;
+	tinyrl->handlers[KEY_SOH] = tinyrl_key_start_of_line;
+	tinyrl->handlers[KEY_ENQ] = tinyrl_key_end_of_line;
+	tinyrl->handlers[KEY_VT] = tinyrl_key_kill;
+	tinyrl->handlers[KEY_EM] = tinyrl_key_yank;
+	tinyrl->handlers[KEY_HT] = tinyrl_key_tab;
+	tinyrl->handlers[KEY_ETB] = tinyrl_key_backword;
+
+	tinyrl->line = NULL;
+	tinyrl->max_line_length = 0;
+	tinyrl->prompt = NULL;
+	tinyrl->prompt_size = 0;
+	tinyrl->buffer = NULL;
+	tinyrl->buffer_size = 0;
+	tinyrl->done = BOOL_FALSE;
+	tinyrl->completion_over = BOOL_FALSE;
+	tinyrl->point = 0;
+	tinyrl->end = 0;
+	tinyrl->attempted_completion_function = complete_fn;
+	tinyrl->timeout_fn = tinyrl_timeout_default;
+	tinyrl->keypress_fn = NULL;
+	tinyrl->hotkey_fn = NULL;
+	tinyrl->state = 0;
+	tinyrl->kill_string = NULL;
+	tinyrl->echo_char = '\0';
+	tinyrl->echo_enabled = BOOL_TRUE;
+	tinyrl->last_buffer = NULL;
+	tinyrl->last_point = 0;
+	tinyrl->last_line_size = 0;
+	tinyrl->utf8 = BOOL_FALSE;
+
+	/* create the vt100 terminal */
+	tinyrl->term = tinyrl_vt100_new(NULL, ostream);
+	tinyrl__set_istream(tinyrl, istream);
+	tinyrl->last_width = tinyrl_vt100__get_width(tinyrl->term);
+
+	/* create the history */
+	tinyrl->history = tinyrl_history_new(stifle);
+}
+
+/*-------------------------------------------------------- */
+int tinyrl_printf(const tinyrl_t * tinyrl, const char *fmt, ...)
+{
+	va_list args;
+	int len;
+
+	va_start(args, fmt);
+	len = tinyrl_vt100_vprintf(tinyrl->term, fmt, args);
+	va_end(args);
+
+	return len;
+}
+
+/*-------------------------------------------------------- */
+void tinyrl_delete(tinyrl_t * tinyrl)
+{
+	assert(tinyrl);
+	if (tinyrl) {
+		/* let the object tidy itself up */
+		tinyrl_fini(tinyrl);
+
+		/* release the memory associate with tinyrl instance */
+		free(tinyrl);
+	}
+}
+
+/*-------------------------------------------------------- */
+
+/*#####################################
+ * EXPORTED INTERFACE
+ *##################################### */
+/*----------------------------------------------------------------------- */
+int tinyrl_getchar(const tinyrl_t * tinyrl)
+{
+	return tinyrl_vt100_getchar(tinyrl->term);
+}
+
+/*----------------------------------------------------------------------- */
+static void tinyrl_internal_print(const tinyrl_t * tinyrl, const char *text)
+{
+	if (tinyrl->echo_enabled) {
+		/* simply echo the line */
+		tinyrl_vt100_printf(tinyrl->term, "%s", text);
+	} else {
+		/* replace the line with echo char if defined */
+		if (tinyrl->echo_char) {
+			unsigned int i = strlen(text);
+			while (i--) {
+				tinyrl_vt100_printf(tinyrl->term, "%c",
+					tinyrl->echo_char);
+			}
+		}
+	}
+}
+
+/*----------------------------------------------------------------------- */
+static void tinyrl_internal_position(const tinyrl_t *tinyrl, int prompt_len,
+	int line_len, unsigned int width)
+{
+	int rows, cols;
+
+	rows = ((line_len + prompt_len) / width) - (prompt_len / width);
+	cols = ((line_len + prompt_len) % width) - (prompt_len % width);
+	if (cols > 0)
+		tinyrl_vt100_cursor_back(tinyrl->term, cols);
+	else if (cols < 0)
+		tinyrl_vt100_cursor_forward(tinyrl->term, -cols);
+	if (rows > 0)
+		tinyrl_vt100_cursor_up(tinyrl->term, rows);
+	else if (rows < 0)
+		tinyrl_vt100_cursor_down(tinyrl->term, -rows);
+}
+
+/*-------------------------------------------------------- */
+/* Jump to first free line after current multiline input   */
+void tinyrl_multi_crlf(const tinyrl_t * tinyrl)
+{
+	unsigned int line_size = strlen(tinyrl->last_buffer);
+	unsigned int line_len = utf8_nsyms(tinyrl, tinyrl->last_buffer, line_size);
+	unsigned int count = utf8_nsyms(tinyrl, tinyrl->last_buffer, tinyrl->last_point);
+
+	tinyrl_internal_position(tinyrl, tinyrl->prompt_len + line_len,
+		- (line_len - count), tinyrl->last_width);
+	tinyrl_crlf(tinyrl);
+	tinyrl_vt100_oflush(tinyrl->term);
+}
+
+/*----------------------------------------------------------------------- */
+void tinyrl_redisplay(tinyrl_t * tinyrl)
+{
+	unsigned int line_size = strlen(tinyrl->line);
+	unsigned int line_len = utf8_nsyms(tinyrl, tinyrl->line, line_size);
+	unsigned int width = tinyrl_vt100__get_width(tinyrl->term);
+	unsigned int count, eq_chars = 0;
+	int cols;
+
+	/* Prepare print position */
+	if (tinyrl->last_buffer && (width == tinyrl->last_width)) {
+		unsigned int eq_len = 0;
+		/* If line and last line have the equal chars at begining */
+		eq_chars = lub_string_equal_part(tinyrl->line, tinyrl->last_buffer,
+			tinyrl->utf8);
+		eq_len = utf8_nsyms(tinyrl, tinyrl->last_buffer, eq_chars);
+		count = utf8_nsyms(tinyrl, tinyrl->last_buffer, tinyrl->last_point);
+		tinyrl_internal_position(tinyrl, tinyrl->prompt_len + eq_len,
+			count - eq_len, width);
+	} else {
+		/* Prepare to resize */
+		if (width != tinyrl->last_width) {
+			tinyrl_vt100_next_line(tinyrl->term);
+			tinyrl_vt100_erase_down(tinyrl->term);
+		}
+		tinyrl_vt100_printf(tinyrl->term, "%s", tinyrl->prompt);
+	}
+
+	/* Print current line */
+	tinyrl_internal_print(tinyrl, tinyrl->line + eq_chars);
+	cols = (tinyrl->prompt_len + line_len) % width;
+	if (!cols && (line_size - eq_chars))
+		tinyrl_vt100_next_line(tinyrl->term);
+	/* Erase down if current line is shorter than previous one */
+	if (tinyrl->last_line_size > line_size)
+		tinyrl_vt100_erase_down(tinyrl->term);
+	/* Move the cursor to the insertion point */
+	if (tinyrl->point < line_size) {
+		unsigned int pre_len = utf8_nsyms(tinyrl,
+			tinyrl->line, tinyrl->point);
+		count = utf8_nsyms(tinyrl, tinyrl->line + tinyrl->point,
+			line_size - tinyrl->point);
+		tinyrl_internal_position(tinyrl, tinyrl->prompt_len + pre_len,
+			count, width);
+	}
+
+	/* Update the display */
+	tinyrl_vt100_oflush(tinyrl->term);
+
+	/* Save the last line buffer */
+	lub_string_free(tinyrl->last_buffer);
+	tinyrl->last_buffer = lub_string_dup(tinyrl->line);
+	tinyrl->last_point = tinyrl->point;
+	tinyrl->last_width = width;
+	tinyrl->last_line_size = line_size;
+}
+
+/*----------------------------------------------------------------------- */
+tinyrl_t *tinyrl_new(FILE * istream, FILE * ostream,
+	unsigned int stifle, tinyrl_completion_func_t * complete_fn)
+{
+	tinyrl_t *tinyrl = NULL;
+
+	tinyrl = malloc(sizeof(tinyrl_t));
+	if (tinyrl)
+		tinyrl_init(tinyrl, istream, ostream, stifle, complete_fn);
+
+	return tinyrl;
+}
+
+/*----------------------------------------------------------------------- */
+static char *internal_insertline(tinyrl_t * tinyrl, char *buffer)
+{
+	char *p;
+	char *s = buffer;
+
+	/* strip any spurious '\r' or '\n' */
+	if ((p = strchr(buffer, '\r')))
+		*p = '\0';
+	if ((p = strchr(buffer, '\n')))
+		*p = '\0';
+	/* skip any whitespace at the beginning of the line */
+	if (0 == tinyrl->point) {
+		while (*s && isspace(*s))
+			s++;
+	}
+	if (*s) {
+		/* append tinyrl string to the input buffer */
+		(void)tinyrl_insert_text(tinyrl, s);
+	}
+	/* echo the command to the output stream */
+	tinyrl_redisplay(tinyrl);
+
+	return s;
+}
+
+/*----------------------------------------------------------------------- */
+static char *internal_readline(tinyrl_t * tinyrl,
+	void *context, const char *str)
+{
+	FILE *istream = tinyrl_vt100__get_istream(tinyrl->term);
+	char *result = NULL;
+	int lerrno = 0;
+
+	tinyrl->done = BOOL_FALSE;
+	tinyrl->point = 0;
+	tinyrl->end = 0;
+	tinyrl->buffer = lub_string_dup("");
+	tinyrl->buffer_size = strlen(tinyrl->buffer);
+	tinyrl->line = tinyrl->buffer;
+	tinyrl->context = context;
+
+	/* Interactive session */
+	if (tinyrl->isatty && !str) {
+		unsigned int utf8_cont = 0; /* UTF-8 continue bytes */
+		unsigned int esc_cont = 0; /* Escape sequence continues */
+		char esc_seq[10]; /* Buffer for ESC sequence */
+		char *esc_p = esc_seq;
+
+		/* Set the terminal into raw mode */
+		tty_set_raw_mode(tinyrl);
+		tinyrl_reset_line_state(tinyrl);
+
+		while (!tinyrl->done) {
+			int key;
+
+			key = tinyrl_getchar(tinyrl);
+
+			/* Error || EOF || Timeout */
+			if (key < 0) {
+				if ((VT100_TIMEOUT == key) &&
+					!tinyrl->timeout_fn(tinyrl))
+					continue;
+				/* It's time to finish the session */
+				tinyrl->done = BOOL_TRUE;
+				tinyrl->line = NULL;
+				lerrno = ENOENT;
+				continue;
+			}
+
+			/* Real key pressed */
+			/* Common callback for any key */
+			if (tinyrl->keypress_fn)
+				tinyrl->keypress_fn(tinyrl, key);
+
+			/* Check for ESC sequence. It's a special case. */
+			if (!esc_cont && (key == KEY_ESC)) {
+				esc_cont = 1; /* Start ESC sequence */
+				esc_p = esc_seq;
+				continue;
+			}
+			if (esc_cont) {
+				/* Broken sequence */
+				if (esc_p >= (esc_seq + sizeof(esc_seq) - 1)) {
+					esc_cont = 0;
+					continue;
+				}
+				/* Dump the control sequence into sequence buffer
+				   ANSI standard control sequences will end
+				   with a character between 64 - 126 */
+				*esc_p = key & 0xff;
+				esc_p++;
+				/* tinyrl is an ANSI control sequence terminator code */
+				if ((key != '[') && (key > 63)) {
+					*esc_p = '\0';
+					tinyrl_escape_seq(tinyrl, esc_seq);
+					esc_cont = 0;
+					tinyrl_redisplay(tinyrl);
+				}
+				continue;
+			}
+
+			/* Call the handler for tinyrl key */
+			if (!tinyrl->handlers[key](tinyrl, key))
+				tinyrl_ding(tinyrl);
+			if (tinyrl->done) /* Some handler set the done flag */
+				continue; /* It will break the loop */
+
+			if (tinyrl->utf8) {
+				if (!(UTF8_7BIT_MASK & key)) /* ASCII char */
+					utf8_cont = 0;
+				else if (utf8_cont && (UTF8_10 == (key & UTF8_MASK))) /* Continue byte */
+					utf8_cont--;
+				else if (UTF8_11 == (key & UTF8_MASK)) { /* First byte of multibyte char */
+					/* Find out number of char's bytes */
+					int b = key;
+					utf8_cont = 0;
+					while ((utf8_cont < 6) && (UTF8_10 != (b & UTF8_MASK))) {
+						utf8_cont++;
+						b = b << 1;
+					}
+				}
+			}
+			/* For non UTF-8 encoding the utf8_cont is always 0.
+			   For UTF-8 it's 0 when one-byte symbol or we get
+			   all bytes for the current multibyte character. */
+			if (!utf8_cont)
+				tinyrl_redisplay(tinyrl);
+		}
+		/* If the last character in the line (other than NULL)
+		   is a space remove it. */
+		if (tinyrl->end && tinyrl->line && isspace(tinyrl->line[tinyrl->end - 1]))
+			tinyrl_delete_text(tinyrl, tinyrl->end - 1, tinyrl->end);
+		/* Restores the terminal mode */
+		tty_restore_mode(tinyrl);
+
+	/* Non-interactive session */
+	} else {
+		char *s = NULL, buffer[80];
+		size_t len = sizeof(buffer);
+		char *tmp = NULL;
+
+		/* manually reset the line state without redisplaying */
+		lub_string_free(tinyrl->last_buffer);
+		tinyrl->last_buffer = NULL;
+
+		if (str) {
+			tmp = lub_string_dup(str);
+			internal_insertline(tinyrl, tmp);
+		} else {
+			while (istream && (sizeof(buffer) == len) &&
+				(s = fgets(buffer, sizeof(buffer), istream))) {
+				s = internal_insertline(tinyrl, buffer);
+				len = strlen(buffer) + 1; /* account for the '\0' */
+			}
+			if (!s || ((tinyrl->line[0] == '\0') && feof(istream))) {
+				/* time to finish the session */
+				tinyrl->line = NULL;
+				lerrno = ENOENT;
+			}
+		}
+
+		/*
+		 * check against fgets returning null as either error or end of file.
+		 * tinyrl is a measure to stop potential task spin on encountering an
+		 * error from fgets.
+		 */
+		if (tinyrl->line && !tinyrl->handlers[KEY_LF](tinyrl, KEY_LF)) {
+			/* an issue has occured */
+			tinyrl->line = NULL;
+			lerrno = ENOEXEC;
+		}
+		if (str)
+			lub_string_free(tmp);
+	}
+	/*
+	 * duplicate the string for return to the client 
+	 * we have to duplicate as we may be referencing a
+	 * history entry or our internal buffer
+	 */
+	result = tinyrl->line ? lub_string_dup(tinyrl->line) : NULL;
+
+	/* free our internal buffer */
+	free(tinyrl->buffer);
+	tinyrl->buffer = NULL;
+
+	if (!result)
+		errno = lerrno; /* get saved errno */
+	return result;
+}
+
+/*----------------------------------------------------------------------- */
+char *tinyrl_readline(tinyrl_t * tinyrl, void *context)
+{
+	return internal_readline(tinyrl, context, NULL);
+}
+
+/*----------------------------------------------------------------------- */
+char *tinyrl_forceline(tinyrl_t * tinyrl, void *context, const char *line)
+{
+	return internal_readline(tinyrl, context, line);
+}
+
+/*----------------------------------------------------------------------- */
+/*
+ * Ensure that buffer has enough space to hold len characters,
+ * possibly reallocating it if necessary. The function returns BOOL_TRUE
+ * if the line is successfully extended, BOOL_FALSE if not.
+ */
+bool_t tinyrl_extend_line_buffer(tinyrl_t * tinyrl, unsigned int len)
+{
+	bool_t result = BOOL_TRUE;
+	char *new_buffer;
+	size_t new_len = len;
+
+	if (tinyrl->buffer_size >= len)
+		return result;
+
+	/*
+	 * What we do depends on whether we are limited by
+	 * memory or a user imposed limit.
+	 */
+	if (tinyrl->max_line_length == 0) {
+		/* make sure we don't realloc too often */
+		if (new_len < tinyrl->buffer_size + 10)
+			new_len = tinyrl->buffer_size + 10;
+		/* leave space for terminator */
+		new_buffer = realloc(tinyrl->buffer, new_len + 1);
+
+		if (!new_buffer) {
+			tinyrl_ding(tinyrl);
+			result = BOOL_FALSE;
+		} else {
+			tinyrl->buffer_size = new_len;
+			tinyrl->line = tinyrl->buffer = new_buffer;
+		}
+	} else {
+		if (new_len < tinyrl->max_line_length) {
+
+			/* Just reallocate once to the max size */
+			new_buffer = realloc(tinyrl->buffer,
+				tinyrl->max_line_length);
+
+			if (!new_buffer) {
+				tinyrl_ding(tinyrl);
+				result = BOOL_FALSE;
+			} else {
+				tinyrl->buffer_size =
+					tinyrl->max_line_length - 1;
+				tinyrl->line = tinyrl->buffer = new_buffer;
+			}
+		} else {
+			tinyrl_ding(tinyrl);
+			result = BOOL_FALSE;
+		}
+	}
+
+	return result;
+}
+
+/*----------------------------------------------------------------------- */
+/*
+ * Insert text into the line at the current cursor position.
+ */
+bool_t tinyrl_insert_text(tinyrl_t * tinyrl, const char *text)
+{
+	unsigned int delta = strlen(text);
+
+	/*
+	 * If the client wants to change the line ensure that the line and buffer
+	 * references are in sync
+	 */
+	changed_line(tinyrl);
+
+	if ((delta + tinyrl->end) > (tinyrl->buffer_size)) {
+		/* extend the current buffer */
+		if (BOOL_FALSE ==
+			tinyrl_extend_line_buffer(tinyrl, tinyrl->end + delta))
+			return BOOL_FALSE;
+	}
+
+	if (tinyrl->point < tinyrl->end) {
+		/* move the current text to the right (including the terminator) */
+		memmove(&tinyrl->buffer[tinyrl->point + delta],
+			&tinyrl->buffer[tinyrl->point],
+			(tinyrl->end - tinyrl->point) + 1);
+	} else {
+		/* terminate the string */
+		tinyrl->buffer[tinyrl->end + delta] = '\0';
+	}
+
+	/* insert the new text */
+	strncpy(&tinyrl->buffer[tinyrl->point], text, delta);
+
+	/* now update the indexes */
+	tinyrl->point += delta;
+	tinyrl->end += delta;
+
+	return BOOL_TRUE;
+}
+
+/*----------------------------------------------------------------------- */
+/* 
+ * A convenience function for displaying a list of strings in columnar
+ * format on Readline's output stream. matches is the list of strings,
+ * in argv format, such as a list of completion matches. len is the number
+ * of strings in matches, and max is the length of the longest string in matches.
+ * tinyrl function uses the setting of print-completions-horizontally to select
+ * how the matches are displayed
+ */
+void tinyrl_display_matches(const tinyrl_t *tinyrl,
+	char *const *matches, unsigned int len, size_t max)
+{
+	unsigned int width = tinyrl_vt100__get_width(tinyrl->term);
+	unsigned int cols, rows;
+
+	/* Find out column and rows number */
+	if (max < width)
+		cols = (width + 1) / (max + 1); /* allow for a space between words */
+	else
+		cols = 1;
+	rows = len / cols + 1;
+
+	assert(matches);
+	if (matches) {
+		unsigned int r, c;
+		len--, matches++; /* skip the subtitution string */
+		/* Print out a table of completions */
+		for (r = 0; r < rows && len; r++) {
+			for (c = 0; c < cols && len; c++) {
+				const char *match = *matches++;
+				len--;
+				if ((c + 1) == cols) /* Last str in row */
+					tinyrl_vt100_printf(tinyrl->term, "%s",
+						match);
+				else
+					tinyrl_vt100_printf(tinyrl->term, "%-*s ",
+						max, match);
+			}
+			tinyrl_crlf(tinyrl);
+		}
+	}
+}
+
+/*----------------------------------------------------------------------- */
+/*
+ * Delete the text between start and end in the current line. (inclusive)
+ * tinyrl adjusts the rl_point and rl_end indexes appropriately.
+ */
+void tinyrl_delete_text(tinyrl_t * tinyrl, unsigned int start, unsigned int end)
+{
+	unsigned int delta;
+
+	/*
+	 * If the client wants to change the line ensure that the line and buffer
+	 * references are in sync
+	 */
+	changed_line(tinyrl);
+
+	/* make sure we play it safe */
+	if (start > end) {
+		unsigned int tmp = end;
+		start = end;
+		end = tmp;
+	}
+	if (end > tinyrl->end)
+		end = tinyrl->end;
+
+	delta = (end - start) + 1;
+
+	/* move any text which is left */
+	memmove(&tinyrl->buffer[start],
+		&tinyrl->buffer[start + delta], tinyrl->end - end);
+
+	/* now adjust the indexs */
+	if (tinyrl->point >= start) {
+		if (tinyrl->point > end) {
+			/* move the insertion point back appropriately */
+			tinyrl->point -= delta;
+		} else {
+			/* move the insertion point to the start */
+			tinyrl->point = start;
+		}
+	}
+	if (tinyrl->end > end)
+		tinyrl->end -= delta;
+	else
+		tinyrl->end = start;
+	/* put a terminator at the end of the buffer */
+	tinyrl->buffer[tinyrl->end] = '\0';
+}
+
+/*----------------------------------------------------------------------- */
+bool_t tinyrl_bind_key(tinyrl_t * tinyrl, int key, tinyrl_key_func_t * fn)
+{
+	bool_t result = BOOL_FALSE;
+
+	if ((key >= 0) && (key < 256)) {
+		/* set the key handling function */
+		tinyrl->handlers[key] = fn;
+		result = BOOL_TRUE;
+	}
+
+	return result;
+}
+
+/*-------------------------------------------------------- */
+/*
+ * Returns an array of strings which is a list of completions for text.
+ * If there are no completions, returns NULL. The first entry in the
+ * returned array is the substitution for text. The remaining entries
+ * are the possible completions. The array is terminated with a NULL pointer.
+ *
+ * entry_func is a function of two args, and returns a char *. 
+ * The first argument is text. The second is a state argument;
+ * it is zero on the first call, and non-zero on subsequent calls.
+ * entry_func returns a NULL pointer to the caller when there are no 
+ * more matches. 
+ */
+char **tinyrl_completion(tinyrl_t * tinyrl,
+	const char *line, unsigned int start, unsigned int end,
+	tinyrl_compentry_func_t * entry_func)
+{
+	unsigned int state = 0;
+	size_t size = 1;
+	unsigned int offset = 1; /* Need at least one entry for the substitution */
+	char **matches = NULL;
+	char *match;
+	/* duplicate the string upto the insertion point */
+	char *text = lub_string_dupn(line, end);
+
+	/* now try and find possible completions */
+	while ((match = entry_func(tinyrl, text, start, state++))) {
+		if (size == offset) {
+			/* resize the buffer if needed - the +1 is for the NULL terminator */
+			size += 10;
+			matches =
+			    realloc(matches, (sizeof(char *) * (size + 1)));
+		}
+		/* not much we can do... */
+		if (!matches)
+			break;
+		matches[offset] = match;
+
+		/*
+		 * augment the substitute string with tinyrl entry
+		 */
+		if (1 == offset) {
+			/* let's be optimistic */
+			matches[0] = lub_string_dup(match);
+		} else {
+			char *p = matches[0];
+			size_t match_len = strlen(p);
+			/* identify the common prefix */
+			while ((tolower(*p) == tolower(*match)) && match_len--) {
+				p++, match++;
+			}
+			/* terminate the prefix string */
+			*p = '\0';
+		}
+		offset++;
+	}
+	/* be a good memory citizen */
+	lub_string_free(text);
+
+	if (matches)
+		matches[offset] = NULL;
+	return matches;
+}
+
+/*-------------------------------------------------------- */
+void tinyrl_delete_matches(char **tinyrl)
+{
+	char **matches = tinyrl;
+	while (*matches) {
+		/* release the memory for each contained string */
+		free(*matches++);
+	}
+	/* release the memory for the array */
+	free(tinyrl);
+}
+
+/*-------------------------------------------------------- */
+void tinyrl_crlf(const tinyrl_t * tinyrl)
+{
+	tinyrl_vt100_printf(tinyrl->term, "\n");
+}
+
+/*-------------------------------------------------------- */
+/*
+ * Ring the terminal bell, obeying the setting of bell-style.
+ */
+void tinyrl_ding(const tinyrl_t * tinyrl)
+{
+	tinyrl_vt100_ding(tinyrl->term);
+}
+
+/*-------------------------------------------------------- */
+void tinyrl_reset_line_state(tinyrl_t * tinyrl)
+{
+	lub_string_free(tinyrl->last_buffer);
+	tinyrl->last_buffer = NULL;
+	tinyrl->last_line_size = 0;
+
+	tinyrl_redisplay(tinyrl);
+}
+
+/*-------------------------------------------------------- */
+void tinyrl_replace_line(tinyrl_t * tinyrl, const char *text, int clear_undo)
+{
+	size_t new_len = strlen(text);
+
+	/* ignored for now */
+	clear_undo = clear_undo;
+
+	/* ensure there is sufficient space */
+	if (tinyrl_extend_line_buffer(tinyrl, new_len)) {
+
+		/* overwrite the current contents of the buffer */
+		strcpy(tinyrl->buffer, text);
+
+		/* set the insert point and end point */
+		tinyrl->point = tinyrl->end = new_len;
+	}
+	tinyrl_redisplay(tinyrl);
+}
+
+/*-------------------------------------------------------- */
+static tinyrl_match_e
+tinyrl_do_complete(tinyrl_t * tinyrl, bool_t with_extensions)
+{
+	tinyrl_match_e result = TINYRL_NO_MATCH;
+	char **matches = NULL;
+	unsigned int start, end;
+	bool_t completion = BOOL_FALSE;
+	bool_t prefix = BOOL_FALSE;
+	int i = 0;
+
+	/* find the start and end of the current word */
+	start = end = tinyrl->point;
+	while (start && !isspace(tinyrl->line[start - 1]))
+		start--;
+
+	if (tinyrl->attempted_completion_function) {
+		tinyrl->completion_over = BOOL_FALSE;
+		tinyrl->completion_error_over = BOOL_FALSE;
+		/* try and complete the current line buffer */
+		matches = tinyrl->attempted_completion_function(tinyrl,
+			tinyrl->line, start, end);
+	}
+	if (!matches && (BOOL_FALSE == tinyrl->completion_over)) {
+		/* insert default completion call here... */
+	}
+	if (!matches)
+		return result;
+
+	/* identify and insert a common prefix if there is one */
+	if (0 != strncmp(matches[0], &tinyrl->line[start],
+		strlen(matches[0]))) {
+		/*
+		 * delete the original text not including
+		 * the current insertion point character
+		 */
+		if (tinyrl->end != end)
+			end--;
+		tinyrl_delete_text(tinyrl, start, end);
+		if (BOOL_FALSE == tinyrl_insert_text(tinyrl, matches[0]))
+			return TINYRL_NO_MATCH;
+		completion = BOOL_TRUE;
+	}
+	for (i = 1; matches[i]; i++) {
+		/* tinyrl is just a prefix string */
+		if (0 == lub_string_nocasecmp(matches[0], matches[i]))
+			prefix = BOOL_TRUE;
+	}
+	/* is there more than one completion? */
+	if (matches[2]) {
+		char **tmp = matches;
+		unsigned int max, len;
+		max = len = 0;
+		while (*tmp) {
+			size_t size = strlen(*tmp++);
+			len++;
+			if (size > max)
+				max = size;
+		}
+		if (completion)
+			result = TINYRL_COMPLETED_AMBIGUOUS;
+		else if (prefix)
+			result = TINYRL_MATCH_WITH_EXTENSIONS;
+		else
+			result = TINYRL_AMBIGUOUS;
+		if (with_extensions || !prefix) {
+			/* Either we always want to show extensions or
+			 * we haven't been able to complete the current line
+			 * and there is just a prefix, so let the user see the options
+			 */
+			tinyrl_crlf(tinyrl);
+			tinyrl_display_matches(tinyrl, matches, len, max);
+			tinyrl_reset_line_state(tinyrl);
+		}
+	} else {
+		result = completion ?
+			TINYRL_COMPLETED_MATCH : TINYRL_MATCH;
+	}
+	/* free the memory */
+	tinyrl_delete_matches(matches);
+	/* redisplay the line */
+	tinyrl_redisplay(tinyrl);
+
+	return result;
+}
+
+/*-------------------------------------------------------- */
+tinyrl_match_e tinyrl_complete_with_extensions(tinyrl_t * tinyrl)
+{
+	return tinyrl_do_complete(tinyrl, BOOL_TRUE);
+}
+
+/*-------------------------------------------------------- */
+tinyrl_match_e tinyrl_complete(tinyrl_t * tinyrl)
+{
+	return tinyrl_do_complete(tinyrl, BOOL_FALSE);
+}
+
+/*-------------------------------------------------------- */
+void *tinyrl__get_context(const tinyrl_t * tinyrl)
+{
+	return tinyrl->context;
+}
+
+/*--------------------------------------------------------- */
+const char *tinyrl__get_line(const tinyrl_t * tinyrl)
+{
+	return tinyrl->line;
+}
+
+/*--------------------------------------------------------- */
+tinyrl_history_t *tinyrl__get_history(const tinyrl_t * tinyrl)
+{
+	return tinyrl->history;
+}
+
+/*--------------------------------------------------------- */
+void tinyrl_completion_over(tinyrl_t * tinyrl)
+{
+	tinyrl->completion_over = BOOL_TRUE;
+}
+
+/*--------------------------------------------------------- */
+void tinyrl_completion_error_over(tinyrl_t * tinyrl)
+{
+	tinyrl->completion_error_over = BOOL_TRUE;
+}
+
+/*--------------------------------------------------------- */
+bool_t tinyrl_is_completion_error_over(const tinyrl_t * tinyrl)
+{
+	return tinyrl->completion_error_over;
+}
+
+/*--------------------------------------------------------- */
+void tinyrl_done(tinyrl_t * tinyrl)
+{
+	tinyrl->done = BOOL_TRUE;
+}
+
+/*--------------------------------------------------------- */
+void tinyrl_enable_echo(tinyrl_t * tinyrl)
+{
+	tinyrl->echo_enabled = BOOL_TRUE;
+}
+
+/*--------------------------------------------------------- */
+void tinyrl_disable_echo(tinyrl_t * tinyrl, char echo_char)
+{
+	tinyrl->echo_enabled = BOOL_FALSE;
+	tinyrl->echo_char = echo_char;
+}
+
+/*--------------------------------------------------------- */
+void tinyrl__set_istream(tinyrl_t * tinyrl, FILE * istream)
+{
+	tinyrl_vt100__set_istream(tinyrl->term, istream);
+	if (istream) {
+		int fd;
+		tinyrl->isatty = isatty(fileno(istream)) ? BOOL_TRUE : BOOL_FALSE;
+		/* Save terminal settings to restore on exit */
+		fd = fileno(istream);
+		tcgetattr(fd, &tinyrl->default_termios);
+	} else
+		tinyrl->isatty = BOOL_FALSE;
+}
+
+/*-------------------------------------------------------- */
+bool_t tinyrl__get_isatty(const tinyrl_t * tinyrl)
+{
+	return tinyrl->isatty;
+}
+
+/*-------------------------------------------------------- */
+FILE *tinyrl__get_istream(const tinyrl_t * tinyrl)
+{
+	return tinyrl_vt100__get_istream(tinyrl->term);
+}
+
+/*-------------------------------------------------------- */
+FILE *tinyrl__get_ostream(const tinyrl_t * tinyrl)
+{
+	return tinyrl_vt100__get_ostream(tinyrl->term);
+}
+
+/*-------------------------------------------------------- */
+const char *tinyrl__get_prompt(const tinyrl_t * tinyrl)
+{
+	return tinyrl->prompt;
+}
+
+/*-------------------------------------------------------- */
+void tinyrl__set_prompt(tinyrl_t *tinyrl, const char *prompt)
+{
+	if (tinyrl->prompt) {
+		lub_string_free(tinyrl->prompt);
+		tinyrl->prompt_size = 0;
+		tinyrl->prompt_len = 0;
+	}
+	tinyrl->prompt = lub_string_dup(prompt);
+	if (tinyrl->prompt) {
+		tinyrl->prompt_size = strlen(tinyrl->prompt);
+		tinyrl->prompt_len = utf8_nsyms(tinyrl, tinyrl->prompt,
+			tinyrl->prompt_size);
+	}
+}
+
+/*-------------------------------------------------------- */
+bool_t tinyrl__get_utf8(const tinyrl_t * tinyrl)
+{
+	return tinyrl->utf8;
+}
+
+/*-------------------------------------------------------- */
+void tinyrl__set_utf8(tinyrl_t * tinyrl, bool_t utf8)
+{
+	tinyrl->utf8 = utf8;
+}
+
+/*-------------------------------------------------------- */
+void tinyrl__set_timeout(tinyrl_t *tinyrl, int timeout)
+{
+	tinyrl_vt100__set_timeout(tinyrl->term, timeout);
+}
+
+/*-------------------------------------------------------- */
+void tinyrl__set_timeout_fn(tinyrl_t *tinyrl,
+	tinyrl_timeout_fn_t *fn)
+{
+	tinyrl->timeout_fn = fn;
+}
+
+/*-------------------------------------------------------- */
+void tinyrl__set_keypress_fn(tinyrl_t *tinyrl,
+	tinyrl_keypress_fn_t *fn)
+{
+	tinyrl->keypress_fn = fn;
+}
+
+/*-------------------------------------------------------- */
+void tinyrl__set_hotkey_fn(tinyrl_t *tinyrl,
+	tinyrl_key_func_t *fn)
+{
+	tinyrl->hotkey_fn = fn;
+}
+
+/*-------------------------------------------------------- */
+bool_t tinyrl_is_quoting(const tinyrl_t * tinyrl)
+{
+	bool_t result = BOOL_FALSE;
+	/* count the quotes upto the current insertion point */
+	unsigned int i = 0;
+	while (i < tinyrl->point) {
+		if (result && (tinyrl->line[i] == '\\')) {
+			i++;
+			if (i >= tinyrl->point)
+				break;
+			i++;
+			continue;
+		}
+		if (tinyrl->line[i++] == '"') {
+			result = result ? BOOL_FALSE : BOOL_TRUE;
+		}
+	}
+	return result;
+}
+
+/*-------------------------------------------------------- */
+bool_t tinyrl_is_empty(const tinyrl_t *tinyrl)
+{
+	return (tinyrl->point == 0) ? BOOL_TRUE : BOOL_FALSE;
+}
+
+/*--------------------------------------------------------- */
+void tinyrl_limit_line_length(tinyrl_t * tinyrl, unsigned int length)
+{
+	tinyrl->max_line_length = length;
+}
+
+/*--------------------------------------------------------- */
+extern unsigned int tinyrl__get_width(const tinyrl_t *tinyrl)
+{
+	return tinyrl_vt100__get_width(tinyrl->term);
+}
+
+/*--------------------------------------------------------- */
+extern unsigned int tinyrl__get_height(const tinyrl_t *tinyrl)
+{
+	return tinyrl_vt100__get_height(tinyrl->term);
+}
+
+/*----------------------------------------------------------*/
+int tinyrl__save_history(const tinyrl_t *tinyrl, const char *fname)
+{
+	return tinyrl_history_save(tinyrl->history, fname);
+}
+
+/*----------------------------------------------------------*/
+int tinyrl__restore_history(tinyrl_t *tinyrl, const char *fname)
+{
+	return tinyrl_history_restore(tinyrl->history, fname);
+}
+
+/*----------------------------------------------------------*/
+void tinyrl__stifle_history(tinyrl_t *tinyrl, unsigned int stifle)
+{
+	tinyrl_history_stifle(tinyrl->history, stifle);
+}
+/*--------------------------------------------------------- */
+
+#endif

+ 253 - 0
tinyrl/tinyrl/utf8.c

@@ -0,0 +1,253 @@
+/*
+ * tinyrl.c
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <faux/str.h>
+
+#include "private.h"
+
+
+/** @brief Converts UTF-8 char to unsigned long wchar
+ *
+ * @param [in] sp Pointer to UTF-8 string to get char from.
+ * @param [out] sym_out Resulting wchar.
+ * @return Number of bytes for current UTF-8 symbol.
+ */
+ssize_t utf8_to_wchar(const char *sp, unsigned long *sym_out)
+{
+	int i = 0;
+	int octets = 0; // Number of 0x10xxxxxx UTF-8 sequence bytes
+	unsigned long sym = 0;
+	const unsigned char *p = (const unsigned char *)sp;
+
+	if (sym_out)
+		*sym_out = *p;
+
+	if (!*p)
+		return 0;
+
+	// Check for first byte of UTF-8
+	if ((*p & 0x80) == 0) // 0xxxxxxx
+		return 1;
+
+	// Analyze first byte to get number of UTF-8 octets
+	if ((*p & 0xe0) == 0xc0) { // 110xxxxx 10xxxxxx
+		octets = 1;
+		sym = (*p & 0x1f);
+	} else if ((*p & 0xf0) == 0xe0) { // 1110xxxx 10xxxxxx 10xxxxxx
+		octets = 2;
+		sym = (*p & 0xf);
+	} else if ((*p & 0xf8) == 0xf0) { // 11110xxx 10xxxxxx 10xxxxxx
+		octets = 3;
+		sym = (*p & 7);
+	} else if ((*p & 0xfc) == 0xf8) { // depricated
+		octets = 4;
+		sym = (*p & 3);
+	} else if ((*p & 0xfe) == 0xfc) { // depricated
+		octets = 5;
+		sym = (*p & 1);
+	} else {
+		return 1; // Error but be robust and skip one byte
+	}
+	p++;
+
+	// Analyze next UTF-8 bytes 10xxxxxx
+	for (i = 0; i < octets; i++) {
+		sym <<= 6;
+		// Check if it's really UTF-8 bytes
+		if ((*p & 0xc0) != 0x80)
+			return 1; // Skip one byte if broken UTF-8 symbol
+		sym |= (*p & 0x3f);
+		p++;
+	}
+
+	if (sym_out)
+		*sym_out = sym;
+
+	return (octets + 1);
+}
+
+
+/** @brief Checks is wchar CJK
+ *
+ * @param [in] sym Widechar symbol to analyze
+ * @return BOOL_TRUE if CJK and BOOL_FALSE else
+ */
+bool_t utf8_wchar_is_cjk(unsigned long sym)
+{
+	if (sym < 0x1100) /* Speed up for non-CJK chars */
+		return BOOL_FALSE;
+
+	if (sym >= 0x1100 && sym <= 0x11FF) /* Hangul Jamo */
+		return BOOL_TRUE;
+#if 0
+	if (sym >=0x2E80 && sym <= 0x2EFF) /* CJK Radicals Supplement */
+		return BOOL_TRUE;
+	if (sym >=0x2F00 && sym <= 0x2FDF) /* Kangxi Radicals */
+		return BOOL_TRUE;
+	if (sym >= 0x2FF0 && sym <= 0x2FFF) /* Ideographic Description Characters */
+		return BOOL_TRUE;
+	if (sym >= 0x3000 && sym < 0x303F) /* CJK Symbols and Punctuation. The U+303f is half space */
+		return BOOL_TRUE;
+	if (sym >= 0x3040 && sym <= 0x309F) /* Hiragana */
+		return BOOL_TRUE;
+	if (sym >= 0x30A0 && sym <=0x30FF) /* Katakana */
+		return BOOL_TRUE;
+	if (sym >= 0x3100 && sym <=0x312F) /* Bopomofo */
+		return BOOL_TRUE;
+	if (sym >= 0x3130 && sym <= 0x318F) /* Hangul Compatibility Jamo */
+		return BOOL_TRUE;
+	if (sym >= 0x3190 && sym <= 0x319F) /* Kanbun */
+		return BOOL_TRUE;
+	if (sym >= 0x31A0 && sym <= 0x31BF) /* Bopomofo Extended */
+		return BOOL_TRUE;
+	if (sym >= 0x31C0 && sym <= 0x31EF) /* CJK strokes */
+		return BOOL_TRUE;
+	if (sym >= 0x31F0 && sym <= 0x31FF) /* Katakana Phonetic Extensions */
+		return BOOL_TRUE;
+	if (sym >= 0x3200 && sym <= 0x32FF) /* Enclosed CJK Letters and Months */
+		return BOOL_TRUE;
+	if (sym >= 0x3300 && sym <= 0x33FF) /* CJK Compatibility */
+		return BOOL_TRUE;
+	if (sym >= 0x3400 && sym <= 0x4DBF) /* CJK Unified Ideographs Extension A */
+		return BOOL_TRUE;
+	if (sym >= 0x4DC0 && sym <= 0x4DFF) /* Yijing Hexagram Symbols */
+		return BOOL_TRUE;
+	if (sym >= 0x4E00 && sym <= 0x9FFF) /* CJK Unified Ideographs */
+		return BOOL_TRUE;
+	if (sym >= 0xA000 && sym <= 0xA48F) /* Yi Syllables */
+		return BOOL_TRUE;
+	if (sym >= 0xA490 && sym <= 0xA4CF) /* Yi Radicals */
+		return BOOL_TRUE;
+#endif
+	/* Speed up previous block */
+	if (sym >= 0x2E80 && sym <= 0xA4CF && sym != 0x303F)
+		return BOOL_TRUE;
+
+	if (sym >= 0xAC00 && sym <= 0xD7AF) /* Hangul Syllables */
+		return BOOL_TRUE;
+	if (sym >= 0xF900 && sym <= 0xFAFF) /* CJK Compatibility Ideographs */
+		return BOOL_TRUE;
+	if (sym >= 0xFE10 && sym <= 0xFE1F) /* Vertical Forms */
+		return BOOL_TRUE;
+
+#if 0
+	if (sym >= 0xFE30 && sym <= 0xFE4F) /* CJK Compatibility Forms */
+		return BOOL_TRUE;
+	if (sym >= 0xFE50 && sym <= 0xFE6F) /* Small Form Variants */
+		return BOOL_TRUE;
+#endif
+	/* Speed up previous block */
+	if (sym >= 0xFE30 && sym <= 0xFE6F)
+		return BOOL_TRUE;
+
+	if ((sym >= 0xFF00 && sym <= 0xFF60) ||
+		(sym >= 0xFFE0 && sym <= 0xFFE6)) /* Fullwidth Forms */
+		return BOOL_TRUE;
+
+	if (sym >= 0x1D300 && sym <= 0x1D35F) /* Tai Xuan Jing Symbols */
+		return BOOL_TRUE;
+	if (sym >= 0x20000 && sym <= 0x2B81F) /* CJK Unified Ideographs Extensions B, C, D */
+		return BOOL_TRUE;
+	if (sym >= 0x2F800 && sym <= 0x2FA1F) /* CJK Compatibility Ideographs Supplement */
+		return BOOL_TRUE;
+
+	return BOOL_FALSE;
+}
+
+
+/** @brief Get position of previous UTF-8 char within string
+ *
+ * @param [in] line UTF-8 string.
+ * @param [in] cur_pos Current position within UTF-8 string.
+ * @return Position (pointer) of previous UTF-8 character or NULL on error.
+ */
+char *utf8_move_left(const char *line, char *cur_pos)
+{
+	char *pos = cur_pos;
+
+	if (!line || !pos)
+		return NULL;
+	if (line > pos)
+		return NULL;
+	if (pos == line)
+		return pos; // It's already leftmost position
+	pos--;
+	while ((pos > line) && (UTF8_10 == (*pos & UTF8_MASK)))
+		pos--;
+
+	return pos;
+}
+
+
+/** @brief Get position of next UTF-8 char within string
+ *
+ * @param [in] line UTF-8 string.
+ * @param [in] cur_pos Current position within UTF-8 string.
+ * @return Position (pointer) of next UTF-8 character or NULL on error.
+ */
+
+
+char *utf8_move_right(const char *line, char *cur_pos)
+{
+	char *pos = cur_pos;
+
+	if (!line || !pos)
+		return NULL;
+	if (line > pos)
+		return NULL;
+	if (*pos == '\0')
+		return pos; // It's already rightmost position
+	pos++;
+	while ((*pos != '\0') && (UTF8_10 == (*pos & UTF8_MASK)))
+		pos++;
+
+	return pos;
+}
+
+
+/** @brief Counts number of printable symbols within UTF-8 string
+ *
+ * One printable symol can consist of several UTF-8 bytes.
+ * CJK UTF-8 character can occupy 2 printable positions.
+ *
+ * @param [in] str UTF-8 string.
+ * @param [in] end End of line position (pointer). Can be NULL - no limit.
+ * @param Number of printable symbols or < 0 on error.
+ */
+ssize_t utf8_nsyms(const char *str, const char *end)
+{
+	const char *pos = str;
+	ssize_t nsym = 0;
+
+	if (!str)
+		return -1;
+
+	while ((pos != end) && (*pos != '\0')) {
+		unsigned long sym = 0;
+
+		// ASCII char
+		if ((UTF8_7BIT_MASK & *pos) == 0) {
+			pos++;
+			nsym++;
+			continue;
+		}
+
+		// Multibyte UTF-8
+		pos += utf8_to_wchar(pos, &sym);
+		if (utf8_wchar_is_cjk(sym)) // CJK chars have double-width
+			nsym += 2;
+		else
+			nsym += 1;
+	}
+
+	return nsym;
+}