diff --git a/src/sbbs3/readmsgs.cpp b/src/sbbs3/readmsgs.cpp
index 1032563717059ea414c0806ceb99c636a425cf08..2e88adc744d6c969c599f5ea066c72f395524961 100644
--- a/src/sbbs3/readmsgs.cpp
+++ b/src/sbbs3/readmsgs.cpp
@@ -149,9 +149,9 @@ void sbbs_t::msghdr(smbmsg_t* msg)
 	bprintf("%-16.16s %04Xh\r\n","attr"				,msg->hdr.attr);
 	bprintf("%-16.16s %08lXh\r\n","auxattr"			,msg->hdr.auxattr);
 	bprintf("%-16.16s %08lXh\r\n","netattr"			,msg->hdr.netattr);
-	bprintf("%-16.16s %ld\r\n"	 ,"number"			,msg->hdr.number);
 	bprintf("%-16.16s %06lXh\r\n","header offset"	,msg->idx.offset);
 	bprintf("%-16.16s %u\r\n"	 ,"header length"	,msg->hdr.length);
+	bprintf("%-16.16s %ld\r\n"	 ,"number"			,msg->hdr.number);
 
 	/* optional fixed fields */
 	if(msg->hdr.thread_id)
@@ -424,6 +424,68 @@ static int rank_post(const void* a1, const void* a2)
 	return diff;
 }
 
+static int find_post(smb_t* smb, uint32_t msgnum, post_t* post)
+{
+	uint32_t i;
+
+	/* ToDo: optimize search */
+	for(i=0; i<smb->msgs; i++)
+		if(post[i].idx.number == msgnum)
+			return i;
+	return -1;
+}
+
+void sbbs_t::show_thread(uint32_t msgnum, post_t* post, unsigned curmsg, int thread_depth, uint64_t reply_mask)
+{
+	char date[32];
+	smbmsg_t msg;
+
+	int i = find_post(&smb, msgnum, post);
+	if(i < 0)
+		return;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.idx = post[i].idx;
+	if(smb_getmsghdr(&smb, &msg) != SMB_SUCCESS)
+		return;
+	attr(LIGHTGRAY);
+	if(thread_depth) {
+		for(int j=0; j < thread_depth-1; j++)
+			bprintf("  %c", (reply_mask&(1LL<<j)) ? 179 : ' ');
+		if(msg.hdr.thread_next)
+			bprintf("  %c", 195);
+		else
+			bprintf("  %c", 192);
+	}
+	if(i == curmsg)
+		attr(WHITE);
+	bprintf("%u%c "
+		,post[i].num
+//		,msg.hdr.number
+		,i == curmsg ? '>':':');
+	bprintf("%-*.*s %c %s\r\n"
+		,cols-column-13
+		,cols-column-13
+		,msg.hdr.attr&MSG_ANONYMOUS && !sub_op(smb.subnum)
+			? text[Anonymous] : msg.from
+		,msg_listing_flag(smb.subnum, &msg, &post[i])
+		,unixtodstr(&cfg, msg.hdr.when_written.time, date));
+
+	if(thread_depth) {
+		if(msg.hdr.thread_first)
+			reply_mask |= (1LL<<(thread_depth-1));
+		else
+			reply_mask &= ~(1LL<<(thread_depth-1));
+	}
+	if(thread_depth && !msg.hdr.thread_next)
+		reply_mask &= ~(1LL<<(thread_depth-1));
+	if(msg.hdr.thread_first)
+		show_thread(msg.hdr.thread_first, post, curmsg, thread_depth+1, reply_mask);
+	if(msg.hdr.thread_next)
+		show_thread(msg.hdr.thread_next, post, curmsg, thread_depth, reply_mask);
+	smb_freemsgmem(&msg);
+}
+
 /****************************************************************************/
 /* Reads posts on subboard sub. 'mode' determines new-posts only, browse,   */
 /* or continuous read.                                                      */
@@ -448,6 +510,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 	uint32_t u;
 	post_t	*post;
 	smbmsg_t	msg;
+	bool	thread_mode = false;
 
 	cursubnum=subnum;	/* for ARS */
 	if(cfg.scanposts_mod[0] && !scanposts_inside) {
@@ -667,7 +730,17 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 
 		mismatches=0;
 
-		if(domsg && !(sys_status&SS_ABORT)) {
+		if(thread_mode) {
+			long first = smb_first_in_thread(&smb, &msg);
+			if(first < 0) {
+				bputs(text[NoMessagesFound]);
+				break;
+			}
+			bprintf("\1n\1l\1h\1bThread: \1c%.70s\r\n", msg.subj);
+			show_thread(first, post, smb.curmsg);
+			subscan[subnum].last = post[smb.curmsg].idx.number;
+		}
+		else if(domsg && !(sys_status&SS_ABORT)) {
 
 			if(do_find && mode&SCAN_FIND) { 			/* Find text in messages */
 				buf=smb_getmsgtxt(&smb,&msg,GETMSGTXT_ALL);
@@ -801,7 +874,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 			bprintf(text[UnvalidatedWarning],unvalidated+1);
 		bprintf(text[ReadingSub],ugrp,cfg.grp[cfg.sub[subnum]->grp]->sname
 			,usub,cfg.sub[subnum]->sname,smb.curmsg+1,smb.msgs);
-		sprintf(str,"ABCDEFHILMNPQRTUVY?<>[]{}-+()");
+		sprintf(str,"ABCDEFHILMNPQRTUVY?*<>[]{}-+()\x0a\x1d\x1e\06");
 		if(sub_op(subnum))
 			strcat(str,"O");
 		do_find=true;
@@ -812,10 +885,21 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 				break; 
 			}
 			smb.curmsg=(l&~0x80000000L)-1;
-			do_find=false;
+			do_find = false;
+			thread_mode = false;
+			domsg = true;
 			continue; 
 		}
+		if(thread_mode && isalpha(l)) {
+			thread_mode = false;
+			domsg = true;
+		}
 		switch(l) {
+			case '*':
+				thread_mode = !thread_mode;
+				if(!thread_mode)
+					domsg = true;
+				continue;
 			case 'A':   
 			case 'R':   
 				if((char)l==(cfg.sys_misc&SM_RA_EMU ? 'A' : 'R')) { 
@@ -1165,6 +1249,21 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 				break;
 			}
 			case '-':
+				if(thread_mode && msg.hdr.thread_id) {
+					for(i=smb.curmsg-1; i >= 0; i--) {
+						smbmsg_t nextmsg;
+						memset(&nextmsg, 0, sizeof(nextmsg));
+						nextmsg.idx = post[i].idx;
+						if(smb_getmsghdr(&smb, &nextmsg) != SMB_SUCCESS)
+							continue;
+						smb_freemsgmem(&nextmsg);
+						if(nextmsg.hdr.thread_id < msg.hdr.thread_id)
+							break;
+					}
+					if(i >= 0)
+						smb.curmsg = i;
+					break;
+				}
 				if(smb.curmsg>0) smb.curmsg--;
 				do_find=false;
 				break;
@@ -1286,11 +1385,34 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 				}
 				break;
 			case ')':   /* Thread forward */
-				l=msg.hdr.thread_first;
-				if(!l) l=msg.hdr.thread_next;
-				if(!l) {
+			case LF:	/* down-arrow */
+                l=msg.hdr.thread_next;
+                if(!l) l=msg.hdr.thread_first;
+                if(!l) {
 					domsg=0;
-					bputs(text[NoMessagesFound]);
+					outchar('\a');
+					break; 
+				}
+				for(u=0;u<smb.msgs;u++)
+					if(l==post[u].idx.number)
+						break;
+				if(u<smb.msgs) {
+					smb.curmsg=u;
+				} else {
+					domsg=0;
+					if(thread_mode)
+						outchar('\a');
+					else
+						bputs(text[NoMessagesFound]);
+				}
+				do_find=false;
+				break;
+			case CTRL_F:	/* Right-arrow */
+                l=msg.hdr.thread_first;
+                if(!l) l=msg.hdr.thread_next;
+                if(!l) {
+					domsg=0;
+					outchar('\a');
 					break; 
 				}
 				for(u=0;u<smb.msgs;u++)
@@ -1300,27 +1422,51 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 					smb.curmsg=u;
 				else {
 					domsg=0;
-					bputs(text[NoMessagesFound]);
+					outchar('\a');
 				}
 				do_find=false;
 				break;
 			case '(':   /* Thread backwards */
+			case '\x1d':	/* left arrow */
 				if(!msg.hdr.thread_back) {
 					domsg=0;
-					bputs(text[NoMessagesFound]);
+					outchar('\a');
 					break; 
 				}
 				for(u=0;u<smb.msgs;u++)
 					if(msg.hdr.thread_back==post[u].idx.number)
 						break;
-				if(u<smb.msgs)
+				if(u<smb.msgs) {
 					smb.curmsg=u;
-				else {
+				} else {
 					domsg=0;
-					bputs(text[NoMessagesFound]);
+					if(thread_mode)
+						outchar('\a');
+					else
+						bputs(text[NoMessagesFound]);
 				}
 				do_find=false;
 				break;
+			case '\x1e':	/* up arrow */
+				if(!msg.hdr.thread_id) {
+					domsg=0;
+					bputs(text[NoMessagesFound]);
+					break; 
+				}
+				for(i=smb.curmsg-1; i >= 0; i--) {
+					smbmsg_t nextmsg;
+					memset(&nextmsg, 0, sizeof(nextmsg));
+					nextmsg.idx = post[i].idx;
+					if(smb_getmsghdr(&smb, &nextmsg) != SMB_SUCCESS)
+						continue;
+					smb_freemsgmem(&nextmsg);
+					if(nextmsg.hdr.thread_id == msg.hdr.thread_id)
+						break;
+				}
+				if(i<0)
+					break;
+				smb.curmsg = i;
+				break;
 			case '>':   /* Search Title forward */
 				for(u=smb.curmsg+1;u<smb.msgs;u++)
 					if(post[u].idx.subj==msg.idx.subj)
@@ -1397,8 +1543,24 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 				}
 				do_find=false;
 				break;
-			case 0: /* Carriage return - Next Message */
+			case 0: /* Carriage return - Next Message/Thread */
 			case '+':
+				if(thread_mode) {
+					for(u=smb.curmsg+1;u<smb.msgs;u++) {
+						smbmsg_t nextmsg;
+						memset(&nextmsg, 0, sizeof(nextmsg));
+						nextmsg.idx = post[u].idx;
+						if(smb_getmsghdr(&smb, &nextmsg) != SMB_SUCCESS)
+							continue;
+						smb_freemsgmem(&nextmsg);
+						if(nextmsg.hdr.thread_id > msg.hdr.thread_id)
+							break;
+					}
+					if(u >= smb.msgs)
+						done=1;
+					smb.curmsg = u;
+					break;
+				}
 				if(smb.curmsg<smb.msgs-1) smb.curmsg++;
 				else done=1;
 				break;
@@ -1406,7 +1568,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 				menu("msgscan");
 				domsg=0;
 				break;	
-		} 
+		}
 	}
 	if(msg.total_hfields)
 		smb_freemsgmem(&msg);
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index f350b67e0d530f34bc589c16d1dfa6a5c926e313..1d6528cb9b4b9abf3b70843eb0c368eec7284fef 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -756,6 +756,7 @@ public:
 	long	listmsgs(uint subnum, long mode, post_t* post, long start, long posts);
 	long	searchposts(uint subnum, post_t* post, long start, long msgs, const char* find);
 	long	showposts_toyou(uint subnum, post_t* post, ulong start, long posts, long mode=0);
+	void	show_thread(uint32_t msgnum, post_t* post, unsigned curmsg, int thread_depth = 0, uint64_t reply_mask = 0);
 	void	msghdr(smbmsg_t* msg);
 	uchar	msg_listing_flag(uint subnum, smbmsg_t*, post_t*);
 
diff --git a/text/menu/msgscan.asc b/text/menu/msgscan.asc
index d2e55eaeac4ff7fc2814049eca5e5ea2cba2d8a3..ac2523cf292f53ccf74adfef20e5c933261bf63a 100644
--- a/text/menu/msgscan.asc
+++ b/text/menu/msgscan.asc
@@ -3,17 +3,17 @@
 c4�k������������������������0
 b������4 hwRead Messages nb��������4 hwPost/Reply Messages nb������4 hwMessage Threading nb����
 �������
-� hy<CR> ngNext message�b� hyP ngPost a message�b� hy< > ng-/+ by Subjects�b�
-�    hy- ngPrevious message   b� hyD ngDelete msg/close poll b� hy{ } ng-/+ by Author�b�
-�    hyC ngContiunous read    b��������������������������� hy[ ] ng-/+ by 'To User'    b�
-�    hy@MSGREREAD@ ngReread last msg    b� hy@MSGREPLY@ ngReply to last message b� hy( ) ng-/+ by Thread-ID    b�
-�    hy# ngGo to message #    b� hyM ngReply in mail to last b���
-�                         b� hyV ngVote on last msg/poll 4 hw      Other Commands nb�����
-������4 hwList Messagesnb�4      ��0                         �
-�                         40�������4 hwSearch/Find nb������� hyB ngBypass current sub    b�
-� hyL ngList all messages     b��� hyI ngInformation on sub    b�
-� hyT ngNext ten messages     b� hyY ngMessages to you�b� hyE ngEdit last message     b�
-� hyN ngNew messages           b� hyU ngYour un-read messages b���������������������������
+� hy<CR> ngNext msg/thread     b� hyP ngPost a message�b� hy< > ng-/+ by Subjects�b�
+�   hy- ngPrevious msg/thread b� hyD ngDelete msg/close poll b� hy{ } ng-/+ by Author�b�
+�   hyC ngContinuous read     b��������������������������� hy[ ] ng-/+ by 'To User'    b�
+�   hy@MSGREREAD@ ngReread last msg     b� hy@MSGREPLY@ ngReply to last message b� hy( ) ng-/+ by Thread-ID    b�
+�   hy# ngGo to message #     b� hyM ngReply in mail to last b�   hy*  ngToggle Thread View  b�
+�                         b� hyV ngVote on last msg/poll b�                          b�
+������4 hwList Messagesnb�4      �4 hw      Other Commands nb�����
+�                         40�������4 hwSearch/Find nb�������                          b�
+� hyL ngList all messages     b��� hyB ngBypass current sub    b�
+� hyT ngNext ten messages     b� hyY ngMessages to you�b� hyI ngInformation on sub    b�
+� hyN ngNew messages           b� hyU ngYour un-read messages b� hyE ngEdit last message     b�
 � hyH ngHighest ranked msgs   b� hyF ngFind text in messages b� hyQ ngQuit to Main menu     b�
 b�������
 4  hyAnytime: cCtrl-U nc4Who's online  hCtrl-P nc4Send private msg  hCtrl-C nc4Abort cmd/text 0