From a63104b0554ec3cb32bcd67308c2a9b677e9fb8e Mon Sep 17 00:00:00 2001
From: nightfox <>
Date: Tue, 6 Feb 2018 20:19:07 +0000
Subject: [PATCH] Reverting SlyEdit back to version 1.54, since there seem to
 still be problems with the new versions that allow text color selection.

---
 ctrl/SlyDCTColors_Default.cfg      |  126 +-
 ctrl/SlyDCTColors_Midnight.cfg     |  130 +-
 ctrl/SlyEdit.cfg                   |   16 -
 ctrl/SlyIceColors_BlueIce.cfg      |   80 +-
 ctrl/SlyIceColors_EmeraldCity.cfg  |   80 +-
 ctrl/SlyIceColors_FieryInferno.cfg |   80 +-
 ctrl/SlyIceColors_Fire-N-Ice.cfg   |   80 +-
 ctrl/SlyIceColors_GenericBlue.cfg  |   80 +-
 ctrl/SlyIceColors_PurpleHaze.cfg   |   82 +-
 ctrl/SlyIceColors_ShadesOfGrey.cfg |   80 +-
 docs/SlyEdit_ReadMe.txt            |   61 +-
 exec/SlyEdit.js                    | 4920 ++++++++++++----------------
 exec/SlyEdit_DCTStuff.js           |  125 +-
 exec/SlyEdit_IceStuff.js           |  100 +-
 exec/SlyEdit_Misc.js               | 2182 ++++--------
 15 files changed, 3368 insertions(+), 4854 deletions(-)

diff --git a/ctrl/SlyDCTColors_Default.cfg b/ctrl/SlyDCTColors_Default.cfg
index da77d1a139..aee9ba67fe 100644
--- a/ctrl/SlyDCTColors_Default.cfg
+++ b/ctrl/SlyDCTColors_Default.cfg
@@ -1,86 +1,88 @@
 ; This is a color theme file for SlyEdit's DCT Style.
 ; This color scheme mimics DCT Edit's default color scheme.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1c
+QuoteLineColor=nc
 
 ; Border colors
-TopBorderColor1=\1n\1r
-TopBorderColor2=\1n\1r\1h
-EditAreaBorderColor1=\1n\1g
-EditAreaBorderColor2=\1n\1g\1h
-EditModeBrackets=\1n\1k\1h
-EditMode=\1n\1w
+TopBorderColor1=nr
+TopBorderColor2=nrh
+EditAreaBorderColor1=ng
+EditAreaBorderColor2=ngh
+EditModeBrackets=nkh
+EditMode=nw
 
 ; Colors for the top informational area
-TopLabelColor=\1n\1b\1h
-TopLabelColonColor=\1n\1b
-TopFromColor=\1n\1c\1h
-TopFromFillColor=\1n\1c
-TopToColor=\1n\1c\1h
-TopToFillColor=\1n\1c
-TopSubjColor=\1n\1w\1h
-TopSubjFillColor=\1n\1w
-TopAreaColor=\1n\1g\1h
-TopAreaFillColor=\1n\1g
-TopTimeColor=\1n\1y\1h
-TopTimeFillColor=\1n\1r
-TopTimeLeftColor=\1n\1y\1h
-TopTimeLeftFillColor=\1n\1r
-TopInfoBracketColor=\1n\1m
+TopLabelColor=nbh
+TopLabelColonColor=nb
+TopFromColor=nch
+TopFromFillColor=nc
+TopToColor=nch
+TopToFillColor=nc
+TopSubjColor=nwh
+TopSubjFillColor=nw
+TopAreaColor=ngh
+TopAreaFillColor=ng
+TopTimeColor=nyh
+TopTimeFillColor=nr
+TopTimeLeftColor=nyh
+TopTimeLeftFillColor=nr
+TopInfoBracketColor=nm
 
 ; Colors for the quote window
-QuoteWinText=\1n7\1b
-QuoteLineHighlightColor=\1n\1w
-QuoteWinBorderTextColor=\1n7\1r
-QuoteWinBorderColor=\1n\1k7
+QuoteWinText=n7b
+QuoteLineHighlightColor=nw
+QuoteWinBorderTextColor=n7r
+QuoteWinBorderColor=nk7
 
 ; Colors for the bottom row help text
-BottomHelpBrackets=\1n\1k\1h
-BottomHelpKeys=\1n\1r\1h
-BottomHelpFill=\1n\1r
-BottomHelpKeyDesc=\1n\1c
+BottomHelpBrackets=nkh
+BottomHelpKeys=nrh
+BottomHelpFill=nr
+BottomHelpKeyDesc=nc
 
 ; Colors for text boxes
-TextBoxBorder=\1n\1k7
-TextBoxBorderText=\1n\1r7
-TextBoxInnerText=\1n\1b7
-YesNoBoxBrackets=\1n\1k7
-YesNoBoxYesNoText=\1n\1w\1h7
+TextBoxBorder=nk7
+TextBoxBorderText=nr7
+TextBoxInnerText=nb7
+YesNoBoxBrackets=nk7
+YesNoBoxYesNoText=nwh7
 
 ; Colors for the menus
-SelectedMenuLabelBorders=\1n\1w
-SelectedMenuLabelText=\1n\1k7
-UnselectedMenuLabelText=\1n\1w\1h
-MenuBorders=\1n\1k7
-MenuSelectedItems=\1n\1w
-MenuUnselectedItems=\1n\1k7
-MenuHotkeys=\1n\1w\1h7
+SelectedMenuLabelBorders=nw
+SelectedMenuLabelText=nk7
+UnselectedMenuLabelText=nwh
+MenuBorders=nk7
+MenuSelectedItems=nw
+MenuUnselectedItems=nk7
+MenuHotkeys=nwh7
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1g
-listBoxBorderText=\1n\1b\1h
+listBoxBorder=ng
+listBoxBorderText=nbh
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1c
-listBoxItemHighlight=\1n4\1w\1h
+listBoxItemText=nc
+listBoxItemHighlight=n4wh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyDCTColors_Midnight.cfg b/ctrl/SlyDCTColors_Midnight.cfg
index 439febb7eb..0937216ab8 100644
--- a/ctrl/SlyDCTColors_Midnight.cfg
+++ b/ctrl/SlyDCTColors_Midnight.cfg
@@ -1,88 +1,90 @@
 ; This is a color theme file for SlyEdit's DCT Style.
 ; This is a color scheme I call "Midnight".
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1c
+QuoteLineColor=nc
 
 ; Border colors
-TopBorderColor1=\1n\1b
-TopBorderColor2=\1n\1k\1h
-EditAreaBorderColor1=\1n\1b\1h
-EditAreaBorderColor2=\1n\1k\1h
-EditModeBrackets=\1n\1k\1h
-EditMode=\1n\1w
+TopBorderColor1=nb
+TopBorderColor2=nkh
+EditAreaBorderColor1=nbh
+EditAreaBorderColor2=nkh
+EditModeBrackets=nkh
+EditMode=nw
 
 ; Colors for the top informational area
-TopLabelColor=\1n\1b\1h
-TopLabelColonColor=\1n\1b
-TopFromColor=\1n\1c
-TopFromFillColor=\1n\1k\1h
-TopToColor=\1n\1b
-TopToFillColor=\1n\1k\1h
-TopSubjColor=\1n\1k\1h
-TopSubjFillColor=\1n\1k\1h
-TopAreaColor=\1n\1b
-TopAreaFillColor=\1n\1k\1h
-TopTimeColor=\1n\1k\1h
-TopTimeFillColor=\1n\1k\1h
-TopTimeLeftColor=\1n\1k\1h
-TopTimeLeftFillColor=\1n\1k\1h
-TopInfoBracketColor=\1n\1w
+TopLabelColor=nbh
+TopLabelColonColor=nb
+TopFromColor=nc
+TopFromFillColor=nkh
+TopToColor=nb
+TopToFillColor=nkh
+TopSubjColor=nkh
+TopSubjFillColor=nkh
+TopAreaColor=nb
+TopAreaFillColor=nkh
+TopTimeColor=nkh
+TopTimeFillColor=nkh
+TopTimeLeftColor=nkh
+TopTimeLeftFillColor=nkh
+TopInfoBracketColor=nw
 
 ; Colors for the quote window
-QuoteWinText=\1n7\1b
-QuoteLineHighlightColor=\1n\1w
-QuoteWinBorderTextColor=\1n7\1r
-QuoteWinBorderColor=\1n\1k7
+QuoteWinText=n7b
+QuoteLineHighlightColor=nw
+QuoteWinBorderTextColor=n7r
+QuoteWinBorderColor=nk7
 
 ; Colors for the bottom row help text
-BottomHelpBrackets=\1n\1k\1h
-BottomHelpKeys=\1n\1b
-BottomHelpFill=\1n\1k\1h
-BottomHelpKeyDesc=\1n\1c
+BottomHelpBrackets=nkh
+BottomHelpKeys=nb
+BottomHelpFill=nkh
+BottomHelpKeyDesc=nc
 
 ; Colors for text boxes
-TextBoxBorder=\1n\1k\1h
-TextBoxBorderText=\1n\1b\1h
-TextBoxInnerText=\1n\1w
-YesNoBoxBrackets=\1n\1k\1h
-YesNoBoxYesNoText=\1n\1w
+TextBoxBorder=nkh
+TextBoxBorderText=nbh
+TextBoxInnerText=nw
+YesNoBoxBrackets=nkh
+YesNoBoxYesNoText=nw
 
 ; Colors for the menus
-SelectedMenuLabelBorders=\1n\1b
-SelectedMenuLabelText=\1n\1k4
-UnselectedMenuLabelText=\1n\1b
-MenuBorders=\1n\1k\1h
-MenuSelectedItems=\1n\1w
-MenuUnselectedItems=\1n\1b
-MenuHotkeys=\1n\1b\1h
+SelectedMenuLabelBorders=nb
+SelectedMenuLabelText=nk4
+UnselectedMenuLabelText=nb
+MenuBorders=nkh
+MenuSelectedItems=nw
+MenuUnselectedItems=nb
+MenuHotkeys=nbh
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1h\1k
-listBoxBorderText=\1n\1b\1h
+listBoxBorder=nhk
+listBoxBorderText=nbh
 
 ; Colors for the cross-post selection box
-crossPostBorder=\1n\1h\1k
-crossPostBorderText=\1n\1b\1h
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostBorder=nhk
+crossPostBorderText=nbh
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1b\1h
-listBoxItemHighlight=\1n4\1w\1h
+listBoxItemText=nbh
+listBoxItemHighlight=n4wh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyEdit.cfg b/ctrl/SlyEdit.cfg
index 420ae20c12..b1e1c0f1e2 100644
--- a/ctrl/SlyEdit.cfg
+++ b/ctrl/SlyEdit.cfg
@@ -43,22 +43,6 @@ enableTaglines=false
 ; Whether or not to allow editing quote lines
 allowEditQuoteLines=true
 
-; Whether or not to allow the user to select a text color
-allowColorSelection=true
-; Comma-separated list of group names (short name or description) for groups to
-; disallow color selection
-noColorSelectionGrpNames=
-; Comma-separated list of sub-board codes for sub-boards to disallow color
-; selection
-noColorSelectionSubBoardCodes=
-; Comma-separated list of group names (short name or description) for message
-; groups where message color codes should be converted to ANSI codes
-cvtColorToANSIGrpNames=
-; Comma-separated list of sub-board codes for message groups where message color
-; codes should be converted to ANSI codes
-cvtColorToANSISubBoardCodes=
-
-
 ; To use a different color theme file, change the ThemeFilename
 ; setting.  If you want, you can comment out the current setting
 ; (by placing a ; in front of the line) and un-commenting one
diff --git a/ctrl/SlyIceColors_BlueIce.cfg b/ctrl/SlyIceColors_BlueIce.cfg
index 95404c667f..7b23f3e612 100644
--- a/ctrl/SlyIceColors_BlueIce.cfg
+++ b/ctrl/SlyIceColors_BlueIce.cfg
@@ -1,60 +1,62 @@
 ; This is a color theme file for SlyEdit's Ice Style.
 ; This color scheme mimics IceEdit/QuikEdit's "Blue Ice" theme.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1c
+QuoteLineColor=nc
 
 ; Border colors
-BorderColor1=\1n\1b
-BorderColor2=\1n\1b\1h
-KeyInfoLabelColor=\1c\1h
+BorderColor1=nb
+BorderColor2=nbh
+KeyInfoLabelColor=ch
 
 ; Colors for the top informational area
 TopInfoBkgColor=4
-TopLabelColor=\1c\1h
-TopLabelColonColor=\1b\1h
-TopToColor=\1w\1h
-TopFromColor=\1w\1h
-TopSubjectColor=\1w\1h
-TopTimeColor=\1g\1h
-TopTimeLeftColor=\1g\1h
-EditMode=\1c\1h
+TopLabelColor=ch
+TopLabelColonColor=bh
+TopToColor=wh
+TopFromColor=wh
+TopSubjectColor=wh
+TopTimeColor=gh
+TopTimeLeftColor=gh
+EditMode=ch
 
 ; Colors for the quote window
-QuoteWinText=\1n\1h\1w
-QuoteLineHighlightColor=4\1h\1c
-QuoteWinBorderTextColor=\1n\1c\1h
+QuoteWinText=nhw
+QuoteLineHighlightColor=4hc
+QuoteWinBorderTextColor=nch
 
 ; Colors for the multi-choice options
-SelectedOptionBorderColor=\1n\1b\1h4
-SelectedOptionTextColor=\1n\1c\1h4
-UnselectedOptionBorderColor=\1n\1b
-UnselectedOptionTextColor=\1n\1w
+SelectedOptionBorderColor=nbh4
+SelectedOptionTextColor=nch4
+UnselectedOptionBorderColor=nb
+UnselectedOptionTextColor=nw
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1b
-listBoxBorderText=\1n\1b\1h
+listBoxBorder=nb
+listBoxBorderText=nbh
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1c
-listBoxItemHighlight=\1n4\1w\1h
+listBoxItemText=nc
+listBoxItemHighlight=n4wh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyIceColors_EmeraldCity.cfg b/ctrl/SlyIceColors_EmeraldCity.cfg
index 670f161cd9..4924ea808e 100644
--- a/ctrl/SlyIceColors_EmeraldCity.cfg
+++ b/ctrl/SlyIceColors_EmeraldCity.cfg
@@ -1,60 +1,62 @@
 ; This is a color theme file for SlyEdit's Ice Style.
 ; This color scheme mimics IceEdit/QuikEdit's "Emerald City" theme.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1g
+QuoteLineColor=ng
 
 ; Border colors
-BorderColor1=\1n\1g
-BorderColor2=\1n\1g\1h
-KeyInfoLabelColor=\1c\1h
+BorderColor1=ng
+BorderColor2=ngh
+KeyInfoLabelColor=ch
 
 ; Colors for the top informational area
 TopInfoBkgColor=2
-TopLabelColor=\1c\1h
-TopLabelColonColor=\1g\1h
-TopToColor=\1w\1h
-TopFromColor=\1w\1h
-TopSubjectColor=\1w\1h
-TopTimeColor=\1g\1h
-TopTimeLeftColor=\1g\1h
-EditMode=\1c\1h
+TopLabelColor=ch
+TopLabelColonColor=gh
+TopToColor=wh
+TopFromColor=wh
+TopSubjectColor=wh
+TopTimeColor=gh
+TopTimeLeftColor=gh
+EditMode=ch
 
 ; Colors for the quote window
-QuoteWinText=\1n\1h\1w
-QuoteLineHighlightColor=6\1h\1c
-QuoteWinBorderTextColor=\1n\1c\1h
+QuoteWinText=nhw
+QuoteLineHighlightColor=6hc
+QuoteWinBorderTextColor=nch
 
 ; Colors for the multi-choice options
-SelectedOptionBorderColor=\1n\1c\1h6
-SelectedOptionTextColor=\1n\1c\1h6
-UnselectedOptionBorderColor=\1n\1g
-UnselectedOptionTextColor=\1n\1w
+SelectedOptionBorderColor=nch6
+SelectedOptionTextColor=nch6
+UnselectedOptionBorderColor=ng
+UnselectedOptionTextColor=nw
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1g
-listBoxBorderText=\1n\1g\1h
+listBoxBorder=ng
+listBoxBorderText=ngh
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1g
-listBoxItemHighlight=\1n4\1w\1h
+listBoxItemText=ng
+listBoxItemHighlight=n4wh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyIceColors_FieryInferno.cfg b/ctrl/SlyIceColors_FieryInferno.cfg
index c0fd369ede..d62cdc8d3c 100644
--- a/ctrl/SlyIceColors_FieryInferno.cfg
+++ b/ctrl/SlyIceColors_FieryInferno.cfg
@@ -1,60 +1,62 @@
 ; This is a color theme file for SlyEdit's Ice Style.
 ; This color scheme mimics IceEdit/QuikEdit's "Fiery Inferno" theme.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1c
+QuoteLineColor=nc
 
 ; Border colors
-BorderColor1=\1n\1r
-BorderColor2=\1n\1r\1h
-KeyInfoLabelColor=\1y\1h
+BorderColor1=nr
+BorderColor2=nrh
+KeyInfoLabelColor=yh
 
 ; Colors for the top informational area
 TopInfoBkgColor=1
-TopLabelColor=\1y\1h
-TopLabelColonColor=\1r\1h
-TopToColor=\1w\1h
-TopFromColor=\1w\1h
-TopSubjectColor=\1w\1h
-TopTimeColor=\1w\1h
-TopTimeLeftColor=\1w\1h
-EditMode=\1y\1h
+TopLabelColor=yh
+TopLabelColonColor=rh
+TopToColor=wh
+TopFromColor=wh
+TopSubjectColor=wh
+TopTimeColor=wh
+TopTimeLeftColor=wh
+EditMode=yh
 
 ; Colors for the quote window
-QuoteWinText=\1n\1h\1w
-QuoteLineHighlightColor=1\1h\1y
-QuoteWinBorderTextColor=\1n\1y\1h
+QuoteWinText=nhw
+QuoteLineHighlightColor=1hy
+QuoteWinBorderTextColor=nyh
 
 ; Colors for the multi-choice options
-SelectedOptionBorderColor=\1n\1r\1h1
-SelectedOptionTextColor=\1n\1y\1h1
-UnselectedOptionBorderColor=\1n\1r
-UnselectedOptionTextColor=\1n\1w
+SelectedOptionBorderColor=nrh1
+SelectedOptionTextColor=nyh1
+UnselectedOptionBorderColor=nr
+UnselectedOptionTextColor=nw
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1r
-listBoxBorderText=\1n\1r\1h
+listBoxBorder=nr
+listBoxBorderText=nrh
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1r\1h
-listBoxItemHighlight=\1n7\1r
+listBoxItemText=nrh
+listBoxItemHighlight=n7r
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyIceColors_Fire-N-Ice.cfg b/ctrl/SlyIceColors_Fire-N-Ice.cfg
index 3c01071e21..fc92b940d3 100644
--- a/ctrl/SlyIceColors_Fire-N-Ice.cfg
+++ b/ctrl/SlyIceColors_Fire-N-Ice.cfg
@@ -1,60 +1,62 @@
 ; This is a color theme file for SlyEdit's Ice Style.
 ; This color scheme mimics IceEdit/QuikEdit's "Fire & Ice" theme.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1c
+QuoteLineColor=nc
 
 ; Border colors
-BorderColor1=\1n\1r
-BorderColor2=\1n\1r\1h
-KeyInfoLabelColor=\1y\1h
+BorderColor1=nr
+BorderColor2=nrh
+KeyInfoLabelColor=yh
 
 ; Colors for the top informational area
 TopInfoBkgColor=4
-TopLabelColor=\1y\1h
-TopLabelColonColor=\1k\1h
-TopToColor=\1w\1h
-TopFromColor=\1w\1h
-TopSubjectColor=\1w\1h
-TopTimeColor=\1w\1h
-TopTimeLeftColor=\1w\1h
-EditMode=\1y\1h
+TopLabelColor=yh
+TopLabelColonColor=kh
+TopToColor=wh
+TopFromColor=wh
+TopSubjectColor=wh
+TopTimeColor=wh
+TopTimeLeftColor=wh
+EditMode=yh
 
 ; Colors for the quote window
-QuoteWinText=\1n\1h\1w
-QuoteLineHighlightColor=4\1h\1y
-QuoteWinBorderTextColor=\1n\1y\1h
+QuoteWinText=nhw
+QuoteLineHighlightColor=4hy
+QuoteWinBorderTextColor=nyh
 
 ; Colors for the multi-choice options
-SelectedOptionBorderColor=\1n\1b\1h4
-SelectedOptionTextColor=\1n\1y\1h4
-UnselectedOptionBorderColor=\1n\1b
-UnselectedOptionTextColor=\1n\1w
+SelectedOptionBorderColor=nbh4
+SelectedOptionTextColor=nyh4
+UnselectedOptionBorderColor=nb
+UnselectedOptionTextColor=nw
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1r
-listBoxBorderText=\1n\1b\1h
+listBoxBorder=nr
+listBoxBorderText=nbh
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1b\1h
-listBoxItemHighlight=\1n4\1w\1h
+listBoxItemText=nbh
+listBoxItemHighlight=n4wh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyIceColors_GenericBlue.cfg b/ctrl/SlyIceColors_GenericBlue.cfg
index b7849c162c..2643030bc2 100644
--- a/ctrl/SlyIceColors_GenericBlue.cfg
+++ b/ctrl/SlyIceColors_GenericBlue.cfg
@@ -1,60 +1,62 @@
 ; This is a color theme file for SlyEdit's Ice Style.
 ; This color scheme mimics IceEdit/QuikEdit's "Generic Blue" theme.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1c
+QuoteLineColor=nc
 
 ; Border colors
-BorderColor1=\1n\1b
-BorderColor2=\1n\1b
-KeyInfoLabelColor=\1c\1h
+BorderColor1=nb
+BorderColor2=nb
+KeyInfoLabelColor=ch
 
 ; Colors for the top informational area
 TopInfoBkgColor=4
-TopLabelColor=\1c\1h
-TopLabelColonColor=\1b\1h
-TopToColor=\1w\1h
-TopFromColor=\1w\1h
-TopSubjectColor=\1w\1h
-TopTimeColor=\1b\1h
-TopTimeLeftColor=\1b\1h
-EditMode=\1c\1h
+TopLabelColor=ch
+TopLabelColonColor=bh
+TopToColor=wh
+TopFromColor=wh
+TopSubjectColor=wh
+TopTimeColor=bh
+TopTimeLeftColor=bh
+EditMode=ch
 
 ; Colors for the quote window
-QuoteWinText=\1n\1h\1w
-QuoteLineHighlightColor=4\1h\1c
-QuoteWinBorderTextColor=\1n\1c\1h
+QuoteWinText=nhw
+QuoteLineHighlightColor=4hc
+QuoteWinBorderTextColor=nch
 
 ; Colors for the multi-choice options
-SelectedOptionBorderColor=\1n\1b\1h4
-SelectedOptionTextColor=\1n\1c\1h4
-UnselectedOptionBorderColor=\1n\1w
-UnselectedOptionTextColor=\1n\1w
+SelectedOptionBorderColor=nbh4
+SelectedOptionTextColor=nch4
+UnselectedOptionBorderColor=nw
+UnselectedOptionTextColor=nw
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1b
-listBoxBorderText=\1n\1b\1h
+listBoxBorder=nb
+listBoxBorderText=nbh
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1b\1h
-listBoxItemHighlight=\1n4\1w\1h
+listBoxItemText=nbh
+listBoxItemHighlight=n4wh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyIceColors_PurpleHaze.cfg b/ctrl/SlyIceColors_PurpleHaze.cfg
index 285796923a..f75fba4181 100644
--- a/ctrl/SlyIceColors_PurpleHaze.cfg
+++ b/ctrl/SlyIceColors_PurpleHaze.cfg
@@ -1,59 +1,61 @@
 ; This is a color theme file for SlyEdit's Ice Style.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1c
+QuoteLineColor=nc
 
 ; Border colors
-BorderColor1=\1n\1m
-BorderColor2=\1n\1m\1h
-KeyInfoLabelColor=\1n\1m
+BorderColor1=nm
+BorderColor2=nmh
+KeyInfoLabelColor=nm
 
 ; Colors for the top informational area
-TopInfoBkgColor=\1n
-TopLabelColor=\1n\1m
-TopLabelColonColor=\1b\1h
-TopToColor=\1m\1h
-TopFromColor=\1m\1h
-TopSubjectColor=\1m\1h
-TopTimeColor=\1n\1m\1h
-TopTimeLeftColor=\1n\1m\1h
-EditMode=\1n\1c
+TopInfoBkgColor=n
+TopLabelColor=nm
+TopLabelColonColor=bh
+TopToColor=mh
+TopFromColor=mh
+TopSubjectColor=mh
+TopTimeColor=nmh
+TopTimeLeftColor=nmh
+EditMode=nc
 
 ; Colors for the quote window
-QuoteWinText=\1n\1h\1w
-QuoteLineHighlightColor=4\1h\1m
-QuoteWinBorderTextColor=\1n\1c\1h
+QuoteWinText=nhw
+QuoteLineHighlightColor=4hm
+QuoteWinBorderTextColor=nch
 
 ; Colors for the multi-choice options
-SelectedOptionBorderColor=\1n\1b\1h4
-SelectedOptionTextColor=\1n\1c\1h4
-UnselectedOptionBorderColor=\1n\1b
-UnselectedOptionTextColor=\1n\1w
+SelectedOptionBorderColor=nbh4
+SelectedOptionTextColor=nch4
+UnselectedOptionBorderColor=nb
+UnselectedOptionTextColor=nw
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1m
-listBoxBorderText=\1n\1b\1h
+listBoxBorder=nm
+listBoxBorderText=nbh
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1m\1h
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4mh
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1c
-listBoxItemHighlight=\1n4\1m\1h
+listBoxItemText=nc
+listBoxItemHighlight=n4mh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/ctrl/SlyIceColors_ShadesOfGrey.cfg b/ctrl/SlyIceColors_ShadesOfGrey.cfg
index c1024ddd10..6b805a9eb2 100644
--- a/ctrl/SlyIceColors_ShadesOfGrey.cfg
+++ b/ctrl/SlyIceColors_ShadesOfGrey.cfg
@@ -1,60 +1,62 @@
 ; This is a color theme file for SlyEdit's Ice Style.
 ; This color scheme mimics IceEdit/QuikEdit's "Shades of Gray" theme.
 
+; Text edit color
+TextEditColor=nw
 ; The color to use for quoted lines in the message
-QuoteLineColor=\1n\1k\1h
+QuoteLineColor=nkh
 
 ; Border colors
-BorderColor1=\1n\1k\1h
-BorderColor2=\1n\1b
-KeyInfoLabelColor=\1n\1w
+BorderColor1=nkh
+BorderColor2=nb
+KeyInfoLabelColor=nw
 
 ; Colors for the top informational area
 TopInfoBkgColor=4
-TopLabelColor=\1n\1w4
-TopLabelColonColor=\1b\1h
-TopToColor=\1w\1h
-TopFromColor=\1w\1h
-TopSubjectColor=\1w\1h
-TopTimeColor=\1w\1h
-TopTimeLeftColor=\1w\1h
-EditMode=\1n\1w4
+TopLabelColor=nw4
+TopLabelColonColor=bh
+TopToColor=wh
+TopFromColor=wh
+TopSubjectColor=wh
+TopTimeColor=wh
+TopTimeLeftColor=wh
+EditMode=nw4
 
 ; Colors for the quote window
-QuoteWinText=\1n\1h\1w
-QuoteLineHighlightColor=7\1h\1w
-QuoteWinBorderTextColor=\1n\1w
+QuoteWinText=nhw
+QuoteLineHighlightColor=7hw
+QuoteWinBorderTextColor=nw
 
 ; Colors for the multi-choice options
-SelectedOptionBorderColor=\1n\1w\1h7
-SelectedOptionTextColor=\1n\1w\1h7
-UnselectedOptionBorderColor=\1n\1k\1h
-UnselectedOptionTextColor=\1n\1w\1h
+SelectedOptionBorderColor=nwh7
+SelectedOptionTextColor=nwh7
+UnselectedOptionBorderColor=nkh
+UnselectedOptionTextColor=nwh
 
 ; Color settings for list boxes
-listBoxBorder=\1n\1h\1k
-listBoxBorderText=\1n\1w
+listBoxBorder=nhk
+listBoxBorderText=nw
 
 ; Colors for the cross-post selection box
-crossPostMsgAreaNum=\1n\1h\1w
-crossPostMsgAreaNumHighlight=\1n4\1h\1w
-crossPostMsgAreaDesc=\1n\1c
-crossPostMsgAreaDescHighlight=\1n4\1c
-crossPostChk=\1n\1h\1y
-crossPostChkHighlight=\1n4\1h\1y
-crossPostMsgGrpMark=\1n\1h\1g
-crossPostMsgGrpMarkHighlight=\1n4\1h\1g
+crossPostMsgAreaNum=nhw
+crossPostMsgAreaNumHighlight=n4hw
+crossPostMsgAreaDesc=nc
+crossPostMsgAreaDescHighlight=n4c
+crossPostChk=nhy
+crossPostChkHighlight=n4hy
+crossPostMsgGrpMark=nhg
+crossPostMsgGrpMarkHighlight=n4hg
 
 ; For the text items in list boxes
-listBoxItemText=\1n\1w
-listBoxItemHighlight=\1n4\1w\1h
+listBoxItemText=nw
+listBoxItemHighlight=n4wh
 
 ; Colors for message saving and sub-board post info when exiting SlyEdit
-msgWillBePostedHdr=\1n\1c
-msgPostedGrpHdr=\1n\1h\1b
-msgPostedSubBoardName=\1n\1g
-msgPostedOriginalAreaText=\1n\1c
-msgHasBeenSavedText=\1n\1h\1c
-msgAbortedText=\1n\1h\1m
-emptyMsgNotSentText=\1n\1h\1m
-genMsgErrorText=\1n\1h\1m
\ No newline at end of file
+msgWillBePostedHdr=nc
+msgPostedGrpHdr=nhb
+msgPostedSubBoardName=ng
+msgPostedOriginalAreaText=nc
+msgHasBeenSavedText=nhc
+msgAbortedText=nhm
+emptyMsgNotSentText=nhm
+genMsgErrorText=nhm
\ No newline at end of file
diff --git a/docs/SlyEdit_ReadMe.txt b/docs/SlyEdit_ReadMe.txt
index 4638b56547..459e3a7364 100644
--- a/docs/SlyEdit_ReadMe.txt
+++ b/docs/SlyEdit_ReadMe.txt
@@ -1,6 +1,6 @@
                          SlyEdit message editor
-                              Version 1.59
-                        Release date: 2018-02-01
+                              Version 1.54
+                        Release date: 2017-12-26
 
                                   by
 
@@ -198,7 +198,7 @@ BBS machine):
 Help keys                                     Slash commands (on blank line)
 ---------                                     ------------------------------
 Ctrl-G       : General help                 � /A      : Abort
-Ctrl-L       : Command key list (this list) � /S      : Save
+Ctrl-P       : Command key help             � /S      : Save
 Ctrl-R       : Program information          � /Q      : Quote message
 Ctrl-T       : List text replacements       � /T      : List text replacements
                                             � /U      : Your settings
@@ -207,11 +207,11 @@ Command/edit keys
 -----------------
 Ctrl-A       : Abort message                � PageUp  : Page up
 Ctrl-Z       : Save message                 � PageDown: Page down
-Ctrl-Q       : Quote message                � Ctrl-S  : Search for text
+Ctrl-Q       : Quote message                � Ctrl-N  : Find text
 Insert/Ctrl-I: Toggle insert/overwrite mode � Ctrl-D  : Delete line
 ESC          : Command menu
-Ctrl-U       : Your settings                � Ctrl-K  : Choose text color
 Ctrl-O       : Import a file                � Ctrl-X  : Export to file
+Ctrl-U       : Your settings
 
 
 5. Digital Distortion Message Lister note
@@ -341,31 +341,6 @@ enableTagLines                    Whether or not to enable the option for users
                                   setting, which users can configure in their
                                   own settings.
 
-allowColorSelection               Whether or not to allow users to select text
-                                  colors for their message.  Valid values are
-                                  true and false.
-
-noColorSelectionGrpNames          A comma-separated list of message group names
-                                  where users are not allowed to select colors
-                                  for their message.  These can be the group's
-                                  short names or their descriptions.
-
-noColorSelectionSubBoardCodes     A comma-separated list of message sub-board
-                                  internal codes where users are not allowed
-                                  to select colors for their message.
-
-cvtColorToANSIGrpNames            A comma-separated list of group names for
-                                  message groups where  text color codes should
-                                  be converted to ANSI codes.  These can be the
-                                  group's short names or their descriptions.
-                                  This can also be "All" to have SlyEdit
-                                  convert to ANSI for all message groups.
-
-
-cvtColorToANSISubBoardCodes       A comma-separated list of sub-board internal
-                                  codes where text color codes should be
-                                  converted to ANSI codes.
-
 Ice colors
 ----------
 Setting                           Description
@@ -685,12 +660,12 @@ listBoxItemHighlight              The color to use for the currently selected
 
 10. Text replacements (AKA Macros)
 ==================================
-Text replacements (AKA Macros), which lets you (the sysop) define words to be
-replaced with other text as the user types a message.  This feature can be
-used, for instance, to replace commonly misspelled words with their correct
-versions or to replace swear words with less offensive words.  This feature is
-toggled by the enableTextReplacements option in SlyEdit.cfg can can have one of
-three values:
+SlyEdit version 1.29 added text replacements (AKA Macros), which lets you (the
+sysop) define words to be replaced with other text as the user types a message.
+This feature can be used, for instance, to replace commonly misspelled words
+with their correct versions or to replace swear words with less offensive
+words.  This feature is toggled by the enableTextReplacements option in
+SlyEdit.cfg can can have one of three values:
 false : Text replacement is disabled
 true  : Text replacement is enabled and performed as literal search and replace
 regex : Text replacement is enabled using regular expressions as implemented by
@@ -699,14 +674,12 @@ regex : Text replacement is enabled using regular expressions as implemented by
         JavaScript-C or "SpiderMonkey").
 
 The text searches are performed on single words only, as the user types the
-message, and are replaced by whatever text you configure for the word.
-Synchronet color/attribute codes are allowed in the replacement text, but the
-color/attribute codes will be stripped if colors are not allowed according to
-the SlyEdit configuration.  The configuration for text replacing is read from a
-configuration file called SlyEdit_TextReplacements.cfg, which is plain text and
-can be placed in either sbbs/ctrl or in the same directory as SlyEdit's .js
-files (SlyEdit.js, SlyEdit_Misc.js, etc.).  Each line in
-SlyEdit_TextReplacements.cfg needs to have the following format:
+message, and are replaced by whatever text you configure for the word.  The
+configuration for text replacing is read from a configuration file called
+SlyEdit_TextReplacements.cfg, which is plain text and can be placed in either
+sbbs/ctrl or in the same directory as SlyEdit's .js files (SlyEdit.js,
+SlyEdit_Misc.js, etc.).  Each line in SlyEdit_TextReplacements.cfg needs to
+have the following format:
 originalWord=replacementText
 where originalWord is the word to be replaced, and replacementText is the text
 to replace the word with.
diff --git a/exec/SlyEdit.js b/exec/SlyEdit.js
index 460d160287..f3ef2392c9 100644
--- a/exec/SlyEdit.js
+++ b/exec/SlyEdit.js
@@ -34,56 +34,6 @@
  *                              or more non-space characters before the >.  Also
  *                              fixed an issue where wrapped quote lines were
  *                              sometimes missing the quote line prefix.
- * 2017-12-27 Eric Oulashin     Version 1.55 Beta
- *                              Started working on text color selection again
- * 2018-01-04 Eric Oulashin     Version 1.55
- *                              Releasing this version, with text color support.
- *                              It seems to be working fairly well overall.
- * 2018-01-05 Eric Oulashin     Version 1.56
- *                              Fixed a bug reported by Al: When saving a message
- *                              with /s on a blank line, SlyEdit was quitting with
- *                              an error due to a non-existent edit line.
- * 2018-01-08 Eric Oulashin     Version 1.57
- *                              Updated the settings noColorSelectionInGrpNames,
- *                              noColorSelectionInSubBoardCodes, cvtColorToANSIGrpNames,
- *                              and cvtColorToANSISubBoardCodes to be comma-separated
- *                              only (instead of either comma or space-separated),
- *                              to keep things simple and in case there are any spaces
- *                              in any message group descriptions.
- *                              Removed the TextEditColor setting from the color theme
- *                              files, since that probably no longer makes sense now
- *                              that the user can change the text color.
- *                              When inserting quote lines into a message, ensured the
- *                              quote lines have the "normal" attribute.  The user's
- *                              chosen colors are applied to their own text lines.
- *                              When importing a file (for sysops only), any color or
- *                              attribute codes are stripped if colors are not allowed
- *                              in the current message area according to the SlyEdit
- *                              configuration file.
- *                              Fixed a bug in refreshing the help text line on the
- *                              bottom of the screen after choosing text colors.
- *                              Also, updated to remove ANSI from quote lines so that
- *                              quote lines look better.
- * 2018-01-27 Eric Oulashin     Version 1.58 Beta
- *                              Bug fix:
- *                              - Incorrect color used when refreshing quote lines
- *                                (such as when the User Settings dialog is closed)
- *                              - After changing the text color for text above a
- *                                quote line, the incorrect color was used for
- *                                refreshing quote lines below it
- *                              Also, when saving a message, SlyEdit now removes any
- *                              stray ASCII-1 characters that aren't part of a
- *                              Synchronet color code.
- * 2018-01-28 Eric Oulashin     Version 1.58
- *                              Releasing this version
- * 2018-02-01 Eric Oulashin     Version 1.59
- *                              Updated to strip control characters from the
- *                              information read from the drop file.  Also made a bug
- *                              fix: When backspacing, it now properly removes any
- *                              Synchronet attribute codes immediately after the
- *                              character being deleted.
- * 2018-02-02 Eric Oulashin     Version 1.60
- *                              Bug fixes for edit line indexes with a wide terminal
  */
 
 /* Command-line arguments:
@@ -129,10 +79,10 @@ var gConfigSettings = ReadSlyEditConfigFile();
 var gUserSettings = ReadUserSettingsFile(gConfigSettings);
 // Load any specified 3rd-party startup scripts
 for (var i = 0; i < gConfigSettings.thirdPartyLoadOnStart.length; ++i)
-	load(gConfigSettings.thirdPartyLoadOnStart[i]);
+  load(gConfigSettings.thirdPartyLoadOnStart[i]);
 // Execute any provided startup JavaScript commands
 for (var i = 0; i < gConfigSettings.runJSOnStart.length; ++i)
-	eval(gConfigSettings.runJSOnStart[i]);
+  eval(gConfigSettings.runJSOnStart[i]);
 
 const EDITOR_PROGRAM_NAME = "SlyEdit";
 const ERRORMSG_PAUSE_MS = 1500;
@@ -161,8 +111,8 @@ if (!console.term_supports(USER_ANSI))
 }
 
 // Constants
-const EDITOR_VERSION = "1.60";
-const EDITOR_VER_DATE = "2018-02-02";
+const EDITOR_VERSION = "1.54";
+const EDITOR_VER_DATE = "2017-12-26";
 
 
 // Program variables
@@ -185,8 +135,9 @@ if (console.screen_columns < 80)
 // Colors
 var gQuoteWinTextColor = "\1n\1" + "7\1k";   // Normal text color for the quote window (DCT default)
 var gQuoteLineHighlightColor = "\1n\1w"; // Highlighted text color for the quote window (DCT default)
-var gTextAttrs = "\1n";                  // The text color for edit mode
+var gTextAttrs = "\1n\1w";               // The text color for edit mode
 var gQuoteLineColor = "\1n\1c";          // The text color for quote lines
+var gUseTextAttribs = false;              // Will be set to true if text colors start to be used
 
 // gQuotePrefix contains the text to prepend to quote lines.
 // gQuotePrefix will later be updated to include the "To" user's
@@ -208,9 +159,9 @@ var gCrossPostMsgSubs = new Object();
 // Return value: Boolean - Whether or not the property is the name of one of the
 //               functions in the gCrossPostMsgSubs object
 gCrossPostMsgSubs.propIsFuncName = function(pPropName) {
-	return((pPropName == "propIsFuncName") || (pPropName == "add") || (pPropName == "remove") ||
-	       (pPropName == "subCodeExists") || (pPropName == "numMsgGrps") ||
-	       (pPropName == "numSubBoards"));
+   return((pPropName == "propIsFuncName") || (pPropName == "add") || (pPropName == "remove") ||
+           (pPropName == "subCodeExists") || (pPropName == "numMsgGrps") ||
+           (pPropName == "numSubBoards"));
 };
 // This function returns whether or not the a message sub-coard code exists in
 // gCrossPostMsgSubs.
@@ -218,14 +169,14 @@ gCrossPostMsgSubs.propIsFuncName = function(pPropName) {
 // Parameters:
 //  pSubCode: The sub-code to look for
 gCrossPostMsgSubs.subCodeExists = function(pSubCode) {
-	if (typeof(pSubCode) != "string")
-		return false;
-
-	var grpIndex = msg_area.sub[pSubCode].grp_index;
-	var foundIt = false;
-	if (this.hasOwnProperty(grpIndex))
-		foundIt = this[grpIndex].hasOwnProperty(pSubCode);
-	return foundIt;
+  if (typeof(pSubCode) != "string")
+    return false;
+
+  var grpIndex = msg_area.sub[pSubCode].grp_index;
+  var foundIt = false;
+  if (this.hasOwnProperty(grpIndex))
+    foundIt = this[grpIndex].hasOwnProperty(pSubCode);
+  return foundIt;
 };
 // This function adds a sub-board code to gCrossPostMsgSubs.
 //
@@ -247,41 +198,41 @@ gCrossPostMsgSubs.add = function(pSubCode) {
 // Parameters:
 //  pSubCode: The sub-code to remove
 gCrossPostMsgSubs.remove = function(pSubCode) {
-	if (typeof(pSubCode) != "string")
-		return;
+  if (typeof(pSubCode) != "string")
+    return;
 
-	var grpIndex = msg_area.sub[pSubCode].grp_index;
-	if (this.hasOwnProperty(grpIndex))
-	{
-		delete this[grpIndex][pSubCode];
-		if (numObjProperties(this[grpIndex]) == 0)
-			delete this[grpIndex];
-	}
+  var grpIndex = msg_area.sub[pSubCode].grp_index;
+  if (this.hasOwnProperty(grpIndex))
+  {
+    delete this[grpIndex][pSubCode];
+    if (numObjProperties(this[grpIndex]) == 0)
+      delete this[grpIndex];
+  }
 };
 // This function returns the number of message groups in
 // gCrossPostMsgSubs.
 gCrossPostMsgSubs.numMsgGrps = function() {
-	var msgGrpCount = 0;
-	for (var prop in this)
-	{
-		if (!this.propIsFuncName(prop))
-			++msgGrpCount;
-	}
-	return msgGrpCount;
+  var msgGrpCount = 0;
+  for (var prop in this)
+  {
+    if (!this.propIsFuncName(prop))
+      ++msgGrpCount;
+  }
+  return msgGrpCount;
 };
 // This function returns the number of sub-boards the user has chosen to post
 // the message into.
 gCrossPostMsgSubs.numSubBoards = function () {
-	var numMsgSubs = 0;
-	for (var grpIndex in this)
-	{
-		if (!this.propIsFuncName(grpIndex))
-		{
-			for (var subCode in gCrossPostMsgSubs[grpIndex])
-				++numMsgSubs;
-		}
-	}
-	return numMsgSubs;
+  var numMsgSubs = 0;
+  for (var grpIndex in this)
+  {
+    if (!this.propIsFuncName(grpIndex))
+    {
+      for (var subCode in gCrossPostMsgSubs[grpIndex])
+        ++numMsgSubs;
+    }
+  }
+  return numMsgSubs;
 }
 
 
@@ -303,6 +254,7 @@ if (EDITOR_STYLE == "DCT")
 	gEditTop = 6;
 	gQuoteWinTextColor = gConfigSettings.DCTColors.QuoteWinText;
 	gQuoteLineHighlightColor = gConfigSettings.DCTColors.QuoteLineHighlightColor;
+	gTextAttrs = gConfigSettings.DCTColors.TextEditColor;
 	gQuoteLineColor = gConfigSettings.DCTColors.QuoteLineColor;
 
 	// Function pointers for the DCTEdit-style screen update functions
@@ -322,6 +274,7 @@ else if (EDITOR_STYLE == "ICE")
 	gEditTop = 5;
 	gQuoteWinTextColor = gConfigSettings.iceColors.QuoteWinText;
 	gQuoteLineHighlightColor = gConfigSettings.iceColors.QuoteLineHighlightColor;
+	gTextAttrs = gConfigSettings.iceColors.TextEditColor;
 	gQuoteLineColor = gConfigSettings.iceColors.QuoteLineColor;
 
 	// Function pointers for the IceEdit-style screen update functions
@@ -354,7 +307,7 @@ var gQuoteLinesTopIndex = 0;   // Index of the first displayed quote line
 var gQuoteLinesIndex = 0;      // Index of the current quote line
 // The gEditLines array will contain TextLine objects storing the line
 // information.
-var gEditLines = [];
+var gEditLines = new Array();
 var gEditLinesIndex = 0;      // Index into gEditLines for the line being edited
 var gTextLineIndex = 0;       // Index into the current text line being edited
 // Format strings used for printf() to display text in the edit area
@@ -373,6 +326,12 @@ function clearEditAreaBuffer()
 		gEditAreaBuffer[lineNum] = "";
 }
 clearEditAreaBuffer();
+// gTxtreplacements will be an associative array that stores words (in upper
+// case) to be replaced with other words.
+var gNumTxtReplacements = 0;
+var gTxtReplacements = new Object();
+if (gConfigSettings.enableTextReplacements)
+   gNumTxtReplacements = populateTxtReplacements(gTxtReplacements, gConfigSettings.textReplacementsUseRegex);
 
 // Set some stuff up for message editing
 var gUseQuotes = true;
@@ -418,12 +377,12 @@ console.clear();
 
 // Read the message from name, to name, and subject from the drop file
 // (msginf in the node directory).
-var gMsgAreaNameInfo = null; // Will store the value returned by getCurMsgInfo().
+var gMsgAreaInfo = null; // Will store the value returned by getCurMsgInfo().
 var setMsgAreaInfoObj = false;
 var gMsgSubj = "";
 var gFromName = user.alias;
 var gToName = gInputFilename;
-var gMsgAreaName = "";
+var gMsgArea = "";
 var gTaglineFile = "";
 var dropFileTime = -Infinity;
 var dropFileName = file_getcase(system.node_dir + "msginf");
@@ -438,15 +397,15 @@ if (dropFileName != undefined)
 			info = dropFile.readAll();
 			dropFile.close();
 
-			gFromName = strip_ctrl(info[0]);
-			gToName = strip_ctrl(info[1]);
-			gMsgSubj = strip_ctrl(info[2]);
-			gMsgAreaName = strip_ctrl(info[4]);
+			gFromName = info[0];
+			gToName = info[1];
+			gMsgSubj = info[2];
+			gMsgArea = info[4];
 
 			// Now that we know the name of the message area
 			// that the message is being posted in, call
-			// getCurMsgInfo() to set gMsgAreaNameInfo.
-			gMsgAreaNameInfo = getCurMsgInfo(gMsgAreaName);
+			// getCurMsgInfo() to set gMsgAreaInfo.
+			gMsgAreaInfo = getCurMsgInfo(gMsgArea);
 			setMsgAreaInfoObj = true;
 
 			// If the msginf file has 7 lines, then the 7th line is the full
@@ -460,26 +419,19 @@ if (dropFileName != undefined)
 	}
 	file_remove(dropFileName);
 }
-// If gMsgAreaNameInfo hasn't been set yet, then set it.
+// If gMsgAreaInfo hasn't been set yet, then set it.
 if (!setMsgAreaInfoObj)
 {
-	gMsgAreaNameInfo = getCurMsgInfo(gMsgAreaName);
+	gMsgAreaInfo = getCurMsgInfo(gMsgArea);
 	setMsgAreaInfoObj = true;
 }
 
-// gTxtreplacements will be an associative array that stores words (in upper
-// case) to be replaced with other words.
-var gNumTxtReplacements = 0;
-var gTxtReplacements = new Object();
-if (gConfigSettings.enableTextReplacements)
-	gNumTxtReplacements = populateTxtReplacements(gTxtReplacements, gConfigSettings.textReplacementsUseRegex);
-
 // Set a variable to store whether or not cross-posting can be done.
-var gCanCrossPost = (gConfigSettings.allowCrossPosting && postingInMsgSubBoard(gMsgAreaName));
+var gCanCrossPost = (gConfigSettings.allowCrossPosting && postingInMsgSubBoard(gMsgArea));
 // If the user is posting in a message sub-board, then add its information
 // to gCrossPostMsgSubs.
-if (postingInMsgSubBoard(gMsgAreaName))
-	gCrossPostMsgSubs.add(gMsgAreaNameInfo.subBoardCode);
+if (postingInMsgSubBoard(gMsgArea))
+	gCrossPostMsgSubs.add(gMsgAreaInfo.subBoardCode);
 
 // Open the quote file / message file
 readQuoteOrMessageFile();
@@ -494,83 +446,17 @@ var gOldSubj = gMsgSubj;
 // Now it's edit time.
 var exitCode = doEditLoop();
 
-var crossPostEditLines = []; // For a copy of the edit lines, for cross-posting, in case of different color settings
-// Do some post-edit processing of the message lines (in gEditLines)
+// Remove any extra blank lines that may be at the end of
+// the message (in gEditLines).
 if ((exitCode == 0) && (gEditLines.length > 0))
 {
-	// Remove any extra blank lines that may be at the end of
-	// the message lines
 	var lineIndex = gEditLines.length - 1;
-	while ((lineIndex > 0) && (lineIndex < gEditLines.length) && (gEditLines[lineIndex].displayLength() == 0))
+	while ((lineIndex > 0) && (lineIndex < gEditLines.length) &&
+	   (gEditLines[lineIndex].length() == 0))
 	{
 		gEditLines.splice(lineIndex, 1);
 		--lineIndex;
 	}
-
-	/////
-	// Color processing
-
-	// If there are other message areas selected for cross-posting,
-	// save a copy of the message lines so we can do color processing
-	// as needed for the other message areas to be cross-posted into
-	if (gCrossPostMsgSubs.numMsgGrps() > 0)
-	{
-		for (var i = 0; i < gEditLines.length; ++i)
-		{
-			var editLineCopy = new TextLine();
-			editLineCopy.text = gEditLines[i].text;
-			editLineCopy.hardNewlineEnd = gEditLines[i].hardNewlineEnd;
-			editLineCopy.isQuoteLine = gEditLines[i].isQuoteLine;
-			editLineCopy.text = gEditLines[i].text;
-			crossPostEditLines.push(editLineCopy);
-		}
-	}
-
-	// If the message text contains only "\1n" or "\1n\1w" attribute codes,
-	// then strip them all out.  Also, if other Synchronet color codes are
-	// found, then check to see if color codes should be converted to ANSI,
-	// and if so, do it.
-	var sawOtherSyncAttrs = false;
-	var syncAttrRegexWithoutNormalOrWhite = /[krgybmc01234567hipq,;\.dtl<>\[\]asz]/i;
-	for (var i = 0; (i < gEditLines.length) && !sawOtherSyncAttrs; ++i)
-		sawOtherSyncAttrs = syncAttrRegexWithoutNormalOrWhite.test(gEditLines[i].text);
-	if (sawOtherSyncAttrs)
-	{
-		if (shouldConvertMsgColorsToANSIInMsgArea(gMsgAreaName, bbs.cursub_code))
-		{
-			for (var i = 0; i < gEditLines.length; ++i)
-				gEditLines[i].text = SyncAttrsToANSI(gEditLines[i].text);
-		}
-	}
-	else
-	{
-		// Check to see if there are only normal or normal & white attribute codes
-		var sawNormalAttrOnly = false;
-		var sawNormalAndWhiteOnly = false;
-		for (var i = 0; (i < gEditLines.length) && !sawNormalAttrOnly && !sawNormalAndWhiteOnly; ++i)
-		{
-			sawNormalAttrOnly = /n/gi.test(gEditLines[i].text);
-			sawNormalAndWhiteOnly = /nw/gi.test(gEditLines[i].text);
-		}
-		if (sawNormalAttrOnly || sawNormalAndWhiteOnly)
-		{
-			// Remove the normal & normal-white attribute codes.
-			for (var i = 0; i < gEditLines.length; ++i)
-			{
-				gEditLines[i].text = gEditLines[i].text.replace(/[nw]/ig, "");
-				if (crossPostEditLines.length > i)
-					crossPostEditLines[i].text = gEditLines[i].text.replace(/[nw]/ig, "");
-			}
-		}
-	}
-	// Remove any stray ASCII-1 characters that aren't part of a Synchronet attribute
-	// code, if there are any.
-	for (var i = 0; i < gEditLines.length; ++i)
-	{
-		gEditLines[i].text = removeStrayANSIOneChars(gEditLines[i].text);
-		if (crossPostEditLines.length > i)
-			crossPostEditLines[i].text = removeStrayANSIOneChars(crossPostEditLines[i].text);
-	}
 }
 
 // Clear the screen and display the end-of-program information (if the setting
@@ -578,8 +464,8 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 console.clear("\1n");
 if (gConfigSettings.displayEndInfoScreen)
 {
-	displayProgramExitInfo(false);
-	console.crlf();
+   displayProgramExitInfo(false);
+   console.crlf();
 }
 
 // If the user wrote & saved a message, then output the message
@@ -589,23 +475,56 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 {
 	// Store whether the user is still posting the message in the original sub-board
 	// and whether that's the only sub-board they're posting in.
-	var postingInOriginalSubBoard = gCrossPostMsgSubs.subCodeExists(gMsgAreaNameInfo.subBoardCode);
+	var postingInOriginalSubBoard = gCrossPostMsgSubs.subCodeExists(gMsgAreaInfo.subBoardCode);
 	var postingOnlyInOriginalSubBoard = (postingInOriginalSubBoard && (gCrossPostMsgSubs.numSubBoards() == 1));
 
-	// If some message areas have been selected for cross-posting, do the
-	// cross-posting.
+	// If some message areas have been selected for cross-posting, then otuput
+	// which areas will be cross-posted into, and do the cross-posting.
 	var crossPosted = false;
 	if (gCrossPostMsgSubs.numMsgGrps() > 0)
 	{
-		// Read the user's signature, in case they have one
-		var msgSigInfo = readUserSigFile();
-
-		// Create a single string containing the user's entire message.
-		var msgObj = {
-			msgContents: ""
-		};
+		// If the will cross-post into other sub-boards, then create a string containing
+		// the user's entire message.
+		var msgContents = "";
 		if (!postingOnlyInOriginalSubBoard)
-			msgObj.msgContents = concatMsgEditLines(crossPostEditLines, msgSigInfo, false);
+		{
+			// Append each line to msgContents.  Then,
+			//  - If using Synchronet 3.15 or higher:
+			//    Depending on whether the line has a hard newline
+			//    or a soft newline, append a "\r\n" or a " \n", as
+			//    per Synchronet's standard as of 3.15.
+			//  - Otherwise (Synchronet 3.14 and below):
+			//    Just append a "\r\n" to the line
+			if (system.version_num >= 31500)
+			{
+				var useHardNewline = false;
+				for (var i = 0; i < gEditLines.length; ++i)
+				{
+					// Use a hard newline if the current edit line has one or if this is
+					// the last line of the message.
+					useHardNewline = (gEditLines[i].hardNewlineEnd || (i == gEditLines.length-1));
+					msgContents += gEditLines[i].text + (useHardNewline ? "\r\n" : " \n");
+				}
+			}
+			else // Synchronet 3.14 and below
+			{
+				for (var i = 0; i < gEditLines.length; ++i)
+					msgContents += gEditLines[i].text + "\r\n";
+			}
+
+			// Read the user's signature, in case they have one
+			var msgSigInfo = readUserSigFile();
+			// If the user has not chosen to auto-sign messages, then also append their
+			// signature to the message now.
+			if (!gUserSettings.autoSignMessages)
+			{
+				// Append a blank line to separate the message & signature.
+				// Note: msgContents already has a newline at the end, so we don't have
+				// to append one here; just append the signature.
+				if (msgSigInfo.sigContents.length > 0)
+					msgContents += msgSigInfo.sigContents + "\r\n";
+			}
+		}
 
 		console.print("\1n");
 		console.crlf();
@@ -616,10 +535,10 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 		// cross-post logging).
 		if (postingInOriginalSubBoard && !postingOnlyInOriginalSubBoard)
 		{
-			log(LOG_INFO, EDITOR_PROGRAM_NAME + ": " + user.alias + " is posting a message in " + msg_area.sub[gMsgAreaNameInfo.subBoardCode].grp_name +
-			    " " + msg_area.sub[gMsgAreaNameInfo.subBoardCode].description + " (" + gMsgSubj + ")");
-			bbs.log_str(EDITOR_PROGRAM_NAME + ": " + user.alias + " is posting a message in " + msg_area.sub[gMsgAreaNameInfo.subBoardCode].grp_name +
-			            " " + msg_area.sub[gMsgAreaNameInfo.subBoardCode].description + " (" + gMsgSubj + ")");
+			log(LOG_INFO, "SlyEdit: " + user.alias + " is posting a message in " + msg_area.sub[gMsgAreaInfo.subBoardCode].grp_name +
+			    " " + msg_area.sub[gMsgAreaInfo.subBoardCode].description + " (" + gMsgSubj + ")");
+			bbs.log_str("SlyEdit: " + user.alias + " is posting a message in " + msg_area.sub[gMsgAreaInfo.subBoardCode].grp_name +
+			            " " + msg_area.sub[gMsgAreaInfo.subBoardCode].description + " (" + gMsgSubj + ")");
 		}
 		var postMsgErrStr = ""; // For storing errors related to saving the message
 		for (var grpIndex in gCrossPostMsgSubs)
@@ -628,12 +547,11 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 			if (gCrossPostMsgSubs.propIsFuncName(grpIndex))
 				continue;
 
-			// Output the sub-board that will be posted to
 			console.print("\1n" + gConfigSettings.genColors.msgPostedGrpHdr + msg_area.grp_list[grpIndex].description + ":");
 			console.crlf();
 			for (var subCode in gCrossPostMsgSubs[grpIndex])
 			{
-				if (subCode == gMsgAreaNameInfo.subBoardCode)
+				if (subCode == gMsgAreaInfo.subBoardCode)
 				{
 					printf("\1n  " + gConfigSettings.genColors.msgPostedSubBoardName + "%-48s", msg_area.sub[subCode].description.substr(0, 48));
 					console.print("\1n " + gConfigSettings.genColors.msgPostedOriginalAreaText + "(original message area)");
@@ -642,56 +560,23 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 				// to post in that sub, then post the message there.
 				else
 				{
+					// Write a log in the BBS log about which message area the user is
+					// cross-posting into.
+					log(LOG_INFO, "SlyEdit: " + user.alias + " is cross-posting a message in " + msg_area.sub[subCode].grp_name +
+					    " " + msg_area.sub[subCode].description + " (" + gMsgSubj + ")");
+					bbs.log_str("SlyEdit: " + user.alias + " is cross-posting a message in " + msg_area.sub[subCode].grp_name +
+					            " " + msg_area.sub[subCode].description + " (" + gMsgSubj + ")");
+
 					// Write the cross-posting message area on the user's screen.
 					printf("\1n  " + gConfigSettings.genColors.msgPostedSubBoardName + "%-73s", msg_area.sub[subCode].description.substr(0, 73));
 					if (user.compare_ars(msg_area.sub[subCode].post_ars))
 					{
-						// msgPostingObj will point to msgObj or a new one, depending on color settings
-						var msgPostingObj;
-						// If message text color is allowed & is allowed in this
-						// area, then check if ANSI conversion should be done.
-						if (gConfigSettings.allowColorSelection && colorSelectionAllowedInMsgArea(msg_area.sub[subCode].name, subCode))
-						{
-							if (shouldConvertMsgColorsToANSIInMsgArea(msg_area.sub[subCode].name, subCode))
-							{
-								msgPostingObj = {
-									msgContents: SyncAttrsToANSI(msgObj.msgContents)
-								};
-								msgSigInfo.sigContents = SyncAttrsToANSI(msgSigInfo.sigContents);
-							}
-							else // Post the message as-is
-								msgPostingObj = msgObj;
-						}
-						else // Color is not allowed - Strip any Synchronet attribute codes from the message
-						{
-							msgPostingObj = {
-								msgContents: concatMsgEditLines(crossPostEditLines, msgSigInfo, true)
-							};
-							// In case the user's signature has any return/newline
-							// characters in it, split it up and strip ctrl characters
-							// from each line, then put it back together.
-							var sigLines = msgSigInfo.sigContents.split("\r\n");
-							if ((sigLines.length > 0) && (sigLines[sigLines.length-1].length == 0))
-								sigLines.splice(-1, 1);
-							msgSigInfo.sigContents = "";
-							for (var sigIdx = 0; sigIdx < sigLines.length; ++sigIdx)
-								msgSigInfo.sigContents += strip_ctrl(sigLines[sigIdx]) + "\r\n";
-						}
-
-
-						// Write a log in the BBS log about which message area the user is
-						// cross-posting into.
-						log(LOG_INFO, EDITOR_PROGRAM_NAME + ": " + user.alias + " is cross-posting a message in " + msg_area.sub[subCode].grp_name +
-						    " " + msg_area.sub[subCode].description + " (" + gMsgSubj + ")");
-						bbs.log_str(EDITOR_PROGRAM_NAME + ": " + user.alias + " is cross-posting a message in " + msg_area.sub[subCode].grp_name +
-						            " " + msg_area.sub[subCode].description + " (" + gMsgSubj + ")");
-
 						// If the user's auto-sign setting is enabled, then auto-sign
 						// the message and append their signature afterward.  Otherwise,
 						// don't auto-sign, and their signature has already been appended.
 						if (gUserSettings.autoSignMessages)
 						{
-							var msgContents2 = msgPostingObj.msgContents + "\r\n";
+							var msgContents2 = msgContents + "\r\n";
 							msgContents2 += getSignName(subCode, gUserSettings.autoSignRealNameOnlyFirst, gUserSettings.autoSignEmailsRealName);
 							msgContents2 += "\r\n\r\n";
 							if (msgSigInfo.sigContents.length > 0)
@@ -699,7 +584,7 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 							postMsgErrStr = postMsgToSubBoard(subCode, gToName, gMsgSubj, msgContents2, user.number);
 						}
 						else
-							postMsgErrStr = postMsgToSubBoard(subCode, gToName, gMsgSubj, msgPostingObj.msgContents, user.number);
+							postMsgErrStr = postMsgToSubBoard(subCode, gToName, gMsgSubj, msgContents, user.number);
 						if (postMsgErrStr.length == 0)
 						{
 							savedTheMessage = true;
@@ -736,9 +621,9 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 	// want to save it will be determined by whether the user's current sub-board
 	// code is in the list of cross-post areas.
 	var saveMsgFile = true;
-	if (postingInMsgSubBoard(gMsgAreaName))
+	if (postingInMsgSubBoard(gMsgArea))
 	{
-		if (!gCrossPostMsgSubs.subCodeExists(gMsgAreaNameInfo.subBoardCode))
+		if (!gCrossPostMsgSubs.subCodeExists(gMsgAreaInfo.subBoardCode))
 		{
 			saveMsgFile = false;
 			// If the message was cross-posted to other message areas and not in the
@@ -775,7 +660,7 @@ if ((exitCode == 0) && (gEditLines.length > 0))
 			if (gUserSettings.autoSignMessages)
 			{
 				msgFile.writeln("");
-				var subCode = (postingInMsgSubBoard(gMsgAreaName) ? gMsgAreaNameInfo.subBoardCode : "mail");
+				var subCode = (postingInMsgSubBoard(gMsgArea) ? gMsgAreaInfo.subBoardCode : "mail");
 				msgFile.writeln(getSignName(subCode, gUserSettings.autoSignRealNameOnlyFirst, gUserSettings.autoSignEmailsRealName));
 			}
 			msgFile.close();
@@ -813,36 +698,36 @@ bbs.sys_status = gOldStatus;
 // Set the end-of-program status message.
 var endStatusMessage = "";
 if (exitCode == 1)
-	endStatusMessage = gConfigSettings.genColors.msgAbortedText + "Message aborted.";
+   endStatusMessage = gConfigSettings.genColors.msgAbortedText + "Message aborted.";
 else if (exitCode == 0)
 {
-	if (gEditLines.length > 0)
-	{
-		if (savedTheMessage)
-			endStatusMessage = gConfigSettings.genColors.msgHasBeenSavedText + "The message has been saved.";
-		else
-			endStatusMessage = gConfigSettings.genColors.msgAbortedText + "Message aborted.";
-	}
-	else
-		endStatusMessage = gConfigSettings.genColors.emptyMsgNotSentText + "Empty message not sent.";
+   if (gEditLines.length > 0)
+   {
+      if (savedTheMessage)
+         endStatusMessage = gConfigSettings.genColors.msgHasBeenSavedText + "The message has been saved.";
+      else
+         endStatusMessage = gConfigSettings.genColors.msgAbortedText + "Message aborted.";
+   }
+   else
+      endStatusMessage = gConfigSettings.genColors.emptyMsgNotSentText + "Empty message not sent.";
 }
 // We shouldn't hit this else case, but it's here just to be safe.
 else
-	endStatusMessage = gConfigSettings.genColors.genMsgErrorText + "Possible message error.";
+   endStatusMessage = gConfigSettings.genColors.genMsgErrorText + "Possible message error.";
 console.print(endStatusMessage);
 console.crlf();
 
 // If the user's setting to pause after every screenful is disabled, then
 // pause here so that they can see the exit information.
 if (user.settings & USER_PAUSE == 0)
-	mswait(1000);
+   mswait(1000);
 
 // Load any specified 3rd-party exit scripts and execute any provided exit
 // JavaScript commands.
 for (var i = 0; i < gConfigSettings.thirdPartyLoadOnExit.length; ++i)
-	load(gConfigSettings.thirdPartyLoadOnExit[i]);
+  load(gConfigSettings.thirdPartyLoadOnExit[i]);
 for (var i = 0; i < gConfigSettings.runJSOnExit.length; ++i)
-	eval(gConfigSettings.runJSOnExit[i]);
+  eval(gConfigSettings.runJSOnExit[i]);
 
 exit(exitCode);
 
@@ -869,9 +754,7 @@ function readQuoteOrMessageFile()
 				// Only use textLine if it's actually a string.
 				if (typeof(textLine) == "string")
 				{
-					// Remove any ANSI and/or Synchronet color codes from quote lines so
-					// the quote lines look better
-					textLine = strip_ctrl(cvtANSIToSyncAndRemoveUnwantedANSI(textLine));
+					textLine = strip_ctrl(textLine);
 					// If the line has only whitespace and/or > characters,
 					// then make the line blank before putting it into
 					// gQuoteLines.
@@ -1006,8 +889,12 @@ function doEditLoop()
 					returnCode = 1; // Aborted
 					continueOn = false;
 				}
-				else // Make sure the edit/quote color attribute is set
+				else
+				{
+					// Make sure the edit color attribute is set.
+					//console.print("\1n" + gTextAttrs);
 					console.print(chooseEditColor());
+				}
 				break;
 			case SAVE_KEY:
 				returnCode = 0; // Save
@@ -1015,7 +902,8 @@ function doEditLoop()
 				break;
 			case CMDLIST_HELP_KEY:
 			case CMDLIST_HELP_KEY_2:
-				displayCommandList(true, true, true, gCanCrossPost, gConfigSettings);
+				displayCommandList(true, true, true, gCanCrossPost, gConfigSettings.userIsSysop,
+				                   gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
 				clearEditAreaBuffer();
 				fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
 				               gInsertMode, gUseQuotes, gEditLinesIndex-(curpos.y-gEditTop),
@@ -1042,7 +930,6 @@ function doEditLoop()
 					var retObject = doQuoteSelection(curpos, currentWordLength);
 					curpos.x = retObject.x;
 					curpos.y = retObject.y;
-					gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 					currentWordLength = retObject.currentWordLength;
 					// If user input timed out, then abort.
 					if (retObject.timedOut)
@@ -1056,81 +943,19 @@ function doEditLoop()
 				}
 				break;
 			case CHANGE_COLOR_KEY:
-				// If the text index is 0 and the current line contains only
-				// Synchronet attribute codes, then change the text index to
-				// the end of the text line
-				if ((gTextLineIndex == 0) && gOnlySyncAttrsInStrRegex.test(gEditLines[gEditLinesIndex].text))
-					gTextLineIndex = gEditLines[gEditLinesIndex].text.length;
-				// Let the user change the text color if SlyEdit is configured
-				// to do so, color selection is allowed in the current message
-				// area, and the current line is not a quote line.  Also, for
-				// now, only allow color selection if the text line index is
-				// at the end of the text line.
-				var colorsAllowedObj = colorSelectionAllowedInSettingsAndLine(gMsgAreaName, bbs.cursub_code);
-				if (colorsAllowedObj.colorsAllowed)
+				// Let the user change the text color.
+				/*if (gConfigSettings.allowColorSelection)
 				{
-					var retObj = doColorSelection(curpos, currentWordLength);
+					var retObj = doColorSelection(gTextAttrs, curpos, currentWordLength);
 					if (!retObj.timedOut)
 					{
-						// Only do this if the user actually chose a text attribute and
-						// it's different from the current text attribute
-						if ((retObj.txtAttrs.length > 0) && (retObj.txtAttrs != gTextAttrs))
-						{
-							var oldTextAttrs = gTextAttrs;
-							gTextAttrs = retObj.txtAttrs;
-							// If the text line currently contains just a normal or normal-white
-							// attribute code, then remove it before appending the new attribute code.
-							if ((gTextLineIndex == 0) && (gEditLines[gEditLinesIndex].displayLength() == 0))
-							{
-								var txtLineLower = gEditLines[gEditLinesIndex].text.toLowerCase();
-								if (txtLineLower == "\1n\1w")
-									gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(4);
-								else if (txtLineLower == "\1n")
-									gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(2);
-								// If the line starts with the old attribute, then remove it.
-								if (gEditLines[gEditLinesIndex].text.indexOf(oldTextAttrs) == 0)
-									gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(oldTextAttrs.length);
-							}
-							// Add the attribute code(s) to the text line
-							if (gTextLineIndex < gEditLines[gEditLinesIndex].text.length)
-								gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex) + gTextAttrs + gEditLines[gEditLinesIndex].text.substr(gTextLineIndex);
-							else
-								gEditLines[gEditLinesIndex].text += gTextAttrs;
-							gTextLineIndex += gTextAttrs.length;
-							curpos.x = retObj.x;
-							curpos.y = retObj.y;
-							// If the text line is now too long, fix off-by-one issue with
-							// gTextLineIndex for when the line is wrapped
-							if (gEditLines[gEditLinesIndex].text.length > gEditWidth-1)
-								++gTextLineIndex;
-							currentWordLength = retObj.currentWordLength;
-							// Output the correct text color
-							if ((gTextAttrs != "\1N") && (gTextAttrs != "\1n"))
-								console.print("\1n" + oldTextAttrs + gTextAttrs);
-							else
-								console.print(gTextAttrs);
-							// Refresh the rest of the text on the screen so that the colors are correct
-							console.print(gEditLines[gEditLinesIndex].text.substr(gTextLineIndex));
-							var screenY = curpos.y + 1;
-							for (var lineIdx = gEditLinesIndex+1; (lineIdx < gEditLines.length) && (screenY <= gEditBottom); ++lineIdx)
-							{
-								console.gotoxy(1, screenY++);
-								if (gEditLines[lineIdx].isQuoteLine)
-									console.print("\1n" + gQuoteLineColor + strip_ctrl(gEditLines[lineIdx].text));
-								else
-									console.print(gEditLines[lineIdx].text);
-							}
-
-							// Put the cursor back where it should be, and output
-							// the correct text color
-							console.gotoxy(curpos);
-							if ((gTextAttrs != "\1N") && (gTextAttrs != "\1n"))
-								console.print("\1n" + oldTextAttrs + gTextAttrs);
-							else
-								console.print(gTextAttrs);
-						}
-						else
-							console.print(gTextAttrs);
+						// Note: DoColorSelection() will prefix the color with the normal
+						// attribute.
+						gTextAttrs = retObj.txtAttrs;
+						console.print(gTextAttrs);
+						curpos.x = retObj.x;
+						curpos.y = retObj.y;
+						currentWordLength = retObj.currentWordLength;
 					}
 					else
 					{
@@ -1141,16 +966,7 @@ function doEditLoop()
 						console.print("\1n\1h\1r" + EDITOR_PROGRAM_NAME + ": Input timeout reached.");
 						continue;
 					}
-				}
-				else
-				{
-					writeWithPause(1, console.screen_rows, "\1y\1h" + colorsAllowedObj.errorMsg + "\1n", ERRORMSG_PAUSE_MS);
-					// Refresh the help line on the bottom of the screen
-					fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
-					// Make sure the edit color is correct, and put the cursor where it should be
-					console.print(chooseEditColor());
-					console.gotoxy(curpos);
-				}
+				}*/
 				break;
 			case KEY_UP:
 				// Move the cursor up one line.
@@ -1158,12 +974,15 @@ function doEditLoop()
 				{
 					--gEditLinesIndex;
 
-					// gTextLineIndex should contain the index in the text
-					// line where the cursor would add text.  Figure it out
-					// based on the cursor's horizontal position.
-					var textLineDisplayableLen = gEditLines[gEditLinesIndex].displayLength();
-					if (curpos.x > gEditLeft + textLineDisplayableLen)
-						curpos.x = gEditLeft + textLineDisplayableLen;
+					// gTextLineIndex should containg the index in the text
+					// line where the cursor would add text.  If the previous
+					// line is shorter than the one we just left, then
+					// gTextLineIndex and curpos.x need to be adjusted.
+					if (gTextLineIndex > gEditLines[gEditLinesIndex].length())
+					{
+						gTextLineIndex = gEditLines[gEditLinesIndex].length();
+						curpos.x = gEditLeft + gEditLines[gEditLinesIndex].length();
+					}
 					// Figure out the vertical coordinate of where the
 					// cursor should be.
 					// If the cursor is at the top of the edit area,
@@ -1174,7 +993,6 @@ function doEditLoop()
 						--curpos.y;
 
 					console.gotoxy(curpos);
-					gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 					currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 					console.print(chooseEditColor()); // Make sure the edit color is correct
 				}
@@ -1184,24 +1002,28 @@ function doEditLoop()
 				if (gEditLinesIndex < gEditLines.length-1)
 				{
 					++gEditLinesIndex;
-
-					// gTextLineIndex should contain the index in the text
-					// line where the cursor would add text.  Figure it out
-					// based on the cursor's horizontal position.
-					var textLineDisplayableLen = gEditLines[gEditLinesIndex].displayLength();
-					if (curpos.x > gEditLeft + textLineDisplayableLen)
-						curpos.x = gEditLeft + textLineDisplayableLen;
+					// gTextLineIndex should containg the index in the text
+					// line where the cursor would add text.  If the next
+					// line is shorter than the one we just left, then
+					// gTextLineIndex and curpos.x need to be adjusted.
+					if (gTextLineIndex > gEditLines[gEditLinesIndex].length())
+					{
+						gTextLineIndex = gEditLines[gEditLinesIndex].length();
+						curpos.x = gEditLeft + gEditLines[gEditLinesIndex].length();
+					}
 					// Figure out the vertical coordinate of where the
 					// cursor should be.
 					// If the cursor is at the bottom of the edit area,
 					// then scroll down through the message by 1 line.
 					if (curpos.y == gEditBottom)
-						displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop), gEditBottom, true, /*true*/false);
+					{
+						displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
+						gEditBottom, true, /*true*/false);
+					}
 					else
 						++curpos.y;
 
 					console.gotoxy(curpos);
-					gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 					currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 					console.print(chooseEditColor()); // Make sure the edit color is correct
 				}
@@ -1214,31 +1036,7 @@ function doEditLoop()
 					--curpos.x;
 					console.gotoxy(curpos);
 					if (gTextLineIndex > 0)
-					{
-						// Account for Synchronet color/attribute codes that might
-						// be before the current index, if this isn't a quote line
-						if (!gEditLines[gEditLinesIndex].isQuoteLine)
-						{
-							var previousSyncColorCodeIdx = regexLastIndexOf(gEditLines[gEditLinesIndex].text, gSyncAttrRegex, 0, gTextLineIndex);
-							while (previousSyncColorCodeIdx == gTextLineIndex-2)
-							{
-								gTextLineIndex -= 2;
-								previousSyncColorCodeIdx = regexLastIndexOf(gEditLines[gEditLinesIndex].text, gSyncAttrRegex, 0, gTextLineIndex);
-							}
-						}
-						if (!gEditLines[gEditLinesIndex].isQuoteLine)
-						{
-							// See if there are any Synchronet color/attribute codes
-							// before the current character in the text line, and if
-							// so, set the text color appropriately (and if there are
-							// no colors, then set the color to normal).
-							var attrsStr = gEditLines[gEditLinesIndex].getLastAttrCodes(gTextLineIndex);
-							if (attrsStr.length == 0)
-								gTextAttrs = "\1n";
-							else
-								gTextAttrs = attrsStr;
-						}
-					}
+						--gTextLineIndex;
 				}
 				else
 				{
@@ -1249,19 +1047,21 @@ function doEditLoop()
 					if (gEditLinesIndex > 0)
 					{
 						--gEditLinesIndex;
-						curpos.x = gEditLeft + gEditLines[gEditLinesIndex].displayLength();
+						curpos.x = gEditLeft + gEditLines[gEditLinesIndex].length();
 						// Move the cursor up or scroll up by one line
 						if (curpos.y > 1)
 							--curpos.y;
 						else
 							displayEditLines(gEditTop, gEditLinesIndex, gEditBottom, true, /*true*/false);
+						gTextLineIndex = gEditLines[gEditLinesIndex].length();
 						console.gotoxy(curpos);
 					}
 				}
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
+				console.print(chooseEditColor());
+
+				// Update the current word length.
 				currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
-				// Output the text color, depending on if this line is a quote line or
-				// an edit line
+				// Make sure the edit color is correct
 				console.print(chooseEditColor());
 				break;
 			case KEY_RIGHT:
@@ -1272,37 +1072,11 @@ function doEditLoop()
 				{
 					// The current line index must be within bounds
 					// before we can move the cursor to the right.
-					if (gTextLineIndex < gEditLines[gEditLinesIndex].text.length)
+					if (gTextLineIndex < gEditLines[gEditLinesIndex].length())
 					{
 						++curpos.x;
-						// Account for Synchronet color codes to the right of gTextLineIndex,
-						// if this isn't a quote line
-						if (!gEditLines[gEditLinesIndex].isQuoteLine)
-						{
-							/*
-							var nextSyncColorCodeIdx = strSearchNext(gEditLines[gEditLinesIndex].text, gSyncAttrRegex, gTextLineIndex);
-							while (nextSyncColorCodeIdx == gTextLineIndex+1)
-							{
-								gTextLineIndex += 2;
-								nextSyncColorCodeIdx = strSearchNext(gEditLines[gEditLinesIndex].text, gSyncAttrRegex, gTextLineIndex);
-							}
-							*/
-						}
-						if (!gEditLines[gEditLinesIndex].isQuoteLine)
-						{
-							// See if there are any Synchronet color/attribute codes
-							// before the current character in the text line, and if
-							// so, set the text color appropriately (and if there are
-							// no colors, then set the color to normal).
-							var attrsStr = gEditLines[gEditLinesIndex].getLastAttrCodes(gTextLineIndex);
-							if (attrsStr.length == 0)
-								gTextAttrs = "\1n";
-							else
-								gTextAttrs = attrsStr;
-						}
-						// Output the text color, depending on if this line is a quote line or
-						// an edit line
-						console.print(chooseEditColor());
+						console.gotoxy(curpos);
+						++gTextLineIndex;
 					}
 					else
 					{
@@ -1320,6 +1094,8 @@ function doEditLoop()
 							else
 								displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
 							gEditBottom, true, /*true*/false);
+							gTextLineIndex = 0;
+							console.gotoxy(curpos);
 						}
 					}
 				}
@@ -1338,34 +1114,31 @@ function doEditLoop()
 							++curpos.y;
 						else
 							displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop), gEditBottom, true, false);
+						gTextLineIndex = 0;
+						console.gotoxy(curpos);
 					}
 				}
-				console.gotoxy(curpos);
 				console.print(chooseEditColor());
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
+
+				// Update the current word length.
 				currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 				// Make sure the edit color is correct
 				console.print(chooseEditColor());
 				break;
 			case KEY_HOME:
 				// Go to the beginning of the line
+				gTextLineIndex = 0;
 				curpos.x = gEditLeft;
 				console.gotoxy(curpos);
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
+				// Update the current word length.
 				currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
-				// Update the text color for the cursor on the screen
-				gTextAttrs = "\1n";
-				console.print(chooseEditColor());
 				break;
 			case KEY_END:
 				// Go to the end of the line
 				if (gEditLinesIndex < gEditLines.length)
 				{
-					// Setting the text line index with the actual text string
-					// length and the cursor X position with the length() function
-					// because the string might have color/attribute codes, so the
-					// text string would be longer than it appears on the screen.
-					curpos.x = gEditLeft + gEditLines[gEditLinesIndex].displayLength();
+					gTextLineIndex = gEditLines[gEditLinesIndex].length();
+					curpos.x = gEditLeft + gTextLineIndex;
 					// If the cursor position would be to the right of the edit
 					// area, then place it at gEditRight.
 					if (curpos.x > gEditRight)
@@ -1377,24 +1150,8 @@ function doEditLoop()
 					// Place the cursor where it should be.
 					console.gotoxy(curpos);
 
-					gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
+					// Update the current word length.
 					currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
-
-					if (!gEditLines[gEditLinesIndex].isQuoteLine)
-					{
-						// See if there are any Synchronet color/attribute codes
-						// before the current character in the text line, and if
-						// so, set the text color appropriately (and if there are
-						// no colors, then set the color to normal).
-						var attrsStr = gEditLines[gEditLinesIndex].getLastAttrCodes(gTextLineIndex);
-						if (attrsStr.length == 0)
-							gTextAttrs = "\1n";
-						else
-							gTextAttrs = attrsStr;
-					}
-					// Output the text color, depending on if this line is a quote line or
-					// an edit line
-					console.print(chooseEditColor());
 				}
 				break;
 			case BACKSPACE:
@@ -1404,7 +1161,6 @@ function doEditLoop()
 					var retObject = doBackspace(curpos, currentWordLength);
 					curpos.x = retObject.x;
 					curpos.y = retObject.y;
-					//gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 					currentWordLength = retObject.currentWordLength;
 					// Make sure the edit color is correct
 					console.print(chooseEditColor());
@@ -1417,28 +1173,26 @@ function doEditLoop()
 					var retObject = doDeleteKey(curpos, currentWordLength);
 					curpos.x = retObject.x;
 					curpos.y = retObject.y;
-					gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 					currentWordLength = retObject.currentWordLength;
 					// Make sure the edit color is correct
 					console.print(chooseEditColor());
 				}
 				break;
 			case KEY_ENTER:
-				var cursorAtBeginningOrEnd = ((curpos.x == 1) || (curpos.x >= gEditLines[gEditLinesIndex].displayLength()));
+				var cursorAtBeginningOrEnd = ((curpos.x == 1) || (curpos.x >= gEditLines[gEditLinesIndex].length()));
 				var letUserEditLine = (cursorAtBeginningOrEnd ? true : textLineIsEditable(gEditLinesIndex));
 				if (letUserEditLine)
 				{
 					var retObject = doEnterKey(curpos, currentWordLength);
+					curpos.x = retObject.x;
+					curpos.y = retObject.y;
+					currentWordLength = retObject.currentWordLength;
 					returnCode = retObject.returnCode;
 					continueOn = retObject.continueOn;
 					// Check for whether we should do quote selection or
 					// show the help screen (if the user entered /Q or /?)
 					if (continueOn)
 					{
-						curpos.x = retObject.x;
-						curpos.y = retObject.y;
-						gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
-						currentWordLength = retObject.currentWordLength;
 						if (retObject.doQuoteSelection)
 						{
 							if (gUseQuotes)
@@ -1446,7 +1200,6 @@ function doEditLoop()
 								retObject = doQuoteSelection(curpos, currentWordLength);
 								curpos.x = retObject.x;
 								curpos.y = retObject.y;
-								gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 								currentWordLength = retObject.currentWordLength;
 								// If user input timed out, then abort.
 								if (retObject.timedOut)
@@ -1462,11 +1215,12 @@ function doEditLoop()
 						else if (retObject.showHelp)
 						{
 							displayProgramInfo(true, false);
-							displayCommandList(false, false, true, gCanCrossPost, gConfigSettings);
+							displayCommandList(false, false, true, gCanCrossPost, gConfigSettings.userIsSysop,
+							                   gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
 							clearEditAreaBuffer();
 							fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
 							               gInsertMode, gUseQuotes, gEditLinesIndex-(curpos.y-gEditTop),
-							               displayEditLines);
+							displayEditLines);
 							console.gotoxy(curpos);
 						}
 						else if (retObject.doCrossPostSelection)
@@ -1474,15 +1228,16 @@ function doEditLoop()
 							if (gCanCrossPost)
 								doCrossPosting();
 						}
-						// Make sure the edit color is correct
-						console.print(chooseEditColor());
 					}
+					// Make sure the edit color is correct
+					console.print(chooseEditColor());
 				}
 				break;
 				// Insert/overwrite mode toggle
 			case KEY_INSERT:
 			case TOGGLE_INSERT_KEY:
 				toggleInsertMode(null);
+				//console.print("\1n" + gTextAttrs);
 				console.print(chooseEditColor());
 				console.gotoxy(curpos);
 				break;
@@ -1493,12 +1248,12 @@ function doEditLoop()
 				continueOn = retObj.continueOn;
 				curpos.x = retObj.x;
 				curpos.y = retObj.y;
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 				currentWordLength = retObj.currentWordLength;
 				// If we can continue on, put the cursor back
 				// where it should be.
 				if (continueOn)
 				{
+					//console.print("\1n" + gTextAttrs);
 					console.print(chooseEditColor());
 					console.gotoxy(curpos);
 				}
@@ -1507,7 +1262,6 @@ function doEditLoop()
 				var retObj = findText(curpos);
 				curpos.x = retObj.x;
 				curpos.y = retObj.y;
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 				console.print(chooseEditColor()); // Make sure the edit color is correct
 				break;
 			case IMPORT_FILE_KEY:
@@ -1517,7 +1271,6 @@ function doEditLoop()
 					var retObj = importFile(gConfigSettings.userIsSysop, curpos);
 					curpos.x = retObj.x;
 					curpos.y = retObj.y;
-					gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 					currentWordLength = retObj.currentWordLength;
 					console.print(chooseEditColor()); // Make sure the edit color is correct
 				}
@@ -1534,7 +1287,6 @@ function doEditLoop()
 				var retObj = doDeleteLine(curpos);
 				curpos.x = retObj.x;
 				curpos.y = retObj.y;
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 				currentWordLength = retObj.currentWordLength;
 				console.print(chooseEditColor()); // Make sure the edit color is correct
 				break;
@@ -1557,10 +1309,15 @@ function doEditLoop()
 					displayEditLines(gEditTop, topEditIndex, gEditBottom, true, /*true*/false);
 					// Set the cursor to the last place on the last line.
 					gEditLinesIndex = topEditIndex + gEditHeight - 1;
-					var textLineDisplayableLen = gEditLines[gEditLinesIndex].displayLength();
-					if (curpos.x > gEditLeft + textLineDisplayableLen)
-						curpos.x = gEditLeft + textLineDisplayableLen;
+					gTextLineIndex = gEditLines[gEditLinesIndex].length();
+					if ((gTextLineIndex > 0) && (gEditLines[gEditLinesIndex].length == gEditWidth))
+						--gTextLineIndex;
+					curpos.x = gEditLeft + gTextLineIndex;
 					curpos.y = gEditBottom;
+					console.gotoxy(curpos);
+
+					// Update the current word length.
+					currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 				}
 				else
 				{
@@ -1572,11 +1329,12 @@ function doEditLoop()
 						gTextLineIndex = 0;
 						curpos.x = gEditLeft;
 						curpos.y = gEditTop;
+						console.gotoxy(curpos);
+
+						// Update the current word length.
+						currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 					}
 				}
-				console.gotoxy(curpos);
-				currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
 				console.print(chooseEditColor()); // Make sure the edit color is correct
 				break;
 			case KEY_PAGE_DOWN: // Move 1 page down in the message
@@ -1603,8 +1361,13 @@ function doEditLoop()
 					displayEditLines(gEditTop, topEditIndex, gEditBottom, true, /*true*/false);
 					// Set the cursor to the first place on the first line.
 					gEditLinesIndex = topEditIndex;
+					gTextLineIndex = 0;
 					curpos.x = gEditLeft;
 					curpos.y = gEditTop;
+					console.gotoxy(curpos);
+
+					// Update the current word length.
+					currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 				}
 				else
 				{
@@ -1622,15 +1385,17 @@ function doEditLoop()
 							gEditLinesIndex = bottomEditIndex;
 						else
 							gEditLinesIndex = lastEditLineIndex;
-						var textLineDisplayableLen = gEditLines[gEditLinesIndex].displayLength();
-						if (curpos.x > gEditLeft + textLineDisplayableLen)
-							curpos.x = gEditLeft + textLineDisplayableLen;
+						gTextLineIndex = gEditLines[gEditLinesIndex].length();
+						if ((gTextLineIndex > 0) && (gEditLines[gEditLinesIndex].length == gEditWidth))
+							--gTextLineIndex;
+						curpos.x = gEditLeft + gTextLineIndex;
 						curpos.y += (gEditLinesIndex-oldEditLinesIndex);
+						console.gotoxy(curpos);
+
+						// Update the current word length.
+						currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 					}
 				}
-				console.gotoxy(curpos);
-				gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(curpos.x-gEditLeft);
-				currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 				console.print(chooseEditColor()); // Make sure the edit color is correct
 				break;
 			case CROSSPOST_KEY:
@@ -1645,15 +1410,10 @@ function doEditLoop()
 				doUserSettings(curpos, true);
 				break;
 			default:
-				// If the text line is editable, let the typed character
-				// be added to the line
+				// For the tab character, insert 3 spaces.  Otherwise,
+				// if it's a printable character, add the character.
 				if (textLineIsEditable(gEditLinesIndex))
 				{
-					// For the tab character, insert 3 spaces.  Otherwise,
-					// if it's a printable character, add the character.
-					// Note: doPrintableChar() updates gEditLinesIndex
-					// and gTextLineIndex.
-					// TODO: This isn't handling tab anymore
 					if (/\t/.test(userInput))
 					{
 						var retObject;
@@ -1736,22 +1496,12 @@ function doEditLoop()
 		}
 	}
 
-	// If gEditLines has only 1 line in it and it's blank or only has Synchronet
-	// attribute codes, then remove it so that we can test to see if the message
-	// is empty.
+	// If gEditLines has only 1 line in it and it's blank, then
+	// remove it so that we can test to see if the message is empty.
 	if (gEditLines.length == 1)
 	{
-		if (gEditLines[0].displayLength() == 0)
+		if (gEditLines[0].length() == 0)
 			gEditLines.splice(0, 1);
-		else
-		{
-			var syncAttrRegex = /\1[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i;
-			var allSyncAttrCodes = true;
-			for (var i = 0; (i < gEditLines[0].text.length) && allSyncAttrCodes; i += 2)
-				allSyncAttrCodes = syncAttrRegex.test(gEditLines[0].text.substr(i, 2));
-			if (allSyncAttrCodes)
-				gEditLines.splice(0, 1);
-		}
 	}
 
 	return returnCode;
@@ -1767,254 +1517,203 @@ function doEditLoop()
 //               position and currentLength, the current word length.
 function doBackspace(pCurpos, pCurrentWordLength)
 {
-	// Create the return object.
-	var retObj = new Object();
-	retObj.x = pCurpos.x;
-	retObj.y = pCurpos.y;
-	retObj.currentWordLength = pCurrentWordLength;
+   // Create the return object.
+   var retObj = new Object();
+   retObj.x = pCurpos.x;
+   retObj.y = pCurpos.y;
+   retObj.currentWordLength = pCurrentWordLength;
+
+   var didBackspace = false;
+   // For later, store a backup of the current edit line index and
+   // cursor position.
+   var originalCurrentLineIndex = gEditLinesIndex;
+   var originalX = pCurpos.x;
+   var originalY = pCurpos.y;
+   var originallyOnLastLine = (gEditLinesIndex == gEditLines.length-1);
+
+   // If the cursor is beyond the leftmost position in
+   // the edit area, then we can simply remove the last
+   // character in the current line and move the cursor
+   // over to the left.
+   if (retObj.x > gEditLeft)
+   {
+      if (gTextLineIndex > 0)
+      {
+         console.print(BACKSPACE);
+         console.print(" ");
+         --retObj.x;
+         console.gotoxy(retObj.x, retObj.y);
+
+         // Remove the previous character from the text line
+         var textLineLength = gEditLines[gEditLinesIndex].length();
+         if (textLineLength > 0)
+         {
+            var textLine = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex-1)
+                         + gEditLines[gEditLinesIndex].text.substr(gTextLineIndex);
+            gEditLines[gEditLinesIndex].text = textLine;
+            didBackspace = true;
+            --gTextLineIndex;
+         }
+      }
+   }
+   else
+   {
+      // The cursor is at the leftmost position in the edit area.
+      // If we are beyond the first text line, then move as much of
+      // the current text line as possible up to the previous line,
+      // if there's room (if not, don't do anything).
+      if (gEditLinesIndex > 0)
+      {
+         var prevLineIndex = gEditLinesIndex - 1;
+         if (gEditLines[gEditLinesIndex].length() > 0)
+         {
+            // Store the previous line's original length
+            var originalPrevLineLen = gEditLines[prevLineIndex].length();
 
-	var didBackspace = false;
-	// For later, store a backup of the current edit line index and
-	// cursor position.
-	var originalCurrentLineIndex = gEditLinesIndex;
-	var originalX = pCurpos.x;
-	var originalY = pCurpos.y;
-	var originallyOnLastLine = (gEditLinesIndex == gEditLines.length-1);
-
-	// If the cursor is beyond the leftmost position in
-	// the edit area, then we can simply remove the last
-	// character in the current line and move the cursor
-	// over to the left.
-	if (retObj.x > gEditLeft)
-	{
-		//displayDebugText(1, 1, "Here 1!!   \1;", console.getxy(), false, false, true); // Temporary
-		if (gTextLineIndex > 0)
-		{
-			//displayDebugText(1, 1, "Here 2!!   \1;", console.getxy(), false, false, true); // Temporary
-			console.print(BACKSPACE);
-			console.print("\1n " + gTextAttrs);
-			--retObj.x;
-			console.gotoxy(retObj.x, retObj.y);
+            // See how much space is at the end of the previous line
+            var previousLineEndSpace = gEditWidth - gEditLines[prevLineIndex].length();
+            if (previousLineEndSpace > 0)
+            {
+               var index = previousLineEndSpace - 1;
+               // If that index is valid for the current line, then find the first
+               // space in the current line so that the text would fit at the end
+               // of the previous line.  Otherwise, set index to the length of the
+               // current line so that we'll move the whole current line up to the
+               // previous line.
+               if (index < gEditLines[gEditLinesIndex].length())
+               {
+                  for (; index >= 0; --index)
+                  {
+                     if (gEditLines[gEditLinesIndex].text.charAt(index) == " ")
+                        break;
+                  }
+               }
+               else
+                  index = gEditLines[gEditLinesIndex].length();
+               // If we found a space, then move the part of the current line before
+               // the space to the end of the previous line.
+               if (index > 0)
+               {
+                  var linePart = gEditLines[gEditLinesIndex].text.substr(0, index);
+                  gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(index);
+                  gEditLines[prevLineIndex].text += linePart;
+                  gEditLines[prevLineIndex].hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
+
+                  // If the current line is now blank, then remove it from gEditLines.
+                  if (gEditLines[gEditLinesIndex].length() == 0)
+                     gEditLines.splice(gEditLinesIndex, 1);
+
+                  // Update the global edit variables so that the cursor is placed
+                  // on the previous line.
+                  --gEditLinesIndex;
+                  // Search for linePart in the line - If found, the cursor should
+                  // be placed where it starts.  If it' snot found, place the cursor
+                  // at the end of the line.
+                  var linePartIndex = gEditLines[gEditLinesIndex].text.indexOf(linePart);
+                  if (linePartIndex > -1)
+                     gTextLineIndex = linePartIndex;
+                  else
+                     gTextLineIndex = gEditLines[gEditLinesIndex].length();
 
-			// Remove the previous character from the text line
-			var textLineVisibleLength = gEditLines[gEditLinesIndex].displayLength();
-			if (textLineVisibleLength > 0)
-			{
-				// Account for any Synchronet color/attribute codes that might be in
-				// the text line immediately before the current index.  If there are
-				// any, then remove those as well as the character before them.  If
-				// there aren't any Synchronet attribute codes immediately before the
-				// current index, then just the previous character will be removed.
-				var attrInfo = gEditLines[gEditLinesIndex].getAttrsAndIndexesBeforeIdx(gTextLineIndex);
-				var substrLen = gTextLineIndex - 1;
-				var textLineIndexDecrementAmount = 1;
-				if (attrInfo.syncAttrEndIdx == gTextLineIndex-1)
-				{
-					substrLen = attrInfo.syncAttrStartIdx - 1; // We want to remove the attribute codes and the character before it
-					textLineIndexDecrementAmount = attrInfo.attrStr.length + 1;
-					if (substrLen < 0) // In case the attribute codes were at the beginning of the line
-					{
-						substrLen = 0;
-						--textLineIndexDecrementAmount;
-					}
-				}
-				// Now actually get the substrings to remove the character(s)
-				var substr1 = gEditLines[gEditLinesIndex].text.substr(0, substrLen);
-				//var substrObj2 = gEditLines[gEditLinesIndex].substrWithSyncColorCodes(substrObj1.endIdx+2); // TODO: Regular substring?
-				var substr2 = gEditLines[gEditLinesIndex].text.substr(gTextLineIndex);
-				// If the 'after' part of the string is just the attributes
-				if (gOnlySyncAttrsInStrRegex.test(substr2))
-					substr2 = "";
-				//if (substrObj2.strSub == attrInfo.attrStr)
-				//	substrObj2.clear();
-				gEditLines[gEditLinesIndex].text = substr1 + substr2;
-				gTextLineIndex -= textLineIndexDecrementAmount;
-				// Set the 'after' part to a blank string, and set the text attribute
-				var newTextAttrs = gEditLines[gEditLinesIndex].getLastAttrCodes(gTextLineIndex);
-				if (newTextAttrs.length > 0)
-					gTextAttrs = newTextAttrs;
-				else
-					gTextAttrs = "\1n";
-				console.print(chooseEditColor()); // Set the cursor color to edit line or quote line color
-				didBackspace = true;
-			}
-		}
-	}
-	else
-	{
-		// The cursor is at the leftmost position in the edit area.
-		// If we are beyond the first text line, then move as much of
-		// the current text line as possible up to the previous line,
-		// if there's room (if not, don't do anything).
-		if (gEditLinesIndex > 0)
-		{
-			//displayDebugText(1, 1, "Here 3!!   \1;", console.getxy(), false, false, true); // Temporary
-			var prevLineIndex = gEditLinesIndex - 1;
-			if (gEditLines[gEditLinesIndex].displayLength() > 0)
-			{
-				//displayDebugText(1, 1, "Here 4!!   \1;", console.getxy(), false, false, true); // Temporary
-				// Store the previous line's original visible length
-				var originalPrevLineLen = gEditLines[prevLineIndex].displayLength();
+                  retObj.x = gEditLeft + gTextLineIndex;
+                  if (retObj.y > gEditTop)
+                     --retObj.y;
 
-				// See how much space is at the end of the previous line
-				var prevLineNumCharsAvailable = gEditWidth - gEditLines[prevLineIndex].displayLength();
-				if (prevLineNumCharsAvailable > 0)
-				{
-					//displayDebugText(1, 1, "Here 5!!   \1;", console.getxy(), false, false, true); // Temporary
-					var index = prevLineNumCharsAvailable - 1;
-					// If that index is valid for the current line, then find the first
-					// space in the current line so that the text would fit at the end
-					// of the previous line.  Otherwise, set index to the length of the
-					// current line so that we'll move the whole current line up to the
-					// previous line.
-					if (index < gEditLines[gEditLinesIndex].text.length)
-					{
-						for (; index >= 0; --index)
-						{
-							if (gEditLines[gEditLinesIndex].text.charAt(index) == " ")
-								break;
-						}
-					}
-					else
-						index = gEditLines[gEditLinesIndex].text.length;
-					// If we found a space, then move the part of the current line before
-					// the space to the end of the previous line.
-					if (index > 0)
-					{
-						//displayDebugText(1, 1, "Here 6!!   \1;", console.getxy(), false, false, true); // Temporary
-						var linePart = gEditLines[gEditLinesIndex].text.substr(0, index);
-						gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(index+1);
-						// Save the text line index for the previous line where
-						// the new part will start - We'll set gTextLineIndex to
-						// this later.
-						var newTextLineIndex = gEditLines[prevLineIndex].text.length;
-						// Append the part of the current line to the previous line
-						gEditLines[prevLineIndex].text += linePart;
-						gEditLines[prevLineIndex].hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
-
-						// If the current line is now blank, then remove it from gEditLines.
-						if (gEditLines[gEditLinesIndex].displayLength() == 0)
-							gEditLines.splice(gEditLinesIndex, 1);
-
-						// Update the global edit variables so that the cursor is placed
-						// on the previous line.
-						--gEditLinesIndex;
-						// Update the text line index to the correct position in the text line
-						gTextLineIndex = newTextLineIndex;
+                  didBackspace = true;
+               }
+            }
+         }
+         else
+         {
+            // The current line's length is 0.
+            // If there's enough room on the previous line, remove the
+            // current line and place the cursor at the end of the
+            // previous line.
+            if (gEditLines[prevLineIndex].length() <= gEditWidth-1)
+            {
+               // Copy the current line's "hard newline end" setting to the
+               // previous line (so that if there's a blank line below the
+               // current line, the blank line will be preserved), then remove
+               // the current edit line.
+               gEditLines[gEditLinesIndex-1].hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
+               gEditLines.splice(gEditLinesIndex, 1);
+
+               --gEditLinesIndex;
+               gTextLineIndex = gEditLines[prevLineIndex].length();
+               retObj.x = gEditLeft + gEditLines[prevLineIndex].length();
+               if (retObj.y > gEditTop)
+                  --retObj.y;
+
+               didBackspace = true;
+            }
+         }
+      }
+   }
 
-						// The horizontal cursor position should be placed right
-						// after the original first line's text, where the 2nd line's
-						// text starts.
-						retObj.x = gEditLeft + strip_ctrl(gEditLines[gEditLinesIndex].text.substr(0, newTextLineIndex)).length;
-						if (retObj.y > gEditTop)
-							--retObj.y;
+   // If the backspace was performed, then re-adjust the text lines
+   // and refresh the screen.
+   if (didBackspace)
+   {
+      // Store the previous line of text now so we can compare it later
+      var prevTextline = "";
+      if (gEditLinesIndex > 0)
+         prevTextline = gEditLines[gEditLinesIndex-1].text;
+
+      // Re-adjust the text lines
+      reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
+
+      // If the previous line's length increased, that probably means that the
+      // user backspaced to the beginning of the current line and the word was
+      // moved to the end of the previous line.  If so, then move the cursor to
+      // the end of the previous line.
+      //var scrolled = false;
+      if ((gEditLinesIndex > 0) &&
+          (gEditLines[gEditLinesIndex-1].length() > prevTextline.length))
+      {
+         // Update the text index variables and cusor position variables.
+         --gEditLinesIndex;
+         gTextLineIndex = gEditLines[gEditLinesIndex].length();
+         retObj.x = gEditLeft + gTextLineIndex;
+         if (retObj.y > gEditTop)
+            --retObj.y;
+      }
 
-						didBackspace = true;
-					}
-				}
-			}
-			else
-			{
-				//displayDebugText(1, 1, "Here 7!!   \1;", console.getxy(), false, false, true); // Temporary
-				// The current line's displayable length is 0.
-				// Save the current line's text, to preserve color/attribute
-				// codes that might be there
-				var currentLineAttrCodes = gEditLines[gEditLinesIndex].text;
-				// If there's enough room on the previous line, remove the
-				// current line and place the cursor at the end of the
-				// previous line.
-				if (gEditLines[prevLineIndex].displayLength() <= gEditWidth-1)
-				{
-					//displayDebugText(1, 1, "Here 8!!   \1;", console.getxy(), false, false, true); // Temporary
-					// Copy the current line's "hard newline end" setting to the
-					// previous line (so that if there's a blank line below the
-					// current line, the blank line will be preserved), then remove
-					// the current edit line.
-					gEditLines[gEditLinesIndex-1].hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
-					gEditLines.splice(gEditLinesIndex, 1);
+      // If the cursor was at the leftmost position in the edit area,
+      // update the edit lines from the currently-set screen line #.
+      if (originalX == gEditLeft)
+      {
+         // Since the original X position was at the left edge of the edit area,
+         // display the edit lines starting with the previous line if possible.
+         if ((gEditLinesIndex > 0) && (retObj.y > gEditTop))
+            displayEditLines(retObj.y-1, gEditLinesIndex-1, gEditBottom, true, true);
+         else
+            displayEditLines(retObj.y, gEditLinesIndex, gEditBottom, true, true);
+      }
+      // If the original horizontal cursor position was in the middle of
+      // the line, and the line is the last line on the screen, then
+      // only refresh that one line on the screen.
+      else if ((originalX > gEditLeft) && (originalX < gEditLeft + gEditWidth - 1) && originallyOnLastLine)
+         displayEditLines(originalY, originalCurrentLineIndex, originalY, false);
+      // If scrolling was to be done, then refresh the entire
+      // current message text on the screen from the top of the
+      // edit area.  Otherwise, only refresh starting from the
+      // original horizontal position and message line.
+      else
+      {
+         // Display the edit lines starting with the previous line if possible.
+         if ((gEditLinesIndex > 0) && (retObj.y > gEditTop))
+            displayEditLines(retObj.y-1, gEditLinesIndex-1, gEditBottom, true, true);
+         else
+            displayEditLines(retObj.y, gEditLinesIndex, gEditBottom, true, true);
+      }
 
-					--gEditLinesIndex;
-					// Append the former previous line's attribute codes to the
-					// end of the current line
-					gEditLines[prevLineIndex].text += currentLineAttrCodes;
-					gTextLineIndex = gEditLines[prevLineIndex].text.length;
-					retObj.x = gEditLeft + gEditLines[prevLineIndex].displayLength();
-					if (retObj.y > gEditTop)
-						--retObj.y;
-					// Set & output the attribute codes
-					gTextAttrs = currentLineAttrCodes;
-					console.print(chooseEditColor());
+      // Make sure the current word length is correct.
+      retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
+   }
 
-					didBackspace = true;
-				}
-			}
-		}
-	}
-
-	// If the backspace was performed, then re-adjust the text lines
-	// and refresh the screen.
-	if (didBackspace)
-	{
-		//displayDebugText(1, 1, "Here 9!!   \1;", console.getxy(), false, false, true); // Temporary
-		// Store the previous line of text now so we can compare it later
-		var prevTextline = "";
-		if (gEditLinesIndex > 0)
-			prevTextline = gEditLines[gEditLinesIndex-1].text;
-
-		// Re-adjust the text lines
-		reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
-
-		// If the previous line's length increased, that probably means that the
-		// user backspaced to the beginning of the current line and the word was
-		// moved to the end of the previous line.  If so, then move the cursor to
-		// the end of the previous line.
-		//var scrolled = false;
-		if ((gEditLinesIndex > 0) && (gEditLines[gEditLinesIndex-1].displayLength() > prevTextline.length))
-		{
-			// Update the text index variables and cusor position variables.
-			--gEditLinesIndex;
-			gTextLineIndex = gEditLines[gEditLinesIndex].text.length;
-			retObj.x = gEditLeft + gTextLineIndex;
-			if (retObj.y > gEditTop)
-				--retObj.y;
-		}
-
-		// If the cursor was at the leftmost position in the edit area,
-		// update the edit lines from the currently-set screen line #.
-		if (originalX == gEditLeft)
-		{
-			// Since the original X position was at the left edge of the edit area,
-			// display the edit lines starting with the previous line if possible.
-			if ((gEditLinesIndex > 0) && (retObj.y > gEditTop))
-				displayEditLines(retObj.y-1, gEditLinesIndex-1, gEditBottom, true, true);
-			else
-				displayEditLines(retObj.y, gEditLinesIndex, gEditBottom, true, true);
-		}
-		// If the original horizontal cursor position was in the middle of
-		// the line, and the line is the last line on the screen, then
-		// only refresh that one line on the screen.
-		else if ((originalX > gEditLeft) && (originalX < gEditLeft + gEditWidth - 1) && originallyOnLastLine)
-			displayEditLines(originalY, originalCurrentLineIndex, originalY, false);
-		// If scrolling was to be done, then refresh the entire
-		// current message text on the screen from the top of the
-		// edit area.  Otherwise, only refresh starting from the
-		// original horizontal position and message line.
-		else
-		{
-			// Display the edit lines starting with the previous line if possible.
-			if ((gEditLinesIndex > 0) && (retObj.y > gEditTop))
-				displayEditLines(retObj.y-1, gEditLinesIndex-1, gEditBottom, true, true);
-			else
-				displayEditLines(retObj.y, gEditLinesIndex, gEditBottom, true, true);
-		}
-
-		// Make sure the current word length is correct.
-		retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
-	}
-
-	// Make sure the cursor is placed where it should be.
-	console.gotoxy(retObj.x, retObj.y);
-	return retObj;
+   // Make sure the cursor is placed where it should be.
+   console.gotoxy(retObj.x, retObj.y);
+   return retObj;
 }
 
 // Helper function for doEditLoop(): Handles the delete key behavior.
@@ -2028,11 +1727,11 @@ function doBackspace(pCurpos, pCurrentWordLength)
 //               position and currentLength, the current word length.
 function doDeleteKey(pCurpos, pCurrentWordLength)
 {
-	var returnObject = {
-		x: pCurpos.x,
-		y: pCurpos.y,
-		currentWordLength: pCurrentWordLength
-	};
+	// Create the return object
+	var returnObject = new Object();
+	returnObject.x = pCurpos.x;
+	returnObject.y = pCurpos.y;
+	returnObject.currentWordLength = pCurrentWordLength;
 
 	// If gEditLinesIndex is invalid, then return without doing anything.
 	if ((gEditLinesIndex < 0) || (gEditLinesIndex >= gEditLines.length))
@@ -2043,21 +1742,14 @@ function doDeleteKey(pCurpos, pCurrentWordLength)
 
 	// If the text line index is within bounds, then we can
 	// delete the next character and refresh the screen.
-	//displayDebugText(1, 1, "Txt idx: " + gTextLineIndex + ", len: " + gEditLines[gEditLinesIndex].text.length + "      ", pCurpos, false, false, false); //  Temporary
-	if (gTextLineIndex < gEditLines[gEditLinesIndex].text.length)
+	if (gTextLineIndex < gEditLines[gEditLinesIndex].length())
 	{
-		var substrObj1 = gEditLines[gEditLinesIndex].substrWithSyncColorCodes(0, pCurpos.x-1);
-		// For the rest-of-line substring, account for Synchronet attribute codes
-		// at/ahead of the start index.
-		var substr2StartIdx = substrObj1.endIdx + 1;
-		while (gSyncAttrRegex.test(gEditLines[gEditLinesIndex].text.substr(substr2StartIdx, 2)))
-			substr2StartIdx += 2;
-		++substr2StartIdx;
-		var substr2 = gEditLines[gEditLinesIndex].text.substr(substr2StartIdx);
-		gEditLines[gEditLinesIndex].text = substrObj1.strSub + substr2;
+		var lineText = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex)
+		             + gEditLines[gEditLinesIndex].text.substr(gTextLineIndex+1);
+		gEditLines[gEditLinesIndex].text = lineText;
 		// If the current character is a space, then reset the current word length.
 		// to 0.  Otherwise, set it to the current word length.
-		if (gTextLineIndex < gEditLines[gEditLinesIndex].text.length)
+		if (gTextLineIndex < gEditLines[gEditLinesIndex].length())
 		{
 			if (gEditLines[gEditLinesIndex].text.charAt(gTextLineIndex) == " ")
 				returnObject.currentWordLength = 0;
@@ -2098,7 +1790,7 @@ function doDeleteKey(pCurpos, pCurrentWordLength)
 		gEditLines[gEditLinesIndex].hardNewlineEnd = false;
 
 		// If the current line is blank and is not the last line, then remove it.
-		if (gEditLines[gEditLinesIndex].displayLength() == 0)
+		if (gEditLines[gEditLinesIndex].length() == 0)
 		{
 			if (gEditLinesIndex < gEditLines.length-1)
 			{
@@ -2112,7 +1804,7 @@ function doDeleteKey(pCurpos, pCurrentWordLength)
 		else if (gEditLinesIndex < gEditLines.length-1)
 		{
 			var nextLineIndex = gEditLinesIndex + 1;
-			if (gEditLines[nextLineIndex].displayLength() == 0)
+			if (gEditLines[nextLineIndex].length() == 0)
 				gEditLines[nextLineIndex].hardNewlineEnd = false;
 		}
 
@@ -2158,218 +1850,168 @@ function doDeleteKey(pCurpos, pCurrentWordLength)
 //               currentLength: The length of the current word
 function doPrintableChar(pUserInput, pCurpos, pCurrentWordLength)
 {
-	var retObj = {
-		x: pCurpos.x,
-		y: pCurpos.y,
-		currentWordLength: pCurrentWordLength
-	};
+   // Create the return object.
+   var retObj = new Object();
+   retObj.x = pCurpos.x;
+   retObj.y = pCurpos.y;
+   retObj.currentWordLength = pCurrentWordLength;
 
-	var lineIdxWasAtBeginningOrMiddle = false;
+   // Note: gTextLineIndex is where the new character will appear in the line.
+   // If gTextLineIndex is somehow past the end of the current line, then
+   // fill it with spaces up to gTextLineIndex.
+   if (gTextLineIndex > gEditLines[gEditLinesIndex].length())
+   {
+      var numSpaces = gTextLineIndex - gEditLines[gEditLinesIndex].length();
+      if (numSpaces > 0)
+         gEditLines[gEditLinesIndex].text += format("%" + numSpaces + "s", "");
+      gEditLines[gEditLinesIndex].text += pUserInput;
+   }
+   // If gTextLineIndex is at the end of the line, then just append the char.
+   else if (gTextLineIndex == gEditLines[gEditLinesIndex].length())
+      gEditLines[gEditLinesIndex].text += pUserInput;
+   else
+   {
+      // gTextLineIndex is at the beginning or in the middle of the line.
+      if (inInsertMode())
+      {
+         gEditLines[gEditLinesIndex].text = spliceIntoStr(gEditLines[gEditLinesIndex].text,
+                                                          gTextLineIndex, pUserInput);
+      }
+      else
+      {
+         gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex)
+                                          + pUserInput + gEditLines[gEditLinesIndex].text.substr(gTextLineIndex+1);
+      }
+   }
 
-	// Note: gTextLineIndex is where the new character will appear in the line.
-	// If gTextLineIndex is somehow past the end of the current line, then
-	// fill it with spaces up to gTextLineIndex.
-	if (gTextLineIndex > gEditLines[gEditLinesIndex].text.length)
-	{
-		var numSpaces = gTextLineIndex - gEditLines[gEditLinesIndex].text.length;
-		if (numSpaces > 0)
-			gEditLines[gEditLinesIndex].text += format("%" + numSpaces + "s", "");
-		gEditLines[gEditLinesIndex].text += pUserInput;
-	}
-	// If gTextLineIndex is at the end of the line, then just append the char.
-	else if (gTextLineIndex == gEditLines[gEditLinesIndex].text.length)
-		gEditLines[gEditLinesIndex].text += pUserInput;
-	else
-	{
-		// gTextLineIndex is at the beginning or in the middle of the line.
-		lineIdxWasAtBeginningOrMiddle = true;
-		if (inInsertMode())
-			gEditLines[gEditLinesIndex].text = spliceIntoStr(gEditLines[gEditLinesIndex].text, gTextLineIndex, pUserInput);
-		else
-		{
-			// If there are Synchronet attribute codes at the current index, replace each 2 chars &
-			// next char; otherwise, replace 1 char.
-			var nextCharDiff = 1;
-			var tmpIdx = gTextLineIndex
-			while (tmpIdx < gEditLines[gEditLinesIndex].text.length)
-			{
-				if (gSyncAttrRegex.test(gEditLines[gEditLinesIndex].text.substr(tmpIdx, 2)))
-				{
-					nextCharDiff += 2;
-					tmpIdx += 2;
-				}
-				else
-					break;
-			}
-			gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex) + pUserInput + gEditLines[gEditLinesIndex].text.substr(gTextLineIndex+nextCharDiff);
-		}
-	}
+   // Handle text replacement (AKA macros).  Added 2013-08-31.
+   var madeTxtReplacement = false; // For screen refresh purposes
+   if (gConfigSettings.enableTextReplacements && (pUserInput == " "))
+   {
+      var txtReplaceObj = gEditLines[gEditLinesIndex].doMacroTxtReplacement(gTxtReplacements, gTextLineIndex,
+                                                            gConfigSettings.textReplacementsUseRegex);
+      madeTxtReplacement = txtReplaceObj.madeTxtReplacement;
+      if (madeTxtReplacement)
+      {
+         retObj.x += txtReplaceObj.wordLenDiff;
+         gTextLineIndex += txtReplaceObj.wordLenDiff;
+      }
+   }
 
-	// Handle text replacement (AKA macros).  Added 2013-08-31.
-	var madeTxtReplacement = false; // For screen refresh purposes
-	if (gConfigSettings.enableTextReplacements && (pUserInput == " "))
-	{
-		var colorsAllowedObj = colorSelectionAllowedInSettingsAndLine(gMsgAreaName, bbs.cursub_code);
-		var txtReplaceObj = gEditLines[gEditLinesIndex].doMacroTxtReplacement(gTxtReplacements, gTextLineIndex,
-		                                                                      gConfigSettings.textReplacementsUseRegex,
-		                                                                      colorsAllowedObj.colorsAllowed, gTextAttrs);
-		madeTxtReplacement = txtReplaceObj.madeTxtReplacement;
-		if (madeTxtReplacement)
-		{
-			retObj.x += txtReplaceObj.displayableWordLenDiff;
-			gTextLineIndex += txtReplaceObj.wordLenDiff;
-		}
-	}
+   // Store a copy of the current line so that we can compare it later to see
+   // if it was modified by reAdjustTextLines().
+   var originalAfterCharApplied = gEditLines[gEditLinesIndex].text;
 
-	// Store a copy of the current line so that we can compare it later to see
-	// if it was modified by reAdjustTextLines().
-	var originalAfterCharApplied = gEditLines[gEditLinesIndex].text;
-	var originalTextLineLength = gEditLines[gEditLinesIndex].text.length;
-	var originalTextLineDisplayLength = gEditLines[gEditLinesIndex].displayLength();
-	var originalTextLineScreenLength = gEditLines[gEditLinesIndex].displayLength();
-
-	// If the line is now too long to fit in the edit area, then we will have
-	// to re-adjust the text lines.
-	var reAdjusted = false;
-	if (gEditLines[gEditLinesIndex].displayLength() >= gEditWidth-1)
-		reAdjusted = reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
-	var wasOnLastLine = (gEditLinesIndex == gEditLines.length-1); // For possible adjustments later
-
-	// placeCursorAtEnd specifies whether or not to place the cursor at its
-	// spot using console.gotoxy() at the end.  This is an optimization.
-	var placeCursorAtEnd = true;
-
-	// If the current text line is now different (modified by reAdjustTextLines())
-	// or text replacements were made, then we'll need to refresh multiple lines
-	// on the screen.
-	if ((reAdjusted && (gEditLines[gEditLinesIndex].text != originalAfterCharApplied)) || madeTxtReplacement)
-	{
-		// If gTextLineIndex is >= gEditLines[gEditLinesIndex].text.length, then
-		// we know the current word was wrapped to the next line.  Figure out what
-		// retObj.x, retObj.currentWordLength, gEditLinesIndex, and gTextLineIndex
-		// should be, and increment retObj.y.  Also figure out what lines on the
-		// screen to update, and deal with scrolling if necessary.
-		if (gTextLineIndex >= gEditLines[gEditLinesIndex].text.length)
-		{
-			var numChars = 0;
-			// Special case: If the current line's length is exactly the longest
-			// edit with, then the # of chars should be 0 or 1, depending on whether the
-			// entered character was a space or not.  Otherwise, calculate numChars
-			// normally.
-			if (gEditLines[gEditLinesIndex].displayLength() == gEditWidth-1)
-				numChars = (pUserInput == " " ? 0 : 1);
-			else
-			{
-				numChars = originalTextLineScreenLength - gEditLines[gEditLinesIndex].displayLength();
-				if (gEditLinesIndex < gEditLines.length-1)
-					numChars = gEditLines[gEditLinesIndex+1].displayLength();
-			}
-			retObj.x = gEditLeft + numChars;
-			var originalEditLinesIndex = gEditLinesIndex++;
-			gTextLineIndex = numChars;
-			// Set gTextLineIndex to the proper value
-			// If The current text line has only Synchronet attribute codes,
-			// then set the text line index after the attribute codes.
-			if (gOnlySyncAttrsInStrRegex.test(gEditLines[gEditLinesIndex].text))
-				gTextLineIndex = gEditLines[gEditLinesIndex].text.length;
-
-			// Figure out which lines we need to update on the screen and whether
-			// to do scrolling and what retObj.y should be.
-			if (retObj.y < gEditBottom)
-			{
-				// We're above the last line on the screen, so we can go one
-				// line down.
-				var originalY = retObj.y++;
-				// Update the lines on the screen.
-				var bottommostRow = calcBottomUpdateRow(originalY, originalEditLinesIndex);
-				displayEditLines(originalY, originalEditLinesIndex, bottommostRow, true, true);
-			}
-			else
-			{
-				// We're on the last line in the edit area, so we need to scroll
-				// the text lines up on the screen.
-				var editLinesTopIndex = gEditLinesIndex - (pCurpos.y - gEditTop);
-				displayEditLines(gEditTop, editLinesTopIndex, gEditBottom, true, true);
-			}
-		}
-		else
-		{
-			// gTextLineIndex is < the line's length.  Update the lines on the
-			// screen from the current line down.  Increment retObj.x,
-			// retObj.currentWordLength, and gTextLineIndex.
-			var bottommostRow = calcBottomUpdateRow(retObj.y, gEditLinesIndex);
-			displayEditLines(retObj.y, gEditLinesIndex, bottommostRow, true, true);
-			if (pUserInput == " ")
-				retObj.currentWordLength = 0;
-			else
-				++retObj.currentWordLength;
-			++retObj.x;
-			++gTextLineIndex;
-		}
-	}
-	else
-	{
-		// The text line wasn't changed by reAdjustTextLines.
+   // If the line is now too long to fit in the edit area, then we will have
+   // to re-adjust the text lines.
+   var reAdjusted = false;
+   if (gEditLines[gEditLinesIndex].length() >= gEditWidth)
+      reAdjusted = reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
 
-		// If gTextLineIndex is not the last index of the line, then refresh the
-		// entire line on the screen.  Otherwise, just output the character that
-		// the user typed.
-		if (gTextLineIndex < gEditLines[gEditLinesIndex].text.length-1)
-			displayEditLines(retObj.y, gEditLinesIndex, retObj.y, false, true);
-		else
-		{
-			console.print(pUserInput);
-			placeCursorAtEnd = false; // Since we just output the character
-		}
+   // placeCursorAtEnd specifies whether or not to place the cursor at its
+   // spot using console.gotoxy() at the end.  This is an optimization.
+   var placeCursorAtEnd = true;
 
-		// Keep housekeeping variables up to date.
-		++retObj.x;
-		++gTextLineIndex;
-		/* retObj.currentWordLength is now calculated at the end, but we could do this:
-		if (pUserInput == " ")
-			retObj.currentWordLength = 0;
-		else
-			++retObj.currentWordLength;
-		*/
-	}
+   // If the current text line is now different (modified by reAdjustTextLines())
+   // or text replacements were made, then we'll need to refresh multiple lines
+   // on the screen.
+   if ((reAdjusted && (gEditLines[gEditLinesIndex].text != originalAfterCharApplied)) || madeTxtReplacement)
+   {
+      // If gTextLineIndex is >= gEditLines[gEditLinesIndex].length(), then
+      // we know the current word was wrapped to the next line.  Figure out what
+      // retObj.x, retObj.currentWordLength, gEditLinesIndex, and gTextLineIndex
+      // should be, and increment retObj.y.  Also figure out what lines on the
+      // screen to update, and deal with scrolling if necessary.
+      if (gTextLineIndex >= gEditLines[gEditLinesIndex].length())
+      {
+         // I changed this on 2010-02-14 to (hopefully) place the cursor where
+         // it should be
+         // Old line (prior to 2010-02-14):
+         //var numChars = gTextLineIndex - gEditLines[gEditLinesIndex].length();
+         // New (2010-02-14):
+         var numChars = 0;
+         // Special case: If the current line's length is exactly the longest
+         // edit with, then the # of chars should be 0 or 1, depending on whether the
+         // entered character was a space or not.  Otherwise, calculate numChars
+         // normally.
+         if (gEditLines[gEditLinesIndex].length() == gEditWidth-1)
+            numChars = ((pUserInput == " ") ? 0 : 1);
+         else
+            numChars = gTextLineIndex - gEditLines[gEditLinesIndex].length();
+         retObj.x = gEditLeft + numChars;
+         var originalEditLinesIndex = gEditLinesIndex++;
+         gTextLineIndex = numChars;
+         // The following line is now done at the end:
+         //retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
+
+         // Figure out which lines we need to update on the screen and whether
+         // to do scrolling and what retObj.y should be.
+         if (retObj.y < gEditBottom)
+         {
+            // We're above the last line on the screen, so we can go one
+            // line down.
+            var originalY = retObj.y++;
+            // Update the lines on the screen.
+            var bottommostRow = calcBottomUpdateRow(originalY, originalEditLinesIndex);
+            displayEditLines(originalY, originalEditLinesIndex, bottommostRow, true, true);
+         }
+         else
+         {
+            // We're on the last line in the edit area, so we need to scroll
+            // the text lines up on the screen.
+            var editLinesTopIndex = gEditLinesIndex - (pCurpos.y - gEditTop);
+            displayEditLines(gEditTop, editLinesTopIndex, gEditBottom, true, true);
+         }
+      }
+      else
+      {
+         // gTextLineIndex is < the line's length.  Update the lines on the
+         // screen from the current line down.  Increment retObj.x,
+         // retObj.currentWordLength, and gTextLineIndex.
+         var bottommostRow = calcBottomUpdateRow(retObj.y, gEditLinesIndex);
+         displayEditLines(retObj.y, gEditLinesIndex, bottommostRow, true, true);
+         if (pUserInput == " ")
+            retObj.currentWordLength = 0;
+         else
+            ++retObj.currentWordLength;
+         ++retObj.x;
+         ++gTextLineIndex;
+      }
+   }
+   else
+   {
+      // The text line wasn't changed by reAdjustTextLines.
+
+      // If gTextLineIndex is not the last index of the line, then refresh the
+      // entire line on the screen.  Otherwise, just output the character that
+      // the user typed.
+      if (gTextLineIndex < gEditLines[gEditLinesIndex].length()-1)
+         displayEditLines(retObj.y, gEditLinesIndex, retObj.y, false, true);
+      else
+      {
+         console.print(pUserInput);
+         placeCursorAtEnd = false; // Since we just output the character
+      }
 
-	// Make sure the current word length is correct.
-	retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
+      // Keep housekeeping variables up to date.
+      ++retObj.x;
+      ++gTextLineIndex;
+      /* retObj.currentWordLength is now calculated at the end, but we could do this:
+      if (pUserInput == " ")
+         retObj.currentWordLength = 0;
+      else
+         ++retObj.currentWordLength;
+      */
+   }
 
-	// If the cursor's horizontal position has gone beyond the edit width, then
-	// update the x & y position of the cursor, as well as the edit lines
-	// indexes.
-	if (retObj.x - gEditLeft + 1 > gEditWidth)
-	{
-		retObj.x -= gEditWidth;
-		++retObj.y;
-		++gEditLinesIndex; // There should be a next line in gEditLines
-		gTextLineIndex = gEditLines[gEditLinesIndex].displayIdxToActualIdx(retObj.x-gEditLeft);
-	}
-	else
-	{
-		// Edge case: If we re-adjusted text and the cursor was not on the last
-		// line, ensure the cursor and text line index are correct, in case the
-		// user was inserting text into a line when more lines existed below.
-		if (reAdjusted && !wasOnLastLine && (gEditLinesIndex > 0))
-		{
-			if (!lineIdxWasAtBeginningOrMiddle && (originalTextLineLength > gEditLines[gEditLinesIndex-1].text.length))
-			{
-				var wordLen = originalTextLineLength - gEditLines[gEditLinesIndex-1].text.length;
-				var displayableWordLen = originalTextLineDisplayLength - gEditLines[gEditLinesIndex-1].displayLength();
-				retObj.x = gEditLeft + displayableWordLen - 1;
-				// For gTextLineIndex, subtract 1 to account for space character at the end
-				// left over from line wrapping
-				gTextLineIndex = wordLen - 1;
-			}
-		}
-	}
+   // Make sure the current word length is correct.
+   retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 
-	// Make sure the cursor is placed where it should be.
-	if (placeCursorAtEnd)
-		console.gotoxy(retObj.x, retObj.y);
+   // Make sure the cursor is placed where it should be.
+   if (placeCursorAtEnd)
+      console.gotoxy(retObj.x, retObj.y);
 
-	return retObj;
+   return retObj;
 }
 
 // Helper function for doEditLoop(): Performs the action for when the user
@@ -2393,16 +2035,15 @@ function doPrintableChar(pUserInput, pCurpos, pCurrentWordLength)
 function doEnterKey(pCurpos, pCurrentWordLength)
 {
 	// Create the return object
-	var retObj = {
-		x: pCurpos.x,
-		y: pCurpos.y,
-		currentWordLength: pCurrentWordLength,
-		returnCode: 0,
-		continueOn: true,
-		doQuoteSelection: false,
-		doCrossPostSelection: false,
-		showHelp: false
-	};
+	var retObj = new Object();
+	retObj.x = pCurpos.x;
+	retObj.y = pCurpos.y;
+	retObj.currentWordLength = pCurrentWordLength;
+	retObj.returnCode = 0;
+	retObj.continueOn = true;
+	retObj.doQuoteSelection = false;
+	retObj.doCrossPostSelection = false;
+	retObj.showHelp = false;
 
 	// Store the current screen row position and gEditLines index.
 	var initialScreenLine = pCurpos.y;
@@ -2411,13 +2052,19 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 	// Check for slash commands (/S, /A, /?).  If the user has
 	// typed one of them by itself at the beginning of the line,
 	// then save, abort, or show help, respectively.
-	if (gEditLines[gEditLinesIndex].displayLength() == 2)
+	if (gEditLines[gEditLinesIndex].length() == 2)
 	{
-		var lineUpper = strip_ctrl(gEditLines[gEditLinesIndex].text).toUpperCase();
+		var lineUpper = gEditLines[gEditLinesIndex].text.toUpperCase();
 		// /S: Save
 		if (lineUpper == "/S")
 		{
-			gEditLines[gEditLinesIndex].text = "";
+			// If the current text line is the last one, remove it; otherwise,
+			// blank it out.
+			if (gEditLinesIndex == gEditLines.length-1)
+				gEditLines.splice(gEditLinesIndex, 1);
+			else
+				gEditLines[gEditLinesIndex].text = "";
+
 			retObj.continueOn = false;
 			return(retObj);
 		}
@@ -2437,10 +2084,9 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 				//console.print("\1n" + gTextAttrs);
 				console.print(chooseEditColor());
 
-				// Blank out the data in the text line, set the data in retObj
-				// and return it (and ensure it has any color/attribute codes
-				// the user may have set).
-				gEditLines[gEditLinesIndex].text = gTextAttrs;
+				// Blank out the data in the text line, set the data in
+				// retObj, and return it.
+				gEditLines[gEditLinesIndex].text = "";
 				retObj.currentWordLength = 0;
 				gTextLineIndex = 0;
 				retObj.x = gEditLeft;
@@ -2462,11 +2108,9 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 			retObj.showHelp = (lineUpper == "/?");
 			retObj.currentWordLength = 0;
 			gTextLineIndex = 0;
-			// Ensure the line is blank and has any color/attribute codes the
-			// user may have set.
-			gEditLines[gEditLinesIndex].text = gTextAttrs;
+			gEditLines[gEditLinesIndex].text = "";
 			// Blank out the /? on the screen
-			//console.print("\1n" + gTextAttrs);
+			//console.print("n" + gTextAttrs);
 			console.print(chooseEditColor());
 			retObj.x = gEditLeft;
 			console.gotoxy(retObj.x, retObj.y);
@@ -2479,10 +2123,9 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 		{
 			retObj.doCrossPostSelection = true;
 
-			// Blank out the data in the text line, set the data in retObj, and
-			// and return it (and ensure the line has any color/attribute codes
-			// the user may have set).
-			gEditLines[gEditLinesIndex].text = gTextAttrs;
+			// Blank out the data in the text line, set the data in
+			// retObj, and return it.
+			gEditLines[gEditLinesIndex].text = "";
 			retObj.currentWordLength = 0;
 			gTextLineIndex = 0;
 			retObj.x = gEditLeft;
@@ -2501,16 +2144,15 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 		{
 			if (gConfigSettings.enableTextReplacements)
 				listTextReplacements();
-			// Blank out the data in the text line, set the data in retObj, and
-			// return it (and ensure it has any color/attribute codes the user
-			// may have set).
-			gEditLines[gEditLinesIndex].text = gTextAttrs;
+			// Blank out the data in the text line, set the data in
+			// retObj, and return it.
+			gEditLines[gEditLinesIndex].text = "";
 			retObj.currentWordLength = 0;
 			gTextLineIndex = 0;
 			retObj.x = gEditLeft;
 			retObj.y = pCurpos.y;
 			// Blank out the /T on the screen
-			//console.print("\1n" + gTextAttrs);
+			//console.print("n" + gTextAttrs);
 			console.print(chooseEditColor());
 			retObj.x = gEditLeft;
 			console.gotoxy(retObj.x, retObj.y);
@@ -2525,10 +2167,9 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 			currentCursorPos.x = retObj.x;
 			currentCursorPos.y = retObj.y;
 			doUserSettings(currentCursorPos, false);
-			// Blank out the data in the text line, set the data in retObj, and
-			// return it (and ensure it has any color/attribute codes the user
-			// may have set).
-			gEditLines[gEditLinesIndex].text = gTextAttrs;
+			// Blank out the data in the text line, set the data in
+			// retObj, and return it.
+			gEditLines[gEditLinesIndex].text = "";
 			retObj.currentWordLength = 0;
 			gTextLineIndex = 0;
 			retObj.x = gEditLeft;
@@ -2553,12 +2194,12 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 	var cursorHorizDiff = 0;
 	if (gConfigSettings.enableTextReplacements)
 	{
-		var colorsAllowedObj = colorSelectionAllowedInSettingsAndLine(gMsgAreaName, bbs.cursub_code);
-		var txtReplaceObj = gEditLines[gEditLinesIndex].doMacroTxtReplacement(gTxtReplacements, gTextLineIndex-1, gConfigSettings.textReplacementsUseRegex, colorsAllowedObj.colorsAllowed, gTextAttrs);
+		var txtReplaceObj = gEditLines[gEditLinesIndex].doMacroTxtReplacement(gTxtReplacements, gTextLineIndex-1,
+		gConfigSettings.textReplacementsUseRegex);
 		if (txtReplaceObj.madeTxtReplacement)
 		{
 			gTextLineIndex += txtReplaceObj.wordLenDiff;
-			retObj.x += txtReplaceObj.displayableWordLenDiff;
+			retObj.x += txtReplaceObj.wordLenDiff;
 			retObj.currentWordLength += txtReplaceObj.wordLenDiff;
 
 			// If the replaced text on the line is too long to print on the screen,then
@@ -2567,6 +2208,7 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 			// is beyond the rightmost colum of the edit area, then wrap the line.
 			if (gEditLeft + txtReplaceObj.newTextEndIdx - 1 >= gEditRight - 1)
 			{
+				//reAdjustedTxtLines = reAdjustTextLines(gEditLines, gEditLinesIndex, gEditLines.length, gEditWidth);
 				// If the replaced text contains at least one space, then look for
 				// the last space that can appear within the edit area on the screen.
 				if (gEditLines[gEditLinesIndex].text.indexOf(" ", txtReplaceObj.wordStartIdx) > -1)
@@ -2629,21 +2271,8 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 	retObj.x += cursorHorizDiff;
 	gTextLineIndex += cursorHorizDiff;
 
-	// Place the cursor where it should be
 	console.gotoxy(retObj.x, retObj.y);
 
-	// If the current text line starts with the same attribute code twice,
-	// then remove one of the attribute codes.
-	if (gEditLines[gEditLinesIndex].text.search(gSyncAttrRegex) == 0)
-	{
-		var attrCode = gEditLines[gEditLinesIndex].text.substr(0, 2).toLowerCase();
-		if (gEditLines[gEditLinesIndex].text.substr(2, 2).toLowerCase() == attrCode)
-		{
-			gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(2);
-			gTextLineIndex -= 2;
-		}
-	}
-
 	return retObj;
 }
 
@@ -2667,163 +2296,122 @@ function doEnterKey(pCurpos, pCurrentWordLength)
 //                 the line was added below the line
 function enterKey_InsertOrAppendNewLine(pCurpos, pCurrentWordLength, pAppendLine)
 {
-	var returnObject = {
-		displayedEditlines: false,
-		addedATextLine: false,
-		addedTextLineBelow: false
-	};
-
-	// If we're at the end of the line, then we can simply
-	// add a new blank line & set the cursor there.
-	// Otherwise, we need to split the current line, and
-	// the text to the right of the cursor will go on the new line.
-	if (gTextLineIndex == gEditLines[gEditLinesIndex].text.length)
-	{
-		if (pAppendLine)
-		{
-			// Add a new blank line to the end of the message, and set
-			// the cursor there.
-			gEditLines.push(new TextLine());
-			++gEditLinesIndex;
-			// Ensure the new text line has any color/attribute codes the user has set
-			gEditLines[gEditLinesIndex].text = gTextAttrs;
-			returnObject.addedATextLine = true;
-			returnObject.addedTextLineBelow = true;
-		}
-		else
-		{
-			// Increment gEditLinesIndex and add a new line there.
-			++gEditLinesIndex;
-			gEditLines.splice(gEditLinesIndex, 0, new TextLine());
-			// Ensure the new text line has any color/attribute codes the user has set
-			gEditLines[gEditLinesIndex].text = gTextAttrs;
-			returnObject.addedATextLine = true;
-		}
-
-		gTextLineIndex = 0;
-		pCurrentWordLength = 0;
-		pCurpos.x = gEditLeft;
-		// Update the vertical cursor position.
-		// If the cursor is at the bottom row, then we need
-		// to scroll the message down by 1 line.  Otherwise,
-		// we can simply increment pCurpos.y.
-		if (pCurpos.y == gEditBottom)
-		{
-			displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop), gEditBottom, true, true);
-			returnObject.displayedEditlines = true;
-		}
-		else
-			++pCurpos.y;
-	}
-	else
-	{
-		// We're in the middle of the line.
-		// Get the text to the end of the current line.
-		// Note: The cursor's x position should be 1 more than display index in the string
-		var actualStartIdx = gEditLines[gEditLinesIndex].displayIdxToActualIdx(pCurpos.x-gEditLeft);
-		// If there are Synchronet attribute codes before actualStartIdx, then get them too
-		if (actualStartIdx >= 2)
-		{
-			var attrStartIdx = -1;
-			var textIdx = actualStartIdx - 2;
-			while (textIdx >= 0)
-			{
-				if (gSyncAttrRegex.test(gEditLines[gEditLinesIndex].text.substr(textIdx, 2)))
-				{
-					attrStartIdx = textIdx;
-					textIdx -= 2;
-				}
-				else
-					break;
-			}
-			if (attrStartIdx > -1)
-				actualStartIdx = attrStartIdx;
-		}
-		// Get the substring from the end of the current text line
-		var lineEnd = gEditLines[gEditLinesIndex].text.substr(actualStartIdx);
-
-		// Remove that text from the current line.
-		gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, actualStartIdx);
-
-		if (pAppendLine)
-		{
-			// Create a new line containing lineEn and append it to
-			// gEditLines.  Then place the cursor at the start of that line.
-			var newTextLine = new TextLine();
-			// Ensure the new text line has the text color/attribute codes the user
-			// has set
-			newTextLine.text = gTextAttrs + lineEnd;
-			newTextLine.hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
-			newTextLine.isQuoteLine = gEditLines[gEditLinesIndex].isQuoteLine;
-			gEditLines.push(newTextLine);
-			++gEditLinesIndex;
-			returnObject.addedATextLine = true;
-			returnObject.addedTextLineBelow = true;
-		}
-		else
-		{
-			// Create a new line containing lineEnd and splice it into
-			// gEditLines on the next line.  Then place the cursor at the
-			// start of that line.
-			var oldIndex = gEditLinesIndex++;
-			var newTextLine = new TextLine();
-			// Ensure the new line has any color/attribute codes the user has set
-			newTextLine.text = gTextAttrs + lineEnd;
-			newTextLine.hardNewlineEnd = gEditLines[oldIndex].hardNewlineEnd;
-			newTextLine.isQuoteLine = gEditLines[oldIndex].isQuoteLine;
-			// If the user pressed enter at the beginning of a line, then a new
-			// blank line will be inserted above, so we want to make sure its
-			// isQuoteLine property is set to false.
-			if (gTextLineIndex == 0)
-				gEditLines[oldIndex].isQuoteLine = false;
-			// Splice the new text line into gEditLines at gEditLinesIndex.
-			gEditLines.splice(gEditLinesIndex, 0, newTextLine);
-			returnObject.addedATextLine = true;
-		}
+   var returnObject = new Object();
+   returnObject.displayedEditlines = false;
+   returnObject.addedATextLine = false;
+   returnObject.addedTextLineBelow = false;
+
+   // If we're at the end of the line, then we can simply
+   // add a new blank line & set the cursor there.
+   // Otherwise, we need to split the current line, and
+   // the text to the right of the cursor will go on the new line.
+   if (gTextLineIndex == gEditLines[gEditLinesIndex].length())
+   {
+      if (pAppendLine)
+      {
+         // Add a new blank line to the end of the message, and set
+         // the cursor there.
+         gEditLines.push(new TextLine());
+         ++gEditLinesIndex;
+         returnObject.addedATextLine = true;
+         returnObject.addedTextLineBelow = true;
+      }
+      else
+      {
+         // Increment gEditLinesIndex and add a new line there.
+         ++gEditLinesIndex;
+         gEditLines.splice(gEditLinesIndex, 0, new TextLine());
+         returnObject.addedATextLine = true;
+      }
 
-		gTextLineIndex = 0;
-		pCurpos.x = gEditLeft;
-		// Update the vertical cursor position.
-		// If the cursor is at the bottom row, then we need
-		// to scroll the message down by 1 line.  Otherwise,
-		// we can simply increment pCurpos.y.
-		if (pCurpos.y == gEditBottom)
-		{
-			displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop), gEditBottom, true, true);
-			returnObject.displayedEditlines = true;
-		}
-		else
-			++pCurpos.y;
-		// Figure out the current word length.
-		// Look for a space in lineEnd.  If a space is found,
-		// the word length is the length of the word up until the
-		// space.  If a space is not found, then the word length
-		// is the entire length of lineEnd.
-		var spacePos = lineEnd.indexOf(" ");
-		if (spacePos > -1)
-			pCurrentWordLength = spacePos;
-		else
-			pCurrentWordLength = strip_ctrl(lineEnd).length;
-	}
+      gTextLineIndex = 0;
+      pCurrentWordLength = 0;
+      pCurpos.x = gEditLeft;
+      // Update the vertical cursor position.
+      // If the cursor is at the bottom row, then we need
+      // to scroll the message down by 1 line.  Otherwise,
+      // we can simply increment pCurpos.y.
+      if (pCurpos.y == gEditBottom)
+      {
+         displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
+                          gEditBottom, true, true);
+         returnObject.displayedEditlines = true;
+      }
+      else
+         ++pCurpos.y;
+   }
+   else
+   {
+      // We're in the middle of the line.
+      // Get the text to the end of the current line.
+      var lineEndText = gEditLines[gEditLinesIndex].text.substr(gTextLineIndex);
+      // Remove that text from the current line.
+      gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, gTextLineIndex);
 
-	// If the current line starts with the normal attribute and ends with normal-white and
-	// has no Synchronet attributes in between, then remove the redundant normal-white
-	// attributes from the end
-	var lineStartWithNormalAttr = (gEditLines[gEditLinesIndex].text.search(/n/i) == 0);
-	var lineEndsWithNormalWhite = /nw$/i.test(gEditLines[gEditLinesIndex].text);
-	if (lineStartWithNormalAttr && lineEndsWithNormalWhite)
-	{
-		var anySyncAttrsInBetween = gOnlySyncAttrsInStrRegex.test(gEditLines[gEditLinesIndex].text.substr(2, gEditLines[gEditLinesIndex].text.length-5));
-		if (!anySyncAttrsInBetween)
-			gEditLines[gEditLinesIndex].text = gEditLines[gEditLinesIndex].text.substr(0, gEditLines[gEditLinesIndex].text.length-4);
-	}
+      if (pAppendLine)
+      {
+         // Create a new line containing lineEndText and append it to
+         // gEditLines.  Then place the cursor at the start of that line.
+         var newTextLine = new TextLine();
+         newTextLine.text = lineEndText;
+         newTextLine.hardNewlineEnd = gEditLines[gEditLinesIndex].hardNewlineEnd;
+         newTextLine.isQuoteLine = gEditLines[gEditLinesIndex].isQuoteLine;
+         gEditLines.push(newTextLine);
+         ++gEditLinesIndex;
+         returnObject.addedATextLine = true;
+         returnObject.addedTextLineBelow = true;
+      }
+      else
+      {
+         // Create a new line containing lineEndText and splice it into
+         // gEditLines on the next line.  Then place the cursor at the
+         // start of that line.
+         var oldIndex = gEditLinesIndex++;
+         var newTextLine = new TextLine();
+         newTextLine.text = lineEndText;
+         newTextLine.hardNewlineEnd = gEditLines[oldIndex].hardNewlineEnd;
+         newTextLine.isQuoteLine = gEditLines[oldIndex].isQuoteLine;
+         // If the user pressed enter at the beginning of a line, then a new
+         // blank line will be inserted above, so we want to make sure its
+         // isQuoteLine property is set to false.
+         if (gTextLineIndex == 0)
+            gEditLines[oldIndex].isQuoteLine = false;
+         // Splice the new text line into gEditLines at gEditLinesIndex.
+         gEditLines.splice(gEditLinesIndex, 0, newTextLine);
+         returnObject.addedATextLine = true;
+      }
 
-	// Set some stuff in the return object, and return it.
-	returnObject.x = pCurpos.x;
-	returnObject.y = pCurpos.y;
-	returnObject.currentWordLength = pCurrentWordLength;
+      gTextLineIndex = 0;
+      pCurpos.x = gEditLeft;
+      // Update the vertical cursor position.
+      // If the cursor is at the bottom row, then we need
+      // to scroll the message down by 1 line.  Otherwise,
+      // we can simply increment pCurpos.y.
+      if (pCurpos.y == gEditBottom)
+      {
+         displayEditLines(gEditTop, gEditLinesIndex-(gEditBottom-gEditTop),
+                          gEditBottom, true, true);
+         returnObject.displayedEditlines = true;
+      }
+      else
+         ++pCurpos.y;
+      // Figure out the current word length.
+      // Look for a space in lineEndText.  If a space is found,
+      // the word length is the length of the word up until the
+      // space.  If a space is not found, then the word length
+      // is the entire length of lineEndText.
+      var spacePos = lineEndText.indexOf(" ");
+      if (spacePos > -1)
+         pCurrentWordLength = spacePos;
+      else
+         pCurrentWordLength = lineEndText.length;
+   }
 
-	return returnObject;
+   // Set some stuff in the return object, and return it.
+   returnObject.x = pCurpos.x;
+   returnObject.y = pCurpos.y;
+   returnObject.currentWordLength = pCurrentWordLength;
+   return returnObject;
 }
 
 // Helper function for doEditLoop(): Returns whether a text line is editable
@@ -2861,12 +2449,11 @@ function textLineIsEditable(pLineIdx)
 function doQuoteSelection(pCurpos, pCurrentWordLength)
 {
 	// Create the return object
-	var retObj = {
-		x: pCurpos.x,
-		y: pCurpos.y,
-		timedOut: false,
-		currentWordLength: pCurrentWordLength
-	};
+	var retObj = new Object();
+	retObj.x = pCurpos.x;
+	retObj.y = pCurpos.y;
+	retObj.timedOut = false;
+	retObj.currentWordLength = pCurrentWordLength;
 
 	// Note: Quote lines are in the gQuoteLines array, where each element is
 	// a string.
@@ -2921,6 +2508,7 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 			// then re-populate gQuoteLines with the original quote lines.
 			if (trimSpacesFromQuoteLinesSettingChanged)
 			{
+				//readQuoteOrMessageFile();
 				gQuoteLines = [];
 				for (var i = 0; i < doQuoteSelection.backupQuoteLines.length; ++i)
 					gQuoteLines.push(doQuoteSelection.backupQuoteLines[i]);
@@ -2944,6 +2532,8 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 			setQuotePrefix();
 			if (gConfigSettings.reWrapQuoteLines)
 			{
+				// TODO: This seemed to never be finishing for certain messages - Entering
+				// an infinite loop?  I believe this was fixed as of version 1.49.
 				wrapQuoteLines(gUserSettings.useQuoteLineInitials, gUserSettings.indentQuoteLinesWithInitials,
 				               gUserSettings.trimSpacesFromQuoteLines);
 			}
@@ -2957,10 +2547,9 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 	}
 
 	// Set up some variables
-	var curpos = {
-		x: pCurpos.x,
-		y: pCurpos.y
-	};
+	var curpos = new Object();
+	curpos.x = pCurpos.x;
+	curpos.y = pCurpos.y;
 	// Make the quote window's height about 42% of the edit area.
 	const quoteWinHeight = Math.floor(gEditHeight * 0.42) + 1;
 	// The first and last lines on the screen where quote lines are written
@@ -2989,7 +2578,6 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 	// User input loop
 	var quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
 	retObj.timedOut = false;
-	var choseFirstQuoteLine = true;
 	var userInput = null;
 	var continueOn = true;
 	while (continueOn)
@@ -3034,7 +2622,6 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 						quoteLine = getQuoteTextLine(gQuoteLinesIndex, quoteWinWidth);
 						console.gotoxy(gEditLeft, screenLine);
 						printf(gFormatStrWithAttr, gQuoteWinTextColor, quoteLine);
-						
 
 						// Go up one line and display that quote line in the
 						// highlighted color.
@@ -3052,8 +2639,8 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 			case KEY_DOWN:
 				// Go down 1 line in the quote window.
 				var downRetObj = moveDownOneQuoteLine(gQuoteLinesIndex, screenLine,
-				                                      quoteWinHeight, quoteWinWidth,
-				                                      quoteBottomScreenRow);
+				quoteWinHeight, quoteWinWidth,
+				quoteBottomScreenRow);
 				gQuoteLinesIndex = downRetObj.quoteLinesIndex;
 				screenLine = downRetObj.screenLine;
 				quoteLine = downRetObj.quoteLine;
@@ -3151,13 +2738,7 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 				var numTimesToMoveDown = 1;
 
 				// Insert the quote line into gEditLines after the current gEditLines index.
-				// Ensure the quote line has a normal attribute.  The user's text color/attributes
-				// will be set back into the edit lines later.
-				var insertedBelow = false;
-				if (choseFirstQuoteLine)
-					insertedBelow = insertLineIntoMsg(gEditLinesIndex, "\1n" + quoteLine, true, true);
-				else
-					insertedBelow = insertLineIntoMsg(gEditLinesIndex, quoteLine, true, true);
+				var insertedBelow = insertLineIntoMsg(gEditLinesIndex, quoteLine, true, true);
 				if (insertedBelow)
 				{
 					// The cursor will need to be moved down 1 more line.
@@ -3169,7 +2750,7 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 					retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 				}
 				else
-					retObj.currentWordLength = 0;
+				retObj.currentWordLength = 0;
 
 				// Refresh the part of the message that needs to be refreshed on the
 				// screen (above the quote window).
@@ -3228,6 +2809,7 @@ function doQuoteSelection(pCurpos, pCurrentWordLength)
 	                              gInsertMode, gConfigSettings.allowColorSelection);
 
 	// Make sure the color is correct for editing.
+	//console.print("n" + gTextAttrs);
 	console.print(chooseEditColor());
 	// Put the cursor where it should be.
 	console.gotoxy(curpos);
@@ -3496,96 +3078,83 @@ function toggleInsertMode(pCurpos)
 //                         to always write the edit text regardless of gEditAreaBuffer.
 //                         By default, gEditAreaBuffer is always checked.
 function displayEditLines(pStartScreenRow, pArrayIndex, pEndScreenRow, pClearRemainingScreenRows,
-                          pIgnoreEditAreaBuffer)
+                           pIgnoreEditAreaBuffer)
 {
-	// Make sure the array has lines in it, the given array index is valid, and
-	// that the given line # is valid.  If not, then just return.
-	if ((gEditLines.length == 0) || (pArrayIndex < 0) || (pStartScreenRow < 1) || (pStartScreenRow > gEditBottom))
-		return;
+   // Make sure the array has lines in it, the given array index is valid, and
+   // that the given line # is valid.  If not, then just return.
+   if ((gEditLines.length == 0) || (pArrayIndex < 0) || (pStartScreenRow < 1) || (pStartScreenRow > gEditBottom))
+      return;
 
-	// Choose which ending screen row to use for displaying text,
-	// pEndScreenRow or gEditBottom.
-	var endScreenRow = (pEndScreenRow != null ? pEndScreenRow : gEditBottom);
+   // Choose which ending screen row to use for displaying text,
+   // pEndScreenRow or gEditBottom.
+   var endScreenRow = (pEndScreenRow != null ? pEndScreenRow : gEditBottom);
 
-	// Display the message lines
-	var screenLine = pStartScreenRow;
-	var arrayIndex = pArrayIndex;
-	while ((screenLine <= endScreenRow) && (arrayIndex < gEditLines.length))
-	{
-		// Print the text from the current line in gEditLines.  Note: Lines starting
-		// with " >" are assumed to be quote lines - Display those lines with cyan
-		// color and the normal lines with the appropriate color
-		// Note: gEditAreaBuffer is also used in clearMsgAreaToBottom().
-		if ((gEditAreaBuffer[screenLine] != gEditLines[arrayIndex].text) || pIgnoreEditAreaBuffer)
-		{
-			// Choose the quote line color or the normal color for the line, then
-			// display the line on the screen.
-			var color = "";
-			if (isQuoteLine(gEditLines, arrayIndex))
-				color = gQuoteLineColor;
-			else
-			{
-				color = "\1n";
-				// If we're past the first line, look for the last color codes
-				// in the previous line and use those, since those should be
-				// applicable to print the current line.
-				if (arrayIndex > 0)
-					color = gEditLines[arrayIndex-1].getLastAttrCodes();
-			}
-			// If a color/attribute code wasn't found, then use the normal
-			// attribute to ensure the wrong color isn't used in the wrong place
-			if (color.length == 0)
-				color = "\1n";
-			console.gotoxy(gEditLeft, screenLine);
-			printLineAndFillEditWidthRemainder(gEditLines[arrayIndex].text, color, isQuoteLine(gEditLines, arrayIndex));
-			gEditAreaBuffer[screenLine] = gEditLines[arrayIndex].text;
-		}
+   // Display the message lines
+   console.print("n" + gTextAttrs);
+   var screenLine = pStartScreenRow;
+   var arrayIndex = pArrayIndex;
+   while ((screenLine <= endScreenRow) && (arrayIndex < gEditLines.length))
+   {
+      // Print the text from the current line in gEditLines.  Note: Lines starting
+      // with " >" are assumed to be quote lines - Display those lines with cyan
+      // color and the normal lines with gTextAttrs.
+      var color = gTextAttrs;
+      // Note: gEditAreaBuffer is also used in clearMsgAreaToBottom().
+      if ((gEditAreaBuffer[screenLine] != gEditLines[arrayIndex].text) || pIgnoreEditAreaBuffer)
+      {
+         // Choose the quote line color or the normal color for the line, then
+         // display the line on the screen.
+         color = (isQuoteLine(gEditLines, arrayIndex) ? gQuoteLineColor : gTextAttrs);
+         console.gotoxy(gEditLeft, screenLine);
+         printf(gFormatStrWithAttr, color, gEditLines[arrayIndex].text);
+         gEditAreaBuffer[screenLine] = gEditLines[arrayIndex].text;
+      }
 
-		++screenLine;
-		++arrayIndex;
-	}
-	if (arrayIndex > 0)
-		--arrayIndex;
-	// incrementLineBeforeClearRemaining stores whether or not we
-	// should increment screenLine before clearing the remaining
-	// lines in the edit area.
-	var incrementLineBeforeClearRemaining = true;
-	// If the array index is valid, and if the current line is shorter
-	// than the edit area width, then place the cursor after the last
-	// character in the line.
-	if ((arrayIndex >= 0) && (arrayIndex < gEditLines.length) && (gEditLines[arrayIndex] != undefined) &&
-	    (gEditLines[arrayIndex].text != undefined))
-	{
-		var lineLength = gEditLines[arrayIndex].displayLength();
-		if (lineLength < gEditWidth)
-		{
-			--screenLine;
-			console.gotoxy(gEditLeft + gEditLines[arrayIndex].displayLength(), screenLine);
-		}
-		else if ((lineLength == gEditWidth) || (lineLength == 0))
-			incrementLineBeforeClearRemaining = false;
-	}
-	else
-		incrementLineBeforeClearRemaining = false;
+      ++screenLine;
+      ++arrayIndex;
+   }
+   if (arrayIndex > 0)
+      --arrayIndex;
+   // incrementLineBeforeClearRemaining stores whether or not we
+   // should increment screenLine before clearing the remaining
+   // lines in the edit area.
+   var incrementLineBeforeClearRemaining = true;
+   // If the array index is valid, and if the current line is shorter
+   // than the edit area width, then place the cursor after the last
+   // character in the line.
+   if ((arrayIndex >= 0) && (arrayIndex < gEditLines.length) &&
+       (gEditLines[arrayIndex] != undefined) && (gEditLines[arrayIndex].text != undefined))
+   {
+      var lineLength = gEditLines[arrayIndex].length();
+      if (lineLength < gEditWidth)
+      {
+         --screenLine;
+         console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLine);
+      }
+      else if ((lineLength == gEditWidth) || (lineLength == 0))
+         incrementLineBeforeClearRemaining = false;
+   }
+   else
+      incrementLineBeforeClearRemaining = false;
 
-	// Edge case: If the current screen line is below the last line, then
-	// clear the lines up until that point.
-	var clearRemainingScreenLines = (pClearRemainingScreenRows != null ? pClearRemainingScreenRows : true);
-	if (clearRemainingScreenLines && (screenLine <= endScreenRow))
-	{
-		//console.print("\1n" + gTextAttrs);
-		console.print("\1n");
-		var screenLineBackup = screenLine; // So we can move the cursor back
-		clearMsgAreaToBottom(incrementLineBeforeClearRemaining ? screenLine+1 : screenLine, pIgnoreEditAreaBuffer);
-		// Move the cursor back to the end of the current text line.
-		if (typeof(gEditLines[arrayIndex]) != "undefined")
-			console.gotoxy(gEditLeft + gEditLines[arrayIndex].displayLength(), screenLineBackup);
-		else
-			console.gotoxy(gEditLeft, screenLineBackup);
-	}
+   // Edge case: If the current screen line is below the last line, then
+   // clear the lines up until that point.
+   var clearRemainingScreenLines = (pClearRemainingScreenRows != null ? pClearRemainingScreenRows : true);
+   if (clearRemainingScreenLines && (screenLine <= endScreenRow))
+   {
+      console.print("n" + gTextAttrs);
+      var screenLineBackup = screenLine; // So we can move the cursor back
+      clearMsgAreaToBottom(incrementLineBeforeClearRemaining ? screenLine+1 : screenLine,
+                           pIgnoreEditAreaBuffer);
+      // Move the cursor back to the end of the current text line.
+      if (typeof(gEditLines[arrayIndex]) != "undefined")
+         console.gotoxy(gEditLeft + gEditLines[arrayIndex].length(), screenLineBackup);
+      else
+         console.gotoxy(gEditLeft, screenLineBackup);
+   }
 
-	// Make sure the correct color is set for the current line.
-	console.print(chooseEditColor());
+   // Make sure the correct color is set for the current line.
+   console.print(chooseEditColor());
 }
 
 // Clears the lines in the message area from a given line to the bottom.
@@ -3617,7 +3186,7 @@ function messageIsEmpty()
    
    for (var i = 0; i < gEditLines.length; ++i)
    {
-      if (gEditLines[i].displayLength() > 0)
+      if (gEditLines[i].length() > 0)
       {
          msgEmpty = false;
          break;
@@ -3641,53 +3210,54 @@ function messageIsEmpty()
 //                    up to the specified width will be cleared.  Defaults to false.
 function displayMessageRectangle(pX, pY, pWidth, pHeight, pEditLinesIndex, pClearExtraWidth)
 {
-	// If any of the parameters are out of bounds, then just return without
-	// doing anything.
-	if ((pX < gEditLeft) || (pY < gEditTop) || (pWidth < 0) || (pHeight < 0) || (pEditLinesIndex < 0))
-		return;
+   // If any of the parameters are out of bounds, then just return without
+   // doing anything.
+   if ((pX < gEditLeft) || (pY < gEditTop) || (pWidth < 0) || (pHeight < 0) || (pEditLinesIndex < 0))
+      return;
 
-	// If pWidth is too long with the given pX, then fix it.
-	if (pWidth > (gEditRight - pX + 1))
-		pWidth = gEditRight - pX + 1;
-	// If pHeight is too much with the given pY, then fix it.
-	if (pHeight > (gEditBottom - pY + 1))
-		pHeight = gEditBottom - pY + 1;
-
-	// Calculate the index into the edit line using pX and gEditLeft.  This
-	// assumes that pX is within the edit area (and it should be).
-	const editLineIndex = pX - gEditLeft;
-
-	// Go to the given position on the screen and output the message text.
-	var messageStr = ""; // Will contain a portion of the message text
-	var screenY = pY;
-	var editLinesIndex = pEditLinesIndex;
-	var formatStr = "%-" + pWidth + "s";
-	var actualLenWritten = 0; // Actual length of text written for each line
-	for (var rectangleLine = 0; rectangleLine < pHeight; ++rectangleLine)
-	{
-		// Go to the position on the screen
-		screenY = pY + rectangleLine;
-		console.gotoxy(pX, screenY);
-		// Display the message text.  If the current edit line is valid,
-		// then print it; otherwise, just print spaces to blank out the line.
-		if (typeof(gEditLines[editLinesIndex]) != "undefined")
-		{
-			actualLenWritten = printEditLine(editLinesIndex, editLineIndex, pWidth);
-			// If pClearExtraWidth is true, then if the box width is longer than
-			// the text that was written, then output spaces to clear the rest
-			// of the line to erase the rest of the box line.
-			if (pClearExtraWidth)
-			{
-				var clearWidth = pWidth - actualLenWritten;
-				if (pWidth > actualLenWritten)
-					printf("%" + clearWidth + "s", "");
-			}
-		}
-		else
-			printf(formatStr, "");
+   // If pWidth is too long with the given pX, then fix it.
+   if (pWidth > (gEditRight - pX + 1))
+      pWidth = gEditRight - pX + 1;
+   // If pHeight is too much with the given pY, then fix it.
+   if (pHeight > (gEditBottom - pY + 1))
+      pHeight = gEditBottom - pY + 1;
+
+   // Calculate the index into the edit line using pX and gEditLeft.  This
+   // assumes that pX is within the edit area (and it should be).
+   const editLineIndex = pX - gEditLeft;
+
+   // Go to the given position on the screen and output the message text.
+   var messageStr = ""; // Will contain a portion of the message text
+   var screenY = pY;
+   var editLinesIndex = pEditLinesIndex;
+   var formatStr = "%-" + pWidth + "s";
+   var actualLenWritten = 0; // Actual length of text written for each line
+   for (var rectangleLine = 0; rectangleLine < pHeight; ++rectangleLine)
+   {
+      // Output the correct color for the line
+      console.print("n" + (isQuoteLine(gEditLines, editLinesIndex) ? gQuoteLineColor : gTextAttrs));
+      // Go to the position on the screen
+      screenY = pY + rectangleLine;
+      console.gotoxy(pX, screenY);
+      // Display the message text.  If the current edit line is valid,
+      // then print it; otherwise, just print spaces to blank out the line.
+      if (typeof(gEditLines[editLinesIndex]) != "undefined")
+      {
+         actualLenWritten = printEditLine(editLinesIndex, false, editLineIndex, pWidth);
+         // If pClearExtraWidth is true, then if the box width is longer than
+         // the text that was written, then output spaces to clear the rest
+         // of the line to erase the rest of the box line.
+         if (pClearExtraWidth)
+         {
+            if (pWidth > actualLenWritten)
+               printf("%" + +(pWidth-actualLenWritten) + "s", "");
+         }
+      }
+      else
+         printf(formatStr, "");
 
-		++editLinesIndex;
-	}
+      ++editLinesIndex;
+   }
 }
 
 // Displays the DCTEdit-style ESC menu and handles user input from that menu.
@@ -3708,134 +3278,139 @@ function displayMessageRectangle(pX, pY, pWidth, pHeight, pEditLinesIndex, pClea
 //                 currentWordLength: The length of the current word
 function handleDCTESCMenu(pCurpos, pCurrentWordLength)
 {
-	var returnObj = new Object();
-	returnObj.returnCode = 0;
-	returnObj.continueOn = true;
-	returnObj.x = pCurpos.x;
-	returnObj.y = pCurpos.y;
-	returnObj.currentWordLength = pCurrentWordLength;
-
-	// Call doDCTMenu() to display the DCT Edit menu and get the
-	// user's choice.
-	var editLineDiff = pCurpos.y - gEditTop;
-	var menuChoice = doDCTMenu(gEditLeft, gEditRight, gEditTop,
-	                           displayMessageRectangle, gEditLinesIndex,
-	                           editLineDiff, gConfigSettings.userIsSysop, gCanCrossPost);
-	// Take action according to the user's choice.
-	// Save
-	if ((menuChoice == "S") || (menuChoice == CTRL_Z) || (menuChoice == DCTMENU_FILE_SAVE))
-	{
-		returnObj.returnCode = 0;
-		returnObj.continueOn = false;
-	}
-	// Abort
-	else if ((menuChoice == "A") || (menuChoice == CTRL_A) || (menuChoice == DCTMENU_FILE_ABORT))
-	{
-		// Before aborting, ask they user if they really want to abort.
-		if (promptYesNo("Abort message", false, "Abort", false))
-		{
-			returnObj.returnCode = 1; // Aborted
-			returnObj.continueOn = false;
-		}
-		else
-		{
-			// Make sure the edit color attribute is set back.
-			console.print(chooseEditColor());
-		}
-	}
-	// Toggle insert/overwrite mode
-	else if ((menuChoice == CTRL_V) || (menuChoice == DCTMENU_EDIT_INSERT_TOGGLE))
-		toggleInsertMode(pCurpos);
-	// Import file (sysop only)
-	else if (menuChoice == DCTMENU_SYSOP_IMPORT_FILE)
-	{
-		var retval = importFile(gConfigSettings.userIsSysop, pCurpos);
-		returnObj.x = retval.x;
-		returnObj.y = retval.y;
-		returnObj.currentWordLength = retval.currentWordLength;
-	}
-	// Import file for sysop, or Insert/Overwrite toggle for non-sysop
-	else if (menuChoice == "I")
-	{
-		if (gConfigSettings.userIsSysop)
-		{
-			var retval = importFile(gConfigSettings.userIsSysop, pCurpos);
-			returnObj.x = retval.x;
-			returnObj.y = retval.y;
-			returnObj.currentWordLength = retval.currentWordLength;
-		}
-		else
-			toggleInsertMode(pCurpos);
-	}
-	// Find text
-	else if ((menuChoice == CTRL_F) || (menuChoice == "F") ||
-	         (menuChoice == DCTMENU_EDIT_FIND_TEXT))
-	{
-		var retval = findText(pCurpos);
-		returnObj.x = retval.x;
-		returnObj.y = retval.y;
-	}
-	// Command List
-	else if ((menuChoice == "O") || (menuChoice == DCTMENU_HELP_COMMAND_LIST))
-	{
-		displayCommandList(true, true, true, gCanCrossPost, gConfigSettings);
-		clearEditAreaBuffer();
-		fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
-		               gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
-		               displayEditLines);
-	}
-	// General help
-	else if ((menuChoice == "G") || (menuChoice == DCTMENU_HELP_GENERAL))
-	{
-		displayGeneralHelp(true, true, true);
-		clearEditAreaBuffer();
-		fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
-		               gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
-		               displayEditLines);
-	}
-	// Program info
-	else if ((menuChoice == "P") || (menuChoice == DCTMENU_HELP_PROGRAM_INFO))
-	{
-		displayProgramInfo(true, true);
-		clearEditAreaBuffer();
-		fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
-		               gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
-		               displayEditLines);
-	}
-	// Export the message
-	else if ((menuChoice == "X") || (menuChoice == DCTMENU_SYSOP_EXPORT_FILE))
-	{
-		if (gConfigSettings.userIsSysop)
-		{
-			exportToFile(gConfigSettings.userIsSysop);
-			console.gotoxy(returnObj.x, returnObj.y);
-		}
-	}
-	// Edit the message
-	else if ((menuChoice == "E") || (menuChoice == KEY_ESC))
-	{
-		// We don't need to do do anything in here.
-	}
-	// Cross-post
-	else if ((menuChoice == CTRL_C) || (menuChoice == "C") || (menuChoice == DCTMENU_CROSS_POST))
-	{
-		if (gCanCrossPost)
-			doCrossPosting(pCurpos);
-	}
-	// List text replacements
-	else if ((menuChoice == CTRL_T) || (menuChoice == "T") || (menuChoice == DCTMENU_LIST_TXT_REPLACEMENTS))
-	{
-		if (gConfigSettings.enableTextReplacements)
-			listTextReplacements();
-	}
-	// User settings
-	else if ((menuChoice == CTRL_U) || (menuChoice == "N") || (menuChoice == DCTMENU_EDIT_SETTINGS))
-		doUserSettings(pCurpos, true);
+   var returnObj = new Object();
+   returnObj.returnCode = 0;
+   returnObj.continueOn = true;
+   returnObj.x = pCurpos.x;
+   returnObj.y = pCurpos.y;
+   returnObj.currentWordLength = pCurrentWordLength;
+
+   // Call doDCTMenu() to display the DCT Edit menu and get the
+   // user's choice.
+   var editLineDiff = pCurpos.y - gEditTop;
+   var menuChoice = doDCTMenu(gEditLeft, gEditRight, gEditTop,
+                              displayMessageRectangle, gEditLinesIndex,
+                              editLineDiff, gConfigSettings.userIsSysop, gCanCrossPost);
+   // Take action according to the user's choice.
+   // Save
+   if ((menuChoice == "S") || (menuChoice == CTRL_Z) ||
+       (menuChoice == DCTMENU_FILE_SAVE))
+   {
+      returnObj.returnCode = 0;
+      returnObj.continueOn = false;
+   }
+   // Abort
+   else if ((menuChoice == "A") || (menuChoice == CTRL_A) ||
+             (menuChoice == DCTMENU_FILE_ABORT))
+   {
+      // Before aborting, ask they user if they really want to abort.
+      if (promptYesNo("Abort message", false, "Abort", false))
+      {
+         returnObj.returnCode = 1; // Aborted
+         returnObj.continueOn = false;
+      }
+      else
+      {
+         // Make sure the edit color attribute is set back.
+         //console.print("n" + gTextAttrs);
+         console.print(chooseEditColor());
+      }
+   }
+   // Toggle insert/overwrite mode
+   else if ((menuChoice == CTRL_V) || (menuChoice == DCTMENU_EDIT_INSERT_TOGGLE))
+      toggleInsertMode(pCurpos);
+   // Import file (sysop only)
+   else if (menuChoice == DCTMENU_SYSOP_IMPORT_FILE)
+   {
+      var retval = importFile(gConfigSettings.userIsSysop, pCurpos);
+      returnObj.x = retval.x;
+      returnObj.y = retval.y;
+      returnObj.currentWordLength = retval.currentWordLength;
+   }
+   // Import file for sysop, or Insert/Overwrite toggle for non-sysop
+   else if (menuChoice == "I")
+   {
+      if (gConfigSettings.userIsSysop)
+      {
+         var retval = importFile(gConfigSettings.userIsSysop, pCurpos);
+         returnObj.x = retval.x;
+         returnObj.y = retval.y;
+         returnObj.currentWordLength = retval.currentWordLength;
+      }
+      else
+         toggleInsertMode(pCurpos);
+   }
+   // Find text
+   else if ((menuChoice == CTRL_F) || (menuChoice == "F") ||
+             (menuChoice == DCTMENU_EDIT_FIND_TEXT))
+   {
+      var retval = findText(pCurpos);
+      returnObj.x = retval.x;
+      returnObj.y = retval.y;
+   }
+   // Command List
+   else if ((menuChoice == "O") || (menuChoice == DCTMENU_HELP_COMMAND_LIST))
+   {
+      displayCommandList(true, true, true, gCanCrossPost, gConfigSettings.userIsSysop,
+                         gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
+      clearEditAreaBuffer();
+      fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
+                     gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
+                     displayEditLines);
+   }
+   // General help
+   else if ((menuChoice == "G") || (menuChoice == DCTMENU_HELP_GENERAL))
+   {
+      displayGeneralHelp(true, true, true);
+      clearEditAreaBuffer();
+      fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
+                     gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
+                     displayEditLines);
+   }
+   // Program info
+   else if ((menuChoice == "P") || (menuChoice == DCTMENU_HELP_PROGRAM_INFO))
+   {
+      displayProgramInfo(true, true);
+      clearEditAreaBuffer();
+      fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
+                     gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
+                     displayEditLines);
+   }
+   // Export the message
+   else if ((menuChoice == "X") || (menuChoice == DCTMENU_SYSOP_EXPORT_FILE))
+   {
+      if (gConfigSettings.userIsSysop)
+      {
+         exportToFile(gConfigSettings.userIsSysop);
+         console.gotoxy(returnObj.x, returnObj.y);
+      }
+   }
+   // Edit the message
+   else if ((menuChoice == "E") || (menuChoice == KEY_ESC))
+   {
+      // We don't need to do do anything in here.
+   }
+   // Cross-post
+   else if ((menuChoice == CTRL_C) || (menuChoice == "C") || (menuChoice == DCTMENU_CROSS_POST))
+   {
+      if (gCanCrossPost)
+         doCrossPosting(pCurpos);
+   }
+   // List text replacements
+   else if ((menuChoice == CTRL_T) || (menuChoice == "T") || (menuChoice == DCTMENU_LIST_TXT_REPLACEMENTS))
+   {
+      if (gConfigSettings.enableTextReplacements)
+         listTextReplacements();
+   }
+   // User settings
+   else if ((menuChoice == CTRL_U) || (menuChoice == "N") || (menuChoice == DCTMENU_EDIT_SETTINGS))
+      doUserSettings(pCurpos, true);
 
-	// Make sure the edit color attribute is set back.
-	console.print(chooseEditColor());
+   // Make sure the edit color attribute is set back.
+   //console.print("n" + gTextAttrs);
+   console.print(chooseEditColor());
 
-	return returnObj;
+   return returnObj;
 }
 
 // Displays the IceEdit-style ESC menu and handles user input from that menu.
@@ -3888,7 +3463,8 @@ function handleIceESCMenu(pCurpos, pCurrentWordLength)
 			break;
 		case ICE_ESC_MENU_HELP:
 			displayProgramInfo(true, false);
-			displayCommandList(false, false, true, gCanCrossPost, gConfigSettings);
+			displayCommandList(false, false, true, gCanCrossPost, gConfigSettings.userIsSysop,
+			                   gConfigSettings.enableTextReplacements, gConfigSettings.allowUserSettings);
 			clearEditAreaBuffer();
 			fpRedrawScreen(gEditLeft, gEditRight, gEditTop, gEditBottom, gTextAttrs,
 			               gInsertMode, gUseQuotes, gEditLinesIndex-(pCurpos.y-gEditTop),
@@ -3922,64 +3498,65 @@ function handleIceESCMenu(pCurpos, pCurrentWordLength)
 // Return value: The length of the word at the given indexes
 function getWordLength(pEditLinesIndex, pTextLineIndex)
 {
-	// pEditLinesIndex and pTextLineIndex should be >= 0 before we can do
-	// anything in this function.
-	if ((pEditLinesIndex < 0) || (pTextLineIndex < 0))
-		return 0;
-	// Also, make sure gEditLines[pEditLinesIndex] is valid.
-	if ((gEditLines[pEditLinesIndex] == null) || (typeof(gEditLines[pEditLinesIndex]) == "undefined"))
-		return 0;
-
-	// This function counts and returns the number of non-whitespace characters
-	// before the current character.
-	function countBeforeCurrentChar()
-	{
-		var charCount = 0;
+   // pEditLinesIndex and pTextLineIndex should be >= 0 before we can do
+   // anything in this function.
+   if ((pEditLinesIndex < 0) || (pTextLineIndex < 0))
+      return 0;
+   // Also, make sure gEditLines[pEditLinesIndex] is valid.
+   if ((gEditLines[pEditLinesIndex] == null) || (typeof(gEditLines[pEditLinesIndex]) == "undefined"))
+      return 0;
+
+   // This function counts and returns the number of non-whitespace characters
+   // before the current character.
+   function countBeforeCurrentChar()
+   {
+      var charCount = 0;
 
-		for (var i = pTextLineIndex-1; i >= 0; --i)
-		{
-			if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(i)))
-				++charCount;
-			else
-				break;
-		}
+      for (var i = pTextLineIndex-1; i >= 0; --i)
+      {
+         if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(i)))
+            ++charCount;
+         else
+            break;
+      }
 
-		return charCount;
-	}
+      return charCount;
+   }
 
-	var wordLen = 0;
+   var wordLen = 0;
 
-	// If there are only characters to the left, or if the current
-	// character is a space, then count before the current character.
-	if ((pTextLineIndex == gEditLines[pEditLinesIndex].displayLength()) || (gEditLines[pEditLinesIndex].text.charAt(gTextLineIndex) == " "))
-		wordLen = countBeforeCurrentChar();
-	// If there are charactrs to the left and at the current line index,
-	// then count to the left only if the current character is not whitespace.
-	else if (pTextLineIndex == gEditLines[pEditLinesIndex].displayLength()-1)
-	{
-		if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(pTextLineIndex)))
-			wordLen = countBeforeCurrentChar() + 1;
-	}
-	// If there are characters to the left and right, then count to the left
-	// and right only if the current character is not whitespace.
-	else if (pTextLineIndex < gEditLines[pEditLinesIndex].displayLength()-1)
-	{
-		if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(pTextLineIndex)))
-		{
-			// Count non-whitespace characters to the left, and include the current one.
-			wordLen = countBeforeCurrentChar() + 1;
-			// Count characters to the right.
-			for (var i = pTextLineIndex+1; i < gEditLines[pEditLinesIndex].displayLength(); ++i)
-			{
-				if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(i)))
-					++wordLen;
-				else
-					break;
-			}
-		}
-	}
+   // If there are only characters to the left, or if the current
+   // character is a space, then count before the current character.
+   if ((pTextLineIndex == gEditLines[pEditLinesIndex].length()) ||
+       (gEditLines[pEditLinesIndex].text.charAt(gTextLineIndex) == " "))
+      wordLen = countBeforeCurrentChar();
+   // If there are charactrs to the left and at the current line index,
+   // then count to the left only if the current character is not whitespace.
+   else if (pTextLineIndex == gEditLines[pEditLinesIndex].length()-1)
+   {
+      if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(pTextLineIndex)))
+         wordLen = countBeforeCurrentChar() + 1;
+   }
+   // If there are characters to the left and right, then count to the left
+   // and right only if the current character is not whitespace.
+   else if (pTextLineIndex < gEditLines[pEditLinesIndex].length()-1)
+   {
+      if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(pTextLineIndex)))
+      {
+         // Count non-whitespace characters to the left, and include the current one.
+         wordLen = countBeforeCurrentChar() + 1;
+         // Count characters to the right.
+         for (var i = pTextLineIndex+1; i < gEditLines[pEditLinesIndex].length(); ++i)
+         {
+            if (!/\s/.test(gEditLines[pEditLinesIndex].text.charAt(i)))
+               ++wordLen;
+            else
+               break;
+         }
+      }
+   }
 
-	return wordLen;
+   return wordLen;
 }
 
 // Inserts a string into gEditLines after a given index.
@@ -3994,38 +3571,38 @@ function getWordLength(pEditLinesIndex, pTextLineIndex)
 //               (as opposed to above).
 function insertLineIntoMsg(pInsertLineIndex, pString, pHardNewline, pIsQuoteLine)
 {
-	var insertedBelow = false;
-
-	// Create the new text line
-	var line = new TextLine();
-	line.text = pString;
-	line.hardNewlineEnd = false;
-	if ((pHardNewline != null) && (typeof(pHardNewline) != "undefined"))
-		line.hardNewlineEnd = pHardNewline;
-	if ((pIsQuoteLine != null) && (typeof(pIsQuoteLine) != "undefined"))
-		line.isQuoteLine = pIsQuoteLine;
-
-	// If the current message line is empty, insert the quote line above
-	// the current line.  Otherwise, insert the quote line below the
-	// current line.
-	if (typeof(gEditLines[pInsertLineIndex]) == "undefined")
-		gEditLines.splice(pInsertLineIndex, 0, line);
-	// Note: One time, I noticed an error with the following test:
-	// gEditLines[pInsertLineIndex] has no properties
-	// Thus, I added the above test to see if the edit line is valid.
-	else if (gEditLines[pInsertLineIndex].displayLength() == 0)
-		gEditLines.splice(pInsertLineIndex, 0, line);
-	else
-	{
-		// Insert the quote line below the given line index
-		gEditLines.splice(pInsertLineIndex + 1, 0, line);
-		// The current message line should have its hardNewlineEnd set
-		// true so that the quote line won't get wrapped up.
-		gEditLines[pInsertLineIndex].hardNewlineEnd = true;
-		insertedBelow = true;
-	}
+   var insertedBelow = false;
+
+   // Create the new text line
+   var line = new TextLine();
+   line.text = pString;
+   line.hardNewlineEnd = false;
+   if ((pHardNewline != null) && (typeof(pHardNewline) != "undefined"))
+      line.hardNewlineEnd = pHardNewline;
+   if ((pIsQuoteLine != null) && (typeof(pIsQuoteLine) != "undefined"))
+      line.isQuoteLine = pIsQuoteLine;
+
+   // If the current message line is empty, insert the quote line above
+   // the current line.  Otherwise, insert the quote line below the
+   // current line.
+   if (typeof(gEditLines[pInsertLineIndex]) == "undefined")
+      gEditLines.splice(pInsertLineIndex, 0, line);
+   // Note: One time, I noticed an error with the following test:
+   // gEditLines[pInsertLineIndex] has no properties
+   // Thus, I added the above test to see if the edit line is valid.
+   else if (gEditLines[pInsertLineIndex].length() == 0)
+      gEditLines.splice(pInsertLineIndex, 0, line);
+   else
+   {
+      // Insert the quote line below the given line index
+      gEditLines.splice(pInsertLineIndex + 1, 0, line);
+      // The current message line should have its hardNewlineEnd set
+      // true so that the quote line won't get wrapped up.
+      gEditLines[pInsertLineIndex].hardNewlineEnd = true;
+      insertedBelow = true;
+   }
 
-	return insertedBelow;
+   return insertedBelow;
 }
 
 // Prompts the user for a filename on the BBS computer and loads its contents
@@ -4041,153 +3618,142 @@ function insertLineIntoMsg(pInsertLineIndex, pString, pHardNewline, pIsQuoteLine
 //               currentWordLength: The length of the current word
 function importFile(pIsSysop, pCurpos)
 {
-	// Create the return object
-	var retObj = {
-		x: pCurpos.x,
-		y: pCurpos.y,
-		currentWordLength: getWordLength(gEditLinesIndex, gTextLineIndex)
-	};
+   // Create the return object
+   var retObj = new Object();
+   retObj.x = pCurpos.x;
+   retObj.y = pCurpos.y;
+   retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
 
-	// Don't let non-sysops do this.
-	if (!pIsSysop)
-		return retObj;
+   // Don't let non-sysops do this.
+   if (!pIsSysop)
+      return retObj;
 
-	var loadedAFile = false;
-	// This loop continues to prompt the user until they enter a valid
-	// filename or a blank string.
-	var continueOn = true;
-	while (continueOn)
-	{
-		// Go to the last row on the screen and prompt the user for a filename
-		var promptText = "\1n\1cFile:\1h";
-		var promptTextLen = strip_ctrl(promptText).length;
-		console.gotoxy(1, console.screen_rows);
-		console.cleartoeol("\1n");
-		console.print(promptText);
-		var filename = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
-		continueOn = (filename != "");
-		if (continueOn)
-		{
-			filename = file_getcase(filename);
-			if (filename != undefined)
-			{
-				// Open the file and insert its contents into the message.
-				var inFile = new File(filename);
-				if (inFile.exists && inFile.open("r"))
-				{
-					const maxLineLength = gEditWidth - 1; // Don't insert lines longer than this
-					var fileLine;
-					while (!inFile.eof)
-					{
-						fileLine = inFile.readln(1024);
-						// fileLine should always be a string, but there seem to be
-						// situations where it isn't.  So if it's a string, we can
-						// insert text into gEditLines as normal.  If it's not a
-						// string, insert a blank line.
-						if (typeof(fileLine) == "string")
-						{
-							// Tab characters can cause problems, so replace tabs with 3 spaces.
-							fileLine = fileLine.replace(/\t/, "   ");
-							// Insert the line into the message, splitting up the line,
-							// if the line is longer than the edit area.  And handle
-							// Synchronet color codes.
-							do
-							{
-								//gConfigSettings.allowColorSelection && colorSelectionAllowedInMsgArea(pMsgAreaName, pSubCode)
-								// Convert any ANSI colors in the file line to Synchronet
-								// colors
-								fileLine = cvtANSIToSyncAndRemoveUnwantedANSI(fileLine);
-								var substrObj = substrWithSyncColorCodes(fileLine, 0, maxLineLength);
-								// If color codes are allowed and allowed in the current message area,
-								// then insert the line as-is.  Otherwise, strip control characters
-								// from the line when inserting it.
-								if (gConfigSettings.allowColorSelection && colorSelectionAllowedInMsgArea(gMsgAreaName, bbs.cursub_code))
-									insertLineIntoMsg(gEditLinesIndex, substrObj.strSub, true, false);
-								else
-									insertLineIntoMsg(gEditLinesIndex, strip_ctrl(substrObj.strSub), true, false);
-								fileLine = fileLine.substr(substrObj.endIdx+1);
-								++gEditLinesIndex;
-							} while (strip_ctrl(fileLine).length > maxLineLength);
-							// Edge case, if the line still has characters in it
-							if (fileLine.length > 0)
-							{
-								insertLineIntoMsg(gEditLinesIndex, fileLine, true, false);
-								++gEditLinesIndex;
-							}
-						}
-						else
-						{
-							insertLineIntoMsg(gEditLinesIndex, "", true, false);
-							++gEditLinesIndex;
-						}
-					}
-					inFile.close();
+   var loadedAFile = false;
+   // This loop continues to prompt the user until they enter a valid
+   // filename or a blank string.
+   var continueOn = true;
+   while (continueOn)
+   {
+      // Go to the last row on the screen and prompt the user for a filename
+      var promptText = "ncFile:h";
+      var promptTextLen = strip_ctrl(promptText).length;
+      console.gotoxy(1, console.screen_rows);
+      console.cleartoeol("n");
+      console.print(promptText);
+      var filename = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
+      continueOn = (filename != "");
+      if (continueOn)
+      {
+         filename = file_getcase(filename);
+         if (filename != undefined)
+         {
+            // Open the file and insert its contents into the message.
+            var inFile = new File(filename);
+            if (inFile.exists && inFile.open("r"))
+            {
+               const maxLineLength = gEditWidth - 1; // Don't insert lines longer than this
+               var fileLine;
+               while (!inFile.eof)
+               {
+                  fileLine = inFile.readln(1024);
+                  // fileLine should always be a string, but there seem to be
+                  // situations where it isn't.  So if it's a string, we can
+                  // insert text into gEditLines as normal.  If it's not a
+                  // string, insert a blank line.
+                  if (typeof(fileLine) == "string")
+                  {
+                     // Tab characters can cause problems, so replace tabs with 3 spaces.
+                     fileLine = fileLine.replace(/\t/, "   ");
+                     // Insert the line into the message, splitting up the line,
+                     // if the line is longer than the edit area.
+                     do
+                     {
+                        insertLineIntoMsg(gEditLinesIndex, fileLine.substr(0, maxLineLength),
+                                          true, false);
+                        fileLine = fileLine.substr(maxLineLength);
+                        ++gEditLinesIndex;
+                     } while (fileLine.length > maxLineLength);
+                     // Edge case, if the line still has characters in it
+                     if (fileLine.length > 0)
+                     {
+                        insertLineIntoMsg(gEditLinesIndex, fileLine, true, false);
+                        ++gEditLinesIndex;
+                     }
+                  }
+                  else
+                  {
+                     insertLineIntoMsg(gEditLinesIndex, "", true, false);
+                     ++gEditLinesIndex;
+                  }
+               }
+               inFile.close();
 
-					// If the last text line is blank, then remove it.
-					if (gEditLines[gEditLinesIndex].displayLength() == 0)
-					{
-						gEditLines.splice(gEditLinesIndex, 1);
-						--gEditLinesIndex;
-					}
+               // If the last text line is blank, then remove it.
+               if (gEditLines[gEditLinesIndex].length() == 0)
+               {
+                  gEditLines.splice(gEditLinesIndex, 1);
+                  --gEditLinesIndex;
+               }
 
-					loadedAFile = true;
-					continueOn = false;
-				}
-				else // Unable to open the file
-					writeWithPause(1, console.screen_rows, "\1y\1hUnable to open the file!", ERRORMSG_PAUSE_MS);
-			}
-			else // Could not find the correct case for the file (it doesn't exist?)
-				writeWithPause(1, console.screen_rows, "\1y\1hUnable to locate the file!", ERRORMSG_PAUSE_MS);
-		}
-	}
+               loadedAFile = true;
+               continueOn = false;
+            }
+            else // Unable to open the file
+               writeWithPause(1, console.screen_rows, "yhUnable to open the file!", ERRORMSG_PAUSE_MS);
+         }
+         else // Could not find the correct case for the file (it doesn't exist?)
+            writeWithPause(1, console.screen_rows, "yhUnable to locate the file!", ERRORMSG_PAUSE_MS);
+      }
+   }
 
-	// Refresh the help line on the bottom of the screen
-	fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
+   // Refresh the help line on the bottom of the screen
+   fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
 
-	// If we loaded a file, then refresh the message text.
-	if (loadedAFile)
-	{
-		// Insert a blank line into gEditLines so that the user ends up on a new
-		// blank line.
-		// Figure out the index to start at in gEditLines
-		var startIndex = 0;
-		if (gEditLines.length > gEditHeight)
-			startIndex = gEditLines.length - gEditHeight;
-		// Refresh the message on the screen
-		displayEditLines(gEditTop, startIndex, gEditBottom, true, true);
-
-		// Set up the edit lines & text line index for the last line, and
-		// place the cursor at the beginning of the last edit line.
-		// If the last line is short enough, place the cursor at the end
-		// of it.  Otherwise, append a new line and place the cursor there.
-		if (gEditLines[gEditLinesIndex].displayLength() < gEditWidth-1)
-		{
-			gEditLinesIndex = gEditLines.length - 1;
-			gTextLineIndex = gEditLines[gEditLinesIndex].text.length;
-			retObj.x = gEditLeft + gEditLines[gEditLinesIndex].displayLength();
-			retObj.y = gEditBottom;
-			if (gEditLines.length < gEditHeight)
-				retObj.y = gEditTop + gEditLines.length - 1;
-			retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
-		}
-		else
-		{
-			// Append a new line and place the cursor there
-			gEditLines.push(new TextLine());
-			gEditLinesIndex = gEditLines.length - 1;
-			gTextLineIndex = 0;
-			retObj.x = gEditLeft;
-			retObj.y = gEditBottom;
-			if (gEditLines.length < gEditHeight)
-				retObj.y = gEditTop + gEditLines.length - 1;
-			retObj.currentWordLength = 0;
-		}
-	}
+   // If we loaded a file, then refresh the message text.
+   if (loadedAFile)
+   {
+      // Insert a blank line into gEditLines so that the user ends up on a new
+      // blank line.
+      //displayEditLines(pScreenLine, pArrayIndex, pEndScreenRow, pClearRemainingScreenRows)
+      // Figure out the index to start at in gEditLines
+      var startIndex = 0;
+      if (gEditLines.length > gEditHeight)
+         startIndex = gEditLines.length - gEditHeight;
+      // Refresh the message on the screen
+      displayEditLines(gEditTop, startIndex, gEditBottom, true, true);
+
+      // Set up the edit lines & text line index for the last line, and
+      // place the cursor at the beginning of the last edit line.
+      // If the last line is short enough, place the cursor at the end
+      // of it.  Otherwise, append a new line and place the cursor there.
+      if (gEditLines[gEditLinesIndex].length() < gEditWidth-1)
+      {
+         gEditLinesIndex = gEditLines.length - 1;
+         gTextLineIndex = gEditLines[gEditLinesIndex].length();
+         retObj.x = gEditLeft + gTextLineIndex;
+         retObj.y = gEditBottom;
+         if (gEditLines.length < gEditHeight)
+            retObj.y = gEditTop + gEditLines.length - 1;
+         retObj.currentWordLength = getWordLength(gEditLinesIndex, gTextLineIndex);
+      }
+      else
+      {
+         // Append a new line and place the cursor there
+         gEditLines.push(new TextLine());
+         gEditLinesIndex = gEditLines.length - 1;
+         gTextLineIndex = 0;
+         retObj.x = gEditLeft;
+         retObj.y = gEditBottom;
+         if (gEditLines.length < gEditHeight)
+            retObj.y = gEditTop + gEditLines.length - 1;
+         retObj.currentWordLength = 0;
+      }
+   }
 
-	// Make sure the cursor is where it's supposed to be.
-	console.gotoxy(retObj.x, retObj.y);
+   // Make sure the cursor is where it's supposed to be.
+   console.gotoxy(retObj.x, retObj.y);
 
-	return retObj;
+   return retObj;
 }
 
 // This function lets sysops export (save) the current message to
@@ -4197,42 +3763,42 @@ function importFile(pIsSysop, pCurpos)
 //  pIsSysop: Whether or not the user is the sysop
 function exportToFile(pIsSysop)
 {
-	// Don't let non-sysops do this.
-	if (!pIsSysop)
-		return;
+   // Don't let non-sysops do this.
+   if (!pIsSysop)
+      return;
 
-	// Go to the last row on the screen and prompt the user for a filename
-	var promptText = "\1n\1cFile:\1h";
-	var promptTextLen = strip_ctrl(promptText).length;
-	console.gotoxy(1, console.screen_rows);
-	console.cleartoeol("\1n");
-	console.print(promptText);
-	var filename = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
-	if (filename != "")
-	{
-		var outFile = new File(filename);
-		if (outFile.open("w"))
-		{
-			const lastLineIndex = gEditLines.length - 1;
-			for (var i = 0; i < gEditLines.length; ++i)
-			{
-				// Use writeln to write all lines with CRLF except the last line.
-				if (i < lastLineIndex)
-					outFile.writeln(gEditLines[i].text);
-				else
-					outFile.write(gEditLines[i].text);
-			}
-			outFile.close();
-			writeWithPause(1, console.screen_rows, "\1m\1hMessage exported.", ERRORMSG_PAUSE_MS);
-		}
-		else // Could not open the file for writing
-			writeWithPause(1, console.screen_rows, "\1y\1hUnable to open the file for writing!", ERRORMSG_PAUSE_MS);
-	}
-	else // No filename specified
-		writeWithPause(1, console.screen_rows, "\1m\1hMessage not exported.", ERRORMSG_PAUSE_MS);
+   // Go to the last row on the screen and prompt the user for a filename
+   var promptText = "ncFile:h";
+   var promptTextLen = strip_ctrl(promptText).length;
+   console.gotoxy(1, console.screen_rows);
+   console.cleartoeol("n");
+   console.print(promptText);
+   var filename = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
+   if (filename != "")
+   {
+      var outFile = new File(filename);
+      if (outFile.open("w"))
+      {
+         const lastLineIndex = gEditLines.length - 1;
+         for (var i = 0; i < gEditLines.length; ++i)
+         {
+            // Use writeln to write all lines with CRLF except the last line.
+            if (i < lastLineIndex)
+               outFile.writeln(gEditLines[i].text);
+            else
+               outFile.write(gEditLines[i].text);
+         }
+         outFile.close();
+         writeWithPause(1, console.screen_rows, "mhMessage exported.", ERRORMSG_PAUSE_MS);
+      }
+      else // Could not open the file for writing
+         writeWithPause(1, console.screen_rows, "yhUnable to open the file for writing!", ERRORMSG_PAUSE_MS);
+   }
+   else // No filename specified
+      writeWithPause(1, console.screen_rows, "mhMessage not exported.", ERRORMSG_PAUSE_MS);
 
-	// Refresh the help line on the bottom of the screen
-	fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
+   // Refresh the help line on the bottom of the screen
+   fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
 }
 
 // Performs a text search.
@@ -4245,135 +3811,135 @@ function exportToFile(pIsSysop)
 //               y: The vertical component of the cursor position
 function findText(pCurpos)
 {
-	// Create the return object.
-	var returnObj = new Object();
-	returnObj.x = pCurpos.x;
-	returnObj.y = pCurpos.y;
-
-	// This function makes use of the following "static" variables:
-	//  lastSearchText: The text searched for last
-	//  searchStartIndex: The starting index for gEditLines that should
-	//                    be used for the search
-	if (typeof(findText.lastSearchText) == "undefined")
-		findText.lastSearchText = "";
-	if (typeof(findText.searchStartIndex) == "undefined")
-		findText.searchStartIndex = 0;
-
-	// Go to the last row on the screen and prompt the user for text to find
-	var promptText = "ncText:h";
-	var promptTextLen = strip_ctrl(promptText).length;
-	console.gotoxy(1, console.screen_rows);
-	console.cleartoeol("n");
-	console.print(promptText);
-	var searchText = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
-
-	// If the user's search is text is different from last time, then set the
-	// starting gEditLines index to 0.  Also, update the last search text.
-	if (searchText != findText.lastSearchText)
-		findText.searchStartIndex = 0;
-	findText.lastSearchText = searchText;
-
-	// Search for the text.
-	var caseSensitive = false; // Case-sensitive search?
-	var textIndex = 0; // The index of the text in the edit lines
-	if (searchText.length > 0)
-	{
-		// editLinesTopIndex is the index of the line currently displayed
-		// at the top of the edit area, and also the line to be displayed
-		// at the top of the edit area.
-		var editLinesTopIndex = gEditLinesIndex - (pCurpos.y - gEditTop);
-
-		// Look for the text in gEditLines
-		var textFound = false;
-		for (var i = findText.searchStartIndex; i < gEditLines.length; ++i)
-		{
-			if (caseSensitive)
-				textIndex = gEditLines[i].text.indexOf(searchText);
-			else
-				textIndex = gEditLines[i].text.toUpperCase().indexOf(searchText.toUpperCase());
-			// If the text was found in this line, then highlight it and
-			// exit the search loop.
-			if (textIndex > -1)
-			{
-				gTextLineIndex = textIndex;
-				textFound = true;
-
-				// If the line is above or below the edit area, then we'll need
-				// to refresh the edit lines on the screen.  We also need to set
-				// the cursor position to the proper place.
-				returnObj.x = gEditLeft + gTextLineIndex;
-				var refresh = false;
-				if (i < editLinesTopIndex)
-				{
-					// The line is above the edit area.
-					refresh = true;
-					returnObj.y = gEditTop;
-					editLinesTopIndex = i;
-				}
-				else if (i >= editLinesTopIndex + gEditHeight)
-				{
-					// The line is below the edit area.
-					refresh = true;
-					returnObj.y = gEditBottom;
-					editLinesTopIndex = i - gEditHeight + 1;
-				}
-				else
-				{
-					// The line is inside the edit area.
-					returnObj.y = pCurpos.y + (i - gEditLinesIndex);
-				}
+   // Create the return object.
+   var returnObj = new Object();
+   returnObj.x = pCurpos.x;
+   returnObj.y = pCurpos.y;
+
+   // This function makes use of the following "static" variables:
+   //  lastSearchText: The text searched for last
+   //  searchStartIndex: The starting index for gEditLines that should
+   //                    be used for the search
+   if (typeof(findText.lastSearchText) == "undefined")
+      findText.lastSearchText = "";
+   if (typeof(findText.searchStartIndex) == "undefined")
+      findText.searchStartIndex = 0;
+
+   // Go to the last row on the screen and prompt the user for text to find
+   var promptText = "ncText:h";
+   var promptTextLen = strip_ctrl(promptText).length;
+   console.gotoxy(1, console.screen_rows);
+   console.cleartoeol("n");
+   console.print(promptText);
+   var searchText = console.getstr(console.screen_columns-promptTextLen-1, K_NOCRLF);
+
+   // If the user's search is text is different from last time, then set the
+   // starting gEditLines index to 0.  Also, update the last search text.
+   if (searchText != findText.lastSearchText)
+      findText.searchStartIndex = 0;
+   findText.lastSearchText = searchText;
+
+   // Search for the text.
+   var caseSensitive = false; // Case-sensitive search?
+   var textIndex = 0; // The index of the text in the edit lines
+   if (searchText.length > 0)
+   {
+      // editLinesTopIndex is the index of the line currently displayed
+      // at the top of the edit area, and also the line to be displayed
+      // at the top of the edit area.
+      var editLinesTopIndex = gEditLinesIndex - (pCurpos.y - gEditTop);
+
+      // Look for the text in gEditLines
+      var textFound = false;
+      for (var i = findText.searchStartIndex; i < gEditLines.length; ++i)
+      {
+         if (caseSensitive)
+            textIndex = gEditLines[i].text.indexOf(searchText);
+         else
+            textIndex = gEditLines[i].text.toUpperCase().indexOf(searchText.toUpperCase());
+         // If the text was found in this line, then highlight it and
+         // exit the search loop.
+         if (textIndex > -1)
+         {
+            gTextLineIndex = textIndex;
+            textFound = true;
+
+            // If the line is above or below the edit area, then we'll need
+            // to refresh the edit lines on the screen.  We also need to set
+            // the cursor position to the proper place.
+            returnObj.x = gEditLeft + gTextLineIndex;
+            var refresh = false;
+            if (i < editLinesTopIndex)
+            {
+               // The line is above the edit area.
+               refresh = true;
+               returnObj.y = gEditTop;
+               editLinesTopIndex = i;
+            }
+            else if (i >= editLinesTopIndex + gEditHeight)
+            {
+               // The line is below the edit area.
+               refresh = true;
+               returnObj.y = gEditBottom;
+               editLinesTopIndex = i - gEditHeight + 1;
+            }
+            else
+            {
+               // The line is inside the edit area.
+               returnObj.y = pCurpos.y + (i - gEditLinesIndex);
+            }
 
-				gEditLinesIndex = i;
+            gEditLinesIndex = i;
 
-				if (refresh)
-					displayEditLines(gEditTop, editLinesTopIndex, gEditBottom, true, true);
+            if (refresh)
+               displayEditLines(gEditTop, editLinesTopIndex, gEditBottom, true, true);
 
-				// Highlight the found text on the line by briefly displaying it in a
-				// different color.
-				var highlightText = gEditLines[i].text.substr(textIndex, searchText.length);
-				console.gotoxy(returnObj.x, returnObj.y);
-				console.print("\1n\1k\1" + "4" + highlightText);
-				mswait(1500);
-				console.gotoxy(returnObj.x, returnObj.y);
-				//console.print(gTextAttrs + highlightText);
-				console.print(chooseEditColor() + highlightText);
+            // Highlight the found text on the line by briefly displaying it in a
+            // different color.
+            var highlightText = gEditLines[i].text.substr(textIndex, searchText.length);
+            console.gotoxy(returnObj.x, returnObj.y);
+            console.print("nk4" + highlightText);
+            mswait(1500);
+            console.gotoxy(returnObj.x, returnObj.y);
+            //console.print(gTextAttrs + highlightText);
+            console.print(chooseEditColor() + highlightText);
 
-				// The next time the user searches with the same text, we'll want
-				// to start searching at the next line.  Wrap around if necessary.
-				findText.searchStartIndex = i + 1;
-				if (findText.searchStartIndex >= gEditLines.length)
-					findText.searchStartIndex = 0;
+            // The next time the user searches with the same text, we'll want
+            // to start searching at the next line.  Wrap around if necessary.
+            findText.searchStartIndex = i + 1;
+            if (findText.searchStartIndex >= gEditLines.length)
+               findText.searchStartIndex = 0;
 
-				break;
-			}
-		}
+            break;
+         }
+      }
 
-		// If the text wasn't found, tell the user.  Also, make sure searchStartIndex
-		// is reset to 0.
-		if (!textFound)
-		{
-			console.gotoxy(1, console.screen_rows);
-			console.cleartoeol("\1n");
-			console.print("\1y\1hThe text wasn't found!");
-			mswait(ERRORMSG_PAUSE_MS);
+      // If the text wasn't found, tell the user.  Also, make sure searchStartIndex
+      // is reset to 0.
+      if (!textFound)
+      {
+         console.gotoxy(1, console.screen_rows);
+         console.cleartoeol("n");
+         console.print("yhThe text wasn't found!");
+         mswait(ERRORMSG_PAUSE_MS);
 
-			findText.searchStartIndex = 0;
-		}
-	}
+         findText.searchStartIndex = 0;
+      }
+   }
 
-	// Refresh the help line on the bottom of the screen
-	fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
+   // Refresh the help line on the bottom of the screen
+   fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
 
-	// Make sure the cursor is positioned where it should be.
-	console.gotoxy(returnObj.x, returnObj.y);
+   // Make sure the cursor is positioned where it should be.
+   console.gotoxy(returnObj.x, returnObj.y);
 
-	return returnObj;
+   return returnObj;
 }
 
 // Returns whether we're in insert mode (if not, we're in overwrite mode).
 function inInsertMode()
 {
-	return (gInsertMode == "INS");
+   return (gInsertMode == "INS");
 }
 
 // Returns either the normal edit color (gTextAttrs) or the quote line
@@ -4381,7 +3947,7 @@ function inInsertMode()
 // is a normal line or a quote line.
 function chooseEditColor()
 {
-	return ("\1n" + (isQuoteLine(gEditLines, gEditLinesIndex) ? gQuoteLineColor : gTextAttrs));
+   return ("n" + (isQuoteLine(gEditLines, gEditLinesIndex) ? gQuoteLineColor : gTextAttrs));
 }
 
 // This function calculates the row on the screen to stop updating the
@@ -4395,14 +3961,14 @@ function chooseEditColor()
 //               message text.
 function calcBottomUpdateRow(pY, pTopIndex)
 {
-	var bottomScreenRow = gEditBottom;
-	// Note: This is designed to return the screen row #
-	// below the last message line.  To return the exact
-	// bottommost screen row, subtract 1 from gEditLines.length-pTopIndex.
-	var bottommost = (pY + (gEditLines.length-pTopIndex));
-	if (bottomScreenRow > bottommost)
-		bottomScreenRow = bottommost;
-	return bottomScreenRow;
+   var bottomScreenRow = gEditBottom;
+   // Note: This is designed to return the screen row #
+   // below the last message line.  To return the exact
+   // bottommost screen row, subtract 1 from gEditLines.length-pTopIndex.
+   var bottommost = (pY + (gEditLines.length-pTopIndex));
+   if (bottomScreenRow > bottommost)
+      bottomScreenRow = bottommost;
+   return bottomScreenRow;
 }
 
 // This function updates the time on the screen and puts
@@ -4428,7 +3994,7 @@ function updateTime(pCurpos, pMoveCursorBack)
       // Display the current time on the screen
       fpDisplayTime(currentTime);
       // Make sure the edit color attribute is set.
-      console.print("\1n" + gTextAttrs);
+      console.print("n" + gTextAttrs);
       // Move the cursor back to where it was
       if (pMoveCursorBack)
          console.gotoxy(curpos);
@@ -4440,6 +4006,7 @@ function updateTime(pCurpos, pMoveCursorBack)
 // This function lets the user change the text color and is called by doEditLoop().
 //
 // Parameters:
+//  pTxtAttrs: The current text color & attributes
 //  pCurpos: An object containing x and y values representing the
 //           cursor position.
 //  pCurrentWordLength: The length of the current word that has been typed
@@ -4449,72 +4016,66 @@ function updateTime(pCurpos, pMoveCursorBack)
 //               x and y: The horizontal and vertical cursor position
 //               timedOut: Whether or not the user input timed out (boolean)
 //               currentWordLength: The length of the current word
-function doColorSelection(pCurpos, pCurrentWordLength)
+function doColorSelection(pTxtAttrs, pCurpos, pCurrentWordLength)
 {
-	// Create the return object
-	var retObj = new Object();
-	retObj.txtAttrs = "";
-	retObj.x = pCurpos.x;
-	retObj.y = pCurpos.y;
-	retObj.timedOut = false;
-	retObj.currentWordLength = pCurrentWordLength;
-
-	const originalScreenY = pCurpos.y; // For screen refreshing
-
-	// Display the 3 rows of color/attribute options and the prompt for the
-	// user
-	var colorSelTopLine = console.screen_rows - 2;
-	var curpos ={
-		x: 1,
-		y: colorSelTopLine
-	};
-	console.gotoxy(curpos);
-	console.print("ncForeground: whK:nkBlack whR:nrRed whG:ngGreen whY:nyYellow whB:nbBlue whM:nmMagenta whC:ncCyan whW:nwWhite");
-	console.cleartoeol("n");
-	console.crlf();
-	console.print("ncBackground: wh0:n0Blackn wh1:n1Redn wh2:n2kGreenn wh3:3Yellown wh4:n4Bluen wh5:n5Magentan wh6:n6kCyann wh7:n7kWhite");
-	console.cleartoeol("n");
-	console.crlf();
-	console.clearline("n");
-	console.print("cSpecial: whH:nhHigh Intensity wI:niBlinking nwhN:nNormal hg� ncChoose colors/attributeshg: c");
-	// Get the attribute codes from the user.  Ideally, we'd use console.getkeys(),
-	// but that outputs a CR at the end, which is undesirable.  So instead, we call
-	// getUserInputWithSetOfInputStrs (defined in SlyEdit_Misc.js).
-	//var key = console.getkeys("KRGYBMCW01234567HIN").toString(); // Outputs a CR..  bad
-	var validKeys = ["KRGYBMCW", // Foreground color codes
-	                 "01234567", // Background color codes
-	                 "HIN"];     // Special color codes
-	var attrCodeKeys = getUserInputWithSetOfInputStrs(K_UPPER|K_NOCRLF|K_NOSPIN, validKeys, gConfigSettings);
-	// If the user entered some attributes, then set them in retObj.txtAttrs.
-	if (attrCodeKeys.length > 0)
-	{
-		retObj.txtAttrs = "";
-		for (var i = 0; i < attrCodeKeys.length; ++i)
-			retObj.txtAttrs += "\1" + attrCodeKeys.charAt(i);
-	}
+   // Create the return object
+   var retObj = new Object();
+   retObj.txtAttrs = pTxtAttrs;
+   retObj.x = pCurpos.x;
+   retObj.y = pCurpos.y;
+   retObj.timedOut = false;
+   retObj.currentWordLength = pCurrentWordLength;
+   
+   const originalScreenY = pCurpos.y; // For screen refreshing
+
+   // Display the 3 rows of color/attribute options and the prompt for the
+   // user
+   var colorSelTopLine = console.screen_rows - 2;
+   var curpos = new Object();
+   curpos.x = 1;
+   curpos.y = colorSelTopLine;
+   console.gotoxy(curpos);
+   console.print("ncForeground: whK:nkBlack whR:nrRed whG:ngGreen whY:nyYellow whB:nbBlue whM:nmMagenta whC:ncCyan whW:nwWhite");
+   console.cleartoeol("n");
+   console.crlf();
+   console.print("ncBackground: wh0:n0Blackn wh1:n1Redn wh2:n2kGreenn wh3:3Yellown wh4:n4Bluen wh5:n5Magentan wh6:n6kCyann wh7:n7kWhite");
+   console.cleartoeol("n");
+   console.crlf();
+   console.clearline("n");
+   console.print("cSpecial: whH:nhHigh Intensity wI:niBlinking nwhN:nNormal hg� ncChoose colors/attributeshg: c");
+   // Get the attribute codes from the user.  Ideally, we'd use console.getkeys(),
+   // but that outputs a CR at the end, which is undesirable.  So instead, we call
+   // getUserInputWithSetOfInputStrs (defined in SlyEdit_Misc.js).
+   //var key = console.getkeys("KRGYBMCW01234567HIN").toString(); // Outputs a CR..  bad
+   var validKeys = ["KRGYBMCW", // Foreground color codes
+                    "01234567", // Background color codes
+                    "HIN"];     // Special color codes
+   var attrCodeKeys = getUserInputWithSetOfInputStrs(K_UPPER|K_NOCRLF|K_NOSPIN, validKeys, gConfigSettings);
+   // If the user entered some attributes, then set them in retObj.txtAttrs.
+   if (attrCodeKeys.length > 0)
+   {
+      retObj.txtAttrs = (attrCodeKeys.charAt(0) == "N" ? "" : "n");
+      for (var i = 0; i < attrCodeKeys.length; ++i)
+         retObj.txtAttrs += "" + attrCodeKeys.charAt(i);
+   }
 
-	// Display the parts of the screen text that we covered up with the
-	// color selection: Message edit lines, bottom border, and bottom help line.
-	var screenYDiff = colorSelTopLine - originalScreenY;
-	displayEditLines(colorSelTopLine, gEditLinesIndex + screenYDiff, gEditBottom, true, true);
-	fpDisplayTextAreaBottomBorder(gEditBottom+1, gUseQuotes, gEditLeft, gEditRight,
-	                              gInsertMode, gConfigSettings.allowColorSelection);
-	// Before displaying the bottom help line, if in DCT mode, clear the bottom
-	// line from the 65th character onwards to erase the attributes prompt text
-	if (EDITOR_STYLE == "DCT")
-	{
-		console.gotoxy(65, console.screen_rows);
-		console.cleartoeol("\1n");
-	}
-	fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes, true);
+   // Display the parts of the screen text that we covered up with the
+   // color selection: Message edit lines, bottom border, and bottom help line.
+   var screenYDiff = colorSelTopLine - originalScreenY;
+   displayEditLines(colorSelTopLine, gEditLinesIndex + screenYDiff, gEditBottom, true, true);
+   fpDisplayTextAreaBottomBorder(gEditBottom+1, gUseQuotes, gEditLeft, gEditRight,
+                                 gInsertMode, gConfigSettings.allowColorSelection);
+   fpDisplayBottomHelpLine(console.screen_rows, gUseQuotes);
 
-	// Move the cursor to where it should be before returning
-	console.gotoxy(pCurpos);
+   // Move the cursor to where it should be before returning
+   curpos.x = pCurpos.x;
+   curpos.y = pCurpos.y;
+   console.gotoxy(curpos);
 
-	// Set the settings in the return object, and return it.
-	retObj.x = pCurpos.x;
-	retObj.y = pCurpos.y;
-	return retObj;
+   // Set the settings in the return object, and return it.
+   retObj.x = curpos.x;
+   retObj.y = curpos.y;
+   return retObj;
 }
 
 // For the cross-posting UI: Draws the initial top border of
@@ -4637,436 +4198,432 @@ function displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight)
 //                   quits out of cross-post selection.
 function doCrossPosting(pOriginalCurpos)
 {
-	// If cross-posting is not allowed, then just return.
-	if (!gCanCrossPost)
-		return;
+  // If cross-posting is not allowed, then just return.
+  if (!gCanCrossPost)
+    return;
 
-	// This function returns the index of the bottommost message group that
-	// can be displayed on the screen.
-	//
-	// Parameters:
-	//  pTopGrpIndex: The index of the topmost message group displayed on screen
-	//  pNumItemsPerPage: The number of items per page
-	function getBottommostGrpIndex(pTopGrpIndex, pNumItemsPerPage)
-	{
-		var bottomGrpIndex = pTopGrpIndex + pNumItemsPerPage - 1;
-		// If bottomGrpIndex is beyond the last index, then adjust it.
-		if (bottomGrpIndex >= msg_area.grp_list.length)
-			bottomGrpIndex = msg_area.grp_list.length - 1;
-		return bottomGrpIndex;
-	}
+  // This function returns the index of the bottommost message group that
+  // can be displayed on the screen.
+  //
+  // Parameters:
+  //  pTopGrpIndex: The index of the topmost message group displayed on screen
+  //  pNumItemsPerPage: The number of items per page
+  function getBottommostGrpIndex(pTopGrpIndex, pNumItemsPerPage)
+  {
+    var bottomGrpIndex = pTopGrpIndex + pNumItemsPerPage - 1;
+    // If bottomGrpIndex is beyond the last index, then adjust it.
+    if (bottomGrpIndex >= msg_area.grp_list.length)
+       bottomGrpIndex = msg_area.grp_list.length - 1;
+    return bottomGrpIndex;
+  }
 
-	// Re-writes the "Choose group" text in the top border of the selection
-	// box.  For use when returning from the sub-board list.
-	//
-	// Parameters:
-	//  pSelBoxUpperLeft: An object containing x and y values for the upper-left
-	//                    corner of the selection box
-	//  pSelBoxInnerWidth: The inner width (inside the left & right borders) of the
-	//                     selection box
-	//  pGrpIndex: The index of message group tht was chosen
-	function reWriteInitialTopBorderText(pSelBoxUpperLeft, pSelBoxInnerWidth, pGrpIndex)
-	{
-		// Position the cursor after the "Cross-posting: " text in the border and
-		// write the "Choose group" text
-		console.gotoxy(pSelBoxUpperLeft.x+17, pSelBoxUpperLeft.y);
-		console.print("\1n" + gConfigSettings.genColors.listBoxBorderText + "Choose group");
-		// Re-write the border characters to overwrite the message group name
-		grpDesc = msg_area.grp_list[pGrpIndex].description.substr(0, pSelBoxInnerWidth-25);
-		// Write the updated border character(s)
-		console.print("\1n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE);
-		if (grpDesc.length > 3)
-		{
-			var numChars = grpDesc.length - 3;
-			for (var i = 0; i < numChars; ++i)
-				console.print(HORIZONTAL_SINGLE);
-		}
-	}
+  // Re-writes the "Choose group" text in the top border of the selection
+  // box.  For use when returning from the sub-board list.
+  //
+  // Parameters:
+  //  pSelBoxUpperLeft: An object containing x and y values for the upper-left
+  //                    corner of the selection box
+  //  pSelBoxInnerWidth: The inner width (inside the left & right borders) of the
+  //                     selection box
+  //  pGrpIndex: The index of message group tht was chosen
+  function reWriteInitialTopBorderText(pSelBoxUpperLeft, pSelBoxInnerWidth, pGrpIndex)
+  {
+    // Position the cursor after the "Cross-posting: " text in the border and
+    // write the "Choose group" text
+    console.gotoxy(pSelBoxUpperLeft.x+17, pSelBoxUpperLeft.y);
+    console.print("n" + gConfigSettings.genColors.listBoxBorderText + "Choose group");
+    // Re-write the border characters to overwrite the message group name
+    grpDesc = msg_area.grp_list[pGrpIndex].description.substr(0, pSelBoxInnerWidth-25);
+    // Write the updated border character(s)
+    console.print("n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE);
+    if (grpDesc.length > 3)
+    {
+      var numChars = grpDesc.length - 3;
+      for (var i = 0; i < numChars; ++i)
+        console.print(HORIZONTAL_SINGLE);
+    }
+  }
 
-	// Store the position of the cursor when we started so that we
-	// can return the cursor back to this position at the end
-	var origStartingCurpos = null;
-	if ((pOriginalCurpos != null) && (typeof(pOriginalCurpos) != "undefined"))
-		origStartingCurpos = pOriginalCurpos;
-	else
-		origStartingCurpos = console.getxy();
-
-	// Construct objects to represent the screen locations of the upper-left
-	// and lower-right corners of the selection box.  Initially, let the box
-	// borders be 1 character into the edit area on all sides.
-	var selBoxUpperLeft = {
-		x: gEditLeft + 3,
-		y: gEditTop + 1
-	};
-	var selBoxLowerRight = {
-		x: gEditRight - 3,
-		y: gEditBottom - 1
-	};
-	// Total and inner text width & height of the selection box
-	var selBoxWidth = selBoxLowerRight.x - selBoxUpperLeft.x + 1;
-	var selBoxHeight = selBoxLowerRight.y - selBoxUpperLeft.y + 1;
-	// Don't let the box's height be more than 17 characters.
-	if (selBoxHeight > 17)
-	{
-		selBoxLowerRight.y = selBoxUpperLeft.y + 16; // For a height of 17 characters
-		selBoxHeight = selBoxLowerRight.y - selBoxUpperLeft.y + 1;
-	}
-	// Inner size of the box (for text)
-	var selBoxInnerWidth = selBoxWidth - 2;
-	var selBoxInnerHeight = selBoxHeight - 2;
-
-	// Calculate the index of the message line at the top of the edit area, which
-	// which is where the message area list box will start.  We need to store
-	// this so that we can erase the selection box when the user is done
-	// selecting a message area.  We'll erase the box by re-writing the message
-	// text.
-	var editLineIndexAtSelBoxTopRow = gEditLinesIndex - (origStartingCurpos.y-selBoxUpperLeft.y);
-
-	// Variables for keeping track of the message group/area list
-	var topMsgGrpIndex = 0;    // The index of the message group at the top of the list
-	// Figure out the index of the last message group to appear on the screen.
-	var bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, selBoxInnerHeight);
-	var numPages = Math.ceil(msg_area.grp_list.length / selBoxInnerHeight);
-	var numItemsPerPage = selBoxInnerHeight;
-	var topIndexForLastPage = (selBoxInnerHeight * numPages) - selBoxInnerHeight;
-	// msgGrpFieldLen will store the length to use for the message group numbers
-	// in the list.  It should be able to accommodate the highest message group
-	// number on the system.
-	var msgGrpFieldLen = msg_area.grp_list.length.toString().length;
-
-	var selectedGrpIndex = 0; // The currently-selected group index
-
-	// Draw the selection box borders
-	// Top border
-	drawInitialCrossPostSelBoxTopBorder(selBoxUpperLeft, selBoxWidth,
-	                                    gConfigSettings.genColors.listBoxBorder,
-	                                    gConfigSettings.genColors.listBoxBorderText);
-	// Side borders
-	console.print(UPPER_RIGHT_SINGLE);
-	for (var row = selBoxUpperLeft.y+1; row < selBoxLowerRight.y; ++row)
-	{
-		console.gotoxy(selBoxUpperLeft.x, row);
-		console.print(VERTICAL_SINGLE);
-		console.gotoxy(selBoxLowerRight.x, row);
-		console.print(VERTICAL_SINGLE);
-	}
-	// Bottom border
-	drawInitialCrossPostSelBoxBottomBorder({ x: selBoxUpperLeft.x, y: selBoxLowerRight.y },
-	                                       selBoxWidth, gConfigSettings.genColors.listBoxBorder,
-	                                       false);
-
-	// Write the message groups
-	var pageNum = 1;
-	ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-	                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
-	                       msgGrpFieldLen, true);
-	// Move the cursor to the inner upper-left corner of the selection box
-	var curpos = { // Current cursor position
-		x: selBoxUpperLeft.x+1,
-		y: selBoxUpperLeft.y+1
-	};
-	console.gotoxy(curpos);
+  // Store the position of the cursor when we started so that we
+  // can return the cursor back to this position at the end
+  var origStartingCurpos = null;
+  if ((pOriginalCurpos != null) && (typeof(pOriginalCurpos) != "undefined"))
+     origStartingCurpos = pOriginalCurpos;
+  else
+     origStartingCurpos = console.getxy();
+
+  // Construct objects to represent the screen locations of the upper-left
+  // and lower-right corners of the selection box.  Initially, let the box
+  // borders be 1 character into the edit area on all sides.
+  var selBoxUpperLeft = new Object();
+  selBoxUpperLeft.x = gEditLeft + 3;
+  selBoxUpperLeft.y = gEditTop + 1;
+  var selBoxLowerRight = new Object();
+  selBoxLowerRight.x = gEditRight - 3;
+  selBoxLowerRight.y = gEditBottom - 1;
+  // Total and inner text width & height of the selection box
+  var selBoxWidth = selBoxLowerRight.x - selBoxUpperLeft.x + 1;
+  var selBoxHeight = selBoxLowerRight.y - selBoxUpperLeft.y + 1;
+  // Don't let the box's height be more than 17 characters.
+  if (selBoxHeight > 17)
+  {
+    selBoxLowerRight.y = selBoxUpperLeft.y + 16; // For a height of 17 characters
+    selBoxHeight = selBoxLowerRight.y - selBoxUpperLeft.y + 1;
+  }
+  // Inner size of the box (for text)
+  var selBoxInnerWidth = selBoxWidth - 2;
+  var selBoxInnerHeight = selBoxHeight - 2;
 
-	// User input loop
-	var userInput = null;
-	var continueChoosingMsgAreaName = true;
-	while (continueChoosingMsgAreaName)
-	{
-		pageNum = calcPageNum(topMsgGrpIndex, selBoxInnerHeight);
+  // Calculate the index of the message line at the top of the edit area, which
+  // which is where the message area list box will start.  We need to store
+  // this so that we can erase the selection box when the user is done
+  // selecting a message area.  We'll erase the box by re-writing the message
+  // text.
+  var editLineIndexAtSelBoxTopRow = gEditLinesIndex - (origStartingCurpos.y-selBoxUpperLeft.y);
 
-		// Get a key from the user (upper-case) and take action based upon it.
-		userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings);
-		switch (userInput)
-		{
-			case KEY_UP: // Move up one message group in the list
-				if (selectedGrpIndex > 0)
-				{
-					// If the previous group index is on the previous page, then
-					// display the previous page.
-					var previousGrpIndex = selectedGrpIndex - 1;
-					if (previousGrpIndex < topMsgGrpIndex)
-					{
-						// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
-						// refresh the list on the screen.
-						topMsgGrpIndex -= numItemsPerPage;
-						bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
-						ListScreenfulOfMsgGrps(topMsgGrpIndex, previousGrpIndex, selBoxUpperLeft.y+1,
-						                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
-						                       msgGrpFieldLen, true);
-						// We'll want to move the cursor to the leftmost character
-						// of the selected line.
-						curpos.x = selBoxUpperLeft.x+1;
-						curpos.y = selBoxUpperLeft.y+selBoxInnerHeight;
-					}
-					else
-					{
-						// Display the current line un-highlighted
-						console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
-						writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
-						// Display the previous line highlighted
-						curpos.x = selBoxUpperLeft.x+1;
-						--curpos.y;
-						console.gotoxy(curpos);
-						writeMsgGroupLine(previousGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
-					}
-					selectedGrpIndex = previousGrpIndex;
-					console.gotoxy(curpos); // Move the cursor into place where it should be
-				}
-				break;
-			case KEY_DOWN: // Move down one message group in the list
-				if (selectedGrpIndex < msg_area.grp_list.length - 1)
-				{
-					// If the next group index is on the next page, then display
-					// the next page.
-					var nextGrpIndex = selectedGrpIndex + 1;
-					if (nextGrpIndex > bottomMsgGrpIndex)
-					{
-						// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
-						// refresh the list on the screen.
-						topMsgGrpIndex += numItemsPerPage;
-						bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
-						ListScreenfulOfMsgGrps(topMsgGrpIndex, nextGrpIndex, selBoxUpperLeft.y+1,
-						                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
-						                       msgGrpFieldLen, true);
-						// We'll want to move the cursor to the leftmost character
-						// of the selected line.
-						curpos.x = selBoxUpperLeft.x+1;
-						curpos.y = selBoxUpperLeft.y+1;
-					}
-					else
-					{
-						// Display the current line un-highlighted
-						console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
-						writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
-						// Display the next line highlighted
-						curpos.x = selBoxUpperLeft.x+1;
-						++curpos.y;
-						console.gotoxy(curpos);
-						writeMsgGroupLine(nextGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
-					}
-					selectedGrpIndex = nextGrpIndex;
-					console.gotoxy(curpos); // Move the cursor into place where it should be
-				}
-				break;
-			case KEY_HOME: // Go to the top message group on the screen
-				if (selectedGrpIndex > topMsgGrpIndex)
-				{
-					// Display the current line un-highlighted, adjust
-					// selectedGrpIndex, then display the new line
-					// highlighted.
-					console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
-					writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
-					selectedGrpIndex = topMsgGrpIndex;
-					curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
-					console.gotoxy(curpos);
-					writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
-					console.gotoxy(curpos);
-				}
-				break;
-			case KEY_END: // Go to the bottom message group on the screen
-				if (selectedGrpIndex < bottomMsgGrpIndex)
-				{
-					// Display the current line un-highlighted, adjust
-					// selectedGrpIndex, then display the new line
-					// highlighted.
-					console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
-					writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
-					selectedGrpIndex = bottomMsgGrpIndex;
-					curpos.x = selBoxUpperLeft.x + 1;
-					curpos.y = selBoxUpperLeft.y + (bottomMsgGrpIndex-topMsgGrpIndex+1);
-					console.gotoxy(curpos);
-					writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
-					console.gotoxy(curpos);
-				}
-				break;
-			case KEY_PAGE_DOWN: // Go to the next page
-				var nextPageTopIndex = topMsgGrpIndex + numItemsPerPage;
-				if (nextPageTopIndex < msg_area.grp_list.length)
-				{
-					// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
-					// refresh the list on the screen.
-					topMsgGrpIndex = nextPageTopIndex;
-					pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
-					bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
-					selectedGrpIndex = topMsgGrpIndex;
-					ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-					                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
-					                       msgGrpFieldLen, true);
-					// Put the cursor at the beginning of the topmost row of message groups
-					curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
-					console.gotoxy(curpos);
-				}
-				break;
-			case KEY_PAGE_UP: // Go to the previous page
-				var prevPageTopIndex = topMsgGrpIndex - numItemsPerPage;
-				if (prevPageTopIndex >= 0)
-				{
-					// Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
-					// refresh the list on the screen.
-					topMsgGrpIndex = prevPageTopIndex;
-					pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
-					bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
-					selectedGrpIndex = topMsgGrpIndex;
-					ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-					                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
-					                       msgGrpFieldLen, true);
-					// Put the cursor at the beginning of the topmost row of message groups
-					curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
-					console.gotoxy(curpos);
-				}
-				break;
-			case 'F': // Go to the first page
-				if (topMsgGrpIndex > 0)
-				{
-					topMsgGrpIndex = 0;
-					pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
-					bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
-					selectedGrpIndex = 0;
-					ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-					                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
-					                       msgGrpFieldLen, true);
-					// Put the cursor at the beginning of the topmost row of message groups
-					curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
-					console.gotoxy(curpos);
-				}
-				break;
-			case 'L': // Go to the last page
-				if (topMsgGrpIndex < topIndexForLastPage)
-				{
-					topMsgGrpIndex = topIndexForLastPage;
-					pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
-					bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
-					selectedGrpIndex = topIndexForLastPage;
-					ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-					                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
-					                       msgGrpFieldLen, true);
-					// Put the cursor at the beginning of the topmost row of message groups
-					curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
-					console.gotoxy(curpos);
-				}
-				break;
-			case CTRL_C:  // Quit (Ctrl-C is the cross-post hotkey)
-			case KEY_ESC: // Quit
-			case "Q":     // Quit
-				continueChoosingMsgAreaName = false;
-				break;
-			case KEY_ENTER: // Select the currently-highlighted message group
-				// Store the current cursor position for later, then show the
-				// sub-boards in the chosen message group and let the user
-				// toggle ones for cross-posting.
-				var selectCurrentGrp_originalCurpos = curpos;
-				var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(selectedGrpIndex,
-				                                                           selBoxUpperLeft, selBoxLowerRight, selBoxWidth,
-				                                                           selBoxHeight, selBoxInnerWidth, selBoxInnerHeight);
-				// If the user toggled some sub-boards...
-				if (selectMsgAreaRetObj.subBoardsToggled)
-				{
-					// TODO: Does anything need to be done here?
-				}
+  // Variables for keeping track of the message group/area list
+  var topMsgGrpIndex = 0;    // The index of the message group at the top of the list
+  // Figure out the index of the last message group to appear on the screen.
+  var bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, selBoxInnerHeight);
+  var numPages = Math.ceil(msg_area.grp_list.length / selBoxInnerHeight);
+  var numItemsPerPage = selBoxInnerHeight;
+  var topIndexForLastPage = (selBoxInnerHeight * numPages) - selBoxInnerHeight;
+  // msgGrpFieldLen will store the length to use for the message group numbers
+  // in the list.  It should be able to accommodate the highest message group
+  // number on the system.
+  var msgGrpFieldLen = msg_area.grp_list.length.toString().length;
+
+  var selectedGrpIndex = 0; // The currently-selected group index
+
+  // Draw the selection box borders
+  // Top border
+  drawInitialCrossPostSelBoxTopBorder(selBoxUpperLeft, selBoxWidth,
+                                      gConfigSettings.genColors.listBoxBorder,
+                                      gConfigSettings.genColors.listBoxBorderText);
+  // Side borders
+  console.print(UPPER_RIGHT_SINGLE);
+  for (var row = selBoxUpperLeft.y+1; row < selBoxLowerRight.y; ++row)
+  {
+    console.gotoxy(selBoxUpperLeft.x, row);
+    console.print(VERTICAL_SINGLE);
+    console.gotoxy(selBoxLowerRight.x, row);
+    console.print(VERTICAL_SINGLE);
+  }
+  // Bottom border
+  drawInitialCrossPostSelBoxBottomBorder({ x: selBoxUpperLeft.x, y: selBoxLowerRight.y },
+                                         selBoxWidth, gConfigSettings.genColors.listBoxBorder,
+                                         false);
+
+  // Write the message groups
+  var pageNum = 1;
+  ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                         selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
+                         msgGrpFieldLen, true);
+  // Move the cursor to the inner upper-left corner of the selection box
+  var curpos = new Object(); // Current cursor position
+  curpos.x = selBoxUpperLeft.x+1;
+  curpos.y = selBoxUpperLeft.y+1;
+  console.gotoxy(curpos);
+
+  // User input loop
+  var userInput = null;
+  var continueChoosingMsgArea = true;
+  while (continueChoosingMsgArea)
+  {
+    pageNum = calcPageNum(topMsgGrpIndex, selBoxInnerHeight);
+
+    // Get a key from the user (upper-case) and take action based upon it.
+    userInput = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings);
+    switch (userInput)
+    {
+      case KEY_UP: // Move up one message group in the list
+         if (selectedGrpIndex > 0)
+         {
+            // If the previous group index is on the previous page, then
+            // display the previous page.
+            var previousGrpIndex = selectedGrpIndex - 1;
+            if (previousGrpIndex < topMsgGrpIndex)
+            {
+               // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
+               // refresh the list on the screen.
+               topMsgGrpIndex -= numItemsPerPage;
+               bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
+               ListScreenfulOfMsgGrps(topMsgGrpIndex, previousGrpIndex, selBoxUpperLeft.y+1,
+                         selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
+                         msgGrpFieldLen, true);
+               // We'll want to move the cursor to the leftmost character
+               // of the selected line.
+               curpos.x = selBoxUpperLeft.x+1;
+               curpos.y = selBoxUpperLeft.y+selBoxInnerHeight;
+            }
+            else
+            {
+               // Display the current line un-highlighted
+               console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
+               writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
+               // Display the previous line highlighted
+               curpos.x = selBoxUpperLeft.x+1;
+               --curpos.y;
+               console.gotoxy(curpos);
+               writeMsgGroupLine(previousGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
+            }
+            selectedGrpIndex = previousGrpIndex;
+            console.gotoxy(curpos); // Move the cursor into place where it should be
+         }
+         break;
+      case KEY_DOWN: // Move down one message group in the list
+         if (selectedGrpIndex < msg_area.grp_list.length - 1)
+         {
+            // If the next group index is on the next page, then display
+            // the next page.
+            var nextGrpIndex = selectedGrpIndex + 1;
+            if (nextGrpIndex > bottomMsgGrpIndex)
+            {
+               // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
+               // refresh the list on the screen.
+               topMsgGrpIndex += numItemsPerPage;
+               bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
+               ListScreenfulOfMsgGrps(topMsgGrpIndex, nextGrpIndex, selBoxUpperLeft.y+1,
+                         selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
+                         msgGrpFieldLen, true);
+               // We'll want to move the cursor to the leftmost character
+               // of the selected line.
+               curpos.x = selBoxUpperLeft.x+1;
+               curpos.y = selBoxUpperLeft.y+1;
+            }
+            else
+            {
+               // Display the current line un-highlighted
+               console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
+               writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
+               // Display the next line highlighted
+               curpos.x = selBoxUpperLeft.x+1;
+               ++curpos.y;
+               console.gotoxy(curpos);
+               writeMsgGroupLine(nextGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
+            }
+            selectedGrpIndex = nextGrpIndex;
+            console.gotoxy(curpos); // Move the cursor into place where it should be
+         }
+         break;
+      case KEY_HOME: // Go to the top message group on the screen
+         if (selectedGrpIndex > topMsgGrpIndex)
+         {
+            // Display the current line un-highlighted, adjust
+            // selectedGrpIndex, then display the new line
+            // highlighted.
+            console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
+            writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
+            selectedGrpIndex = topMsgGrpIndex;
+            curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
+            console.gotoxy(curpos);
+            writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
+            console.gotoxy(curpos);
+         }
+         break;
+      case KEY_END: // Go to the bottom message group on the screen
+         if (selectedGrpIndex < bottomMsgGrpIndex)
+         {
+            // Display the current line un-highlighted, adjust
+            // selectedGrpIndex, then display the new line
+            // highlighted.
+            console.gotoxy(selBoxUpperLeft.x+1, curpos.y);
+            writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, false);
+            selectedGrpIndex = bottomMsgGrpIndex;
+            curpos.x = selBoxUpperLeft.x + 1;
+            curpos.y = selBoxUpperLeft.y + (bottomMsgGrpIndex-topMsgGrpIndex+1);
+            console.gotoxy(curpos);
+            writeMsgGroupLine(selectedGrpIndex, selBoxInnerWidth, msgGrpFieldLen, true);
+            console.gotoxy(curpos);
+         }
+         break;
+      case KEY_PAGE_DOWN: // Go to the next page
+         var nextPageTopIndex = topMsgGrpIndex + numItemsPerPage;
+         if (nextPageTopIndex < msg_area.grp_list.length)
+         {
+            // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
+            // refresh the list on the screen.
+            topMsgGrpIndex = nextPageTopIndex;
+            pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
+            bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
+            selectedGrpIndex = topMsgGrpIndex;
+            ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                         selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
+                         msgGrpFieldLen, true);
+            // Put the cursor at the beginning of the topmost row of message groups
+            curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
+            console.gotoxy(curpos);
+         }
+         break;
+      case KEY_PAGE_UP: // Go to the previous page
+         var prevPageTopIndex = topMsgGrpIndex - numItemsPerPage;
+         if (prevPageTopIndex >= 0)
+         {
+            // Adjust topMsgGrpIndex and bottomMsgGrpIndex, and
+            // refresh the list on the screen.
+            topMsgGrpIndex = prevPageTopIndex;
+            pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
+            bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
+            selectedGrpIndex = topMsgGrpIndex;
+            ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                         selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
+                         msgGrpFieldLen, true);
+            // Put the cursor at the beginning of the topmost row of message groups
+            curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
+            console.gotoxy(curpos);
+         }
+         break;
+      case 'F': // Go to the first page
+         if (topMsgGrpIndex > 0)
+         {
+            topMsgGrpIndex = 0;
+            pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
+            bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
+            selectedGrpIndex = 0;
+            ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                         selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
+                         msgGrpFieldLen, true);
+            // Put the cursor at the beginning of the topmost row of message groups
+            curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
+            console.gotoxy(curpos);
+         }
+         break;
+      case 'L': // Go to the last page
+         if (topMsgGrpIndex < topIndexForLastPage)
+         {
+            topMsgGrpIndex = topIndexForLastPage;
+            pageNum = calcPageNum(topMsgGrpIndex, numItemsPerPage);
+            bottomMsgGrpIndex = getBottommostGrpIndex(topMsgGrpIndex, numItemsPerPage);
+            selectedGrpIndex = topIndexForLastPage;
+            ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                         selBoxUpperLeft.x+1, selBoxLowerRight.y-1, selBoxLowerRight.x-1,
+                         msgGrpFieldLen, true);
+            // Put the cursor at the beginning of the topmost row of message groups
+            curpos = { x: selBoxUpperLeft.x+1, y: selBoxUpperLeft.y+1 };
+            console.gotoxy(curpos);
+         }
+         break;
+      case CTRL_C:  // Quit (Ctrl-C is the cross-post hotkey)
+      case KEY_ESC: // Quit
+      case "Q":     // Quit
+         continueChoosingMsgArea = false;
+         break;
+      case KEY_ENTER: // Select the currently-highlighted message group
+         // Store the current cursor position for later, then show the
+         // sub-boards in the chosen message group and let the user
+         // toggle ones for cross-posting.
+         var selectCurrentGrp_originalCurpos = curpos;
+         var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(selectedGrpIndex,
+                                               selBoxUpperLeft, selBoxLowerRight, selBoxWidth,
+                                               selBoxHeight, selBoxInnerWidth, selBoxInnerHeight);
+         // If the user toggled some sub-boards...
+         if (selectMsgAreaRetObj.subBoardsToggled)
+         {
+            // TODO: Does anything need to be done here?
+         }
 
-				// Update the Enter action text in the bottom border to say "Select"
-				// (instead of "Toggle").
-				console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y);
-				console.print("\1n\1h\1bSelect");
-				// Refresh the top border of the selection box, refresh the list of
-				// message groups in the box, and move the cursor back to its original
-				// position.
-				reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, selectedGrpIndex);
-				ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-				                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
-				                       selBoxLowerRight.x-1, msgGrpFieldLen, true);
-				console.gotoxy(selectCurrentGrp_originalCurpos);
-				break;
-			case '?': // Display cross-post help
-				displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight);
-				console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y-1);
-				console.pause();
-				ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-				                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
-				                       selBoxLowerRight.x-1, msgGrpFieldLen, true);
-				console.gotoxy(curpos);
-				break;
-			default:
-				// If the user entered a numeric digit, then treat it as
-				// the start of the message group number.
-				if (userInput.match(/[0-9]/))
-				{
-					var originalCurpos = curpos;
-					// Put the user's input back in the input buffer to
-					// be used for getting the rest of the message number.
-					console.ungetstr(userInput);
-					// We want to write the prompt text only if the first digit entered
-					// by the user is an ambiguous message group number (i.e., if
-					// the first digit is 2 and there's a message group # 2 and 20).
-					var writePromptText = (msg_area.grp_list.length >= +userInput * 10);
-					if (writePromptText)
-					{
-						console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
-						printf("\1n\1cChoose group #:%" + +(selBoxInnerWidth-15) + "s", "");
-						console.gotoxy(selBoxUpperLeft.x+17, selBoxLowerRight.y);
-						console.print("\1h");
-					}
-					else
-						console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
-					userInput = console.getnum(msg_area.grp_list.length);
+         // Update the Enter action text in the bottom border to say "Select"
+         // (instead of "Toggle").
+         console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y);
+         console.print("nhbSelect");
+         // Refresh the top border of the selection box, refresh the list of
+         // message groups in the box, and move the cursor back to its original
+         // position.
+         reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, selectedGrpIndex);
+         ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                                selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
+                                selBoxLowerRight.x-1, msgGrpFieldLen, true);
+         console.gotoxy(selectCurrentGrp_originalCurpos);
+         break;
+      case '?': // Display cross-post help
+         displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight);
+         console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y-1);
+         console.pause();
+         ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                                selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
+                                selBoxLowerRight.x-1, msgGrpFieldLen, true);
+         console.gotoxy(curpos);
+         break;
+      default:
+         // If the user entered a numeric digit, then treat it as
+         // the start of the message group number.
+         if (userInput.match(/[0-9]/))
+         {
+            var originalCurpos = curpos;
+            // Put the user's input back in the input buffer to
+            // be used for getting the rest of the message number.
+            console.ungetstr(userInput);
+            // We want to write the prompt text only if the first digit entered
+            // by the user is an ambiguous message group number (i.e., if
+            // the first digit is 2 and there's a message group # 2 and 20).
+            var writePromptText = (msg_area.grp_list.length >= +userInput * 10);
+            if (writePromptText)
+            {
+              console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
+              printf("ncChoose group #:%" + +(selBoxInnerWidth-15) + "s", "");
+              console.gotoxy(selBoxUpperLeft.x+17, selBoxLowerRight.y);
+              console.print("h");
+            }
+            else
+              console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
+            userInput = console.getnum(msg_area.grp_list.length);
 
-					// Re-draw the bottom border of the selection box
-					if (writePromptText)
-					{
-						drawInitialCrossPostSelBoxBottomBorder({ x: selBoxUpperLeft.x, y: selBoxLowerRight.y },
-						                                       selBoxWidth, gConfigSettings.genColors.listBoxBorder,
-						                                       false);
-					}
-					else
-					{
-						console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
-						console.print(gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE);
-					}
+            // Re-draw the bottom border of the selection box
+            if (writePromptText)
+            {
+              drawInitialCrossPostSelBoxBottomBorder({ x: selBoxUpperLeft.x, y: selBoxLowerRight.y },
+                                                     selBoxWidth, gConfigSettings.genColors.listBoxBorder,
+                                                     false);
+            }
+            else
+            {
+              console.gotoxy(selBoxUpperLeft.x+1, selBoxLowerRight.y);
+              console.print(gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE);
+            }
 
-					// If the user made a selection, then let them choose a
-					// sub-board from the group.
-					if (userInput > 0)
-					{
-						// Show the sub-boards in the chosen message group and
-						// let the user toggle ones for cross-posting.
-						// userInput-1 is the group index
-						var chosenGrpIndex = userInput - 1;
-						var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(chosenGrpIndex,
-						selBoxUpperLeft, selBoxLowerRight, selBoxWidth,
-						selBoxHeight, selBoxInnerWidth, selBoxInnerHeight);
-						// If the user chose a sub-board, then set bbs.curgrp and
-						// bbs.cursub, and don't continue the input loop anymore.
-						if (selectMsgAreaRetObj.subBoardsToggled)
-						{
-							// TODO: Does anything need to be done here?
-						}
-						// Update the Enter action text in the bottom border to say "Select"
-						// (instead of "Toggle").
-						console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y);
-						console.print("\1n\1h\1bSelect");
-						// Refresh the top border of the selection box
-						reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, chosenGrpIndex);
-					}
+            // If the user made a selection, then let them choose a
+            // sub-board from the group.
+            if (userInput > 0)
+            {
+               // Show the sub-boards in the chosen message group and
+               // let the user toggle ones for cross-posting.
+               // userInput-1 is the group index
+               var chosenGrpIndex = userInput - 1;
+               var selectMsgAreaRetObj = crossPosting_selectSubBoardInGrp(chosenGrpIndex,
+                                               selBoxUpperLeft, selBoxLowerRight, selBoxWidth,
+                                               selBoxHeight, selBoxInnerWidth, selBoxInnerHeight);
+               // If the user chose a sub-board, then set bbs.curgrp and
+               // bbs.cursub, and don't continue the input loop anymore.
+               if (selectMsgAreaRetObj.subBoardsToggled)
+               {
+                  // TODO: Does anything need to be done here?
+               }
+               // Update the Enter action text in the bottom border to say "Select"
+               // (instead of "Toggle").
+               console.gotoxy(selBoxUpperLeft.x+41, selBoxLowerRight.y);
+               console.print("nhbSelect");
+               // Refresh the top border of the selection box
+               reWriteInitialTopBorderText(selBoxUpperLeft, selBoxInnerWidth, chosenGrpIndex);
+            }
 
-					// Refresh the list of message groups in the box and move the
-					// cursor back to its original position.
-					ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
-					                       selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
-					                       selBoxLowerRight.x-1, msgGrpFieldLen, true);
-					console.gotoxy(originalCurpos);
-				}
-				break;
-		}
-	}
+            // Refresh the list of message groups in the box and move the
+            // cursor back to its original position.
+            ListScreenfulOfMsgGrps(topMsgGrpIndex, selectedGrpIndex, selBoxUpperLeft.y+1,
+                                   selBoxUpperLeft.x+1, selBoxLowerRight.y-1,
+                                   selBoxLowerRight.x-1, msgGrpFieldLen, true);
+            console.gotoxy(originalCurpos);
+         }
+         break;
+    }
+  }
 
-	// We're done selecting message areas for cross-posting.
-	// Erase the message area selection rectangle by re-drawing the message text.
-	// Then, move the cursor back to where it was when we started the message
-	// area selection.
-	displayMessageRectangle(selBoxUpperLeft.x, selBoxUpperLeft.y, selBoxWidth,
-	                        selBoxHeight, editLineIndexAtSelBoxTopRow, true);
-	console.gotoxy(origStartingCurpos);
-	console.print(chooseEditColor());
+  // We're done selecting message areas for cross-posting.
+  // Erase the message area selection rectangle by re-drawing the message text.
+  // Then, move the cursor back to where it was when we started the message
+  // area selection.
+  displayMessageRectangle(selBoxUpperLeft.x, selBoxUpperLeft.y, selBoxWidth,
+                          selBoxHeight, editLineIndexAtSelBoxTopRow, true);
+  console.gotoxy(origStartingCurpos);
 }
 // Displays a screenful of message groups, for the cross-posting
 // interface.
@@ -5186,7 +4743,7 @@ function writeMsgGroupLine(pGrpIndex, pTextWidth, pMsgGrpFieldLen, pHighlight)
    }
 
    // Write the message group information line
-   var markChar = (pGrpIndex == gMsgAreaNameInfo.grpIndex ? "*" : " ");
+   var markChar = (pGrpIndex == gMsgAreaInfo.grpIndex ? "*" : " ");
    printf(printfStr, markChar, +(pGrpIndex+1), msg_area.grp_list[pGrpIndex].description.substr(0, grpDescLen));
 }
 // For cross-posting: Lets the user choose a sub-board within a message group
@@ -5785,7 +5342,7 @@ function writeMsgSubLine(pGrpIndex, pSubIndex, pTextWidth, pSubNumFieldLen, pHig
 
    // Put together the printf format string
    var msgSubDescLen = pTextWidth - pSubNumFieldLen - 2;
-   var printfStr = "\1n";
+   var printfStr = "n";
    if (pHighlight)
    {
      printfStr += gConfigSettings.genColors.crossPostChkHighlight + "%1s"
@@ -5808,284 +5365,287 @@ function writeMsgSubLine(pGrpIndex, pSubIndex, pTextWidth, pSubNumFieldLen, pHig
 // Writes a line in the edit lines array
 //
 // Parameters:
-//  pEditLineIdx: Integer - The index of the line to write. Required.
+//  pIndex: Integer - The index of the line to write. Required.
+//  pUseColors: Boolean - Whether or not to use the line's colors.
+//              Optional.  If omitted, the colors will be used.
 //  pStart: Integer - The index in the line of where to start.
 //          Optional.  If omitted, 0 will be used.
 //  pLength: Integer - The length to write.  Optional.  If
 //           omitted, the entire line will be written.  <= 0 can be
 //           passed to write the entire string.
 //
-// Return value: The actual length of text written as appearing on the screen
-function printEditLine(pEditLineIdx, pStart, pLength)
+// Return value: The actual length of text written
+function printEditLine(pIndex, pUseColors, pStart, pLength)
 {
-	if (typeof(pEditLineIdx) != "number")
-		return 0;
-	var start = (typeof(pStart) == "number" ? pStart : 0);
-	var length = (typeof(pLength) == "number" ? pLength : -1);
-	// Validation of variable values
-	if (pEditLineIdx < 0)
-		pEditLineIdx = 0;
-	else if (pEditLineIdx >= gEditLines.length)
-	{
-		// Before returning, write spaces for the length specified so
-		// that the screen is updated correctly
-		printf("\1n%" + length + "s", "");
-		return length;
-	}
-	if (start < 0)
-		start = 0;
-	else if (start >= gEditLines[pEditLineIdx].text.length)
-	{
-		// Before returning, write spaces for the length specified so
-		// that the screen is updated correctly
-		printf("\1n%" + length + "s", "");
-		return length;
-	}
-	//if (length > (gEditLines[pEditLineIdx].text.length - start))
-	//   length = gEditLines[pEditLineIdx].text.length - start;
-
-	if (gEditLines[pEditLineIdx].isQuoteLine)
-		console.print("\1n" + gQuoteLineColor);
-
-	var lengthWritten = 0;
-	// Cases where the start index is at the beginning of the line
-	if (start == 0)
-	{
-		// Simplest case: start is 0 and length is negative -
-		// Just print the entire line.
-		if (length <= 0)
-		{
-			console.print(gEditLines[pEditLineIdx].text);
-			lengthWritten = strip_ctrl(gEditLines[pEditLineIdx].text).length;
-		}
-		else
-		{
-			var textToWrite = gEditLines[pEditLineIdx].substrWithSyncColorCodes(start, length).strSub;
-			if (gEditLines[pEditLineIdx].isQuoteLine)
-				console.print(strip_ctrl(textToWrite));
-			else
-				console.print(textToWrite);
-			lengthWritten = strip_ctrl(textToWrite).length;
-		}
-	}
-	else
-	{
-		// Start is > 0
-		var textToWrite = "";
-		if (length <= 0)
-			textToWrite = gEditLines[pEditLineIdx].substrWithSyncColorCodes(start).strSub;
-		else
-			textToWrite = gEditLines[pEditLineIdx].substrWithSyncColorCodes(start, length).strSub;
-		if (gEditLines[pEditLineIdx].isQuoteLine)
-			console.print(strip_ctrl(textToWrite));
-		else
-		{
-			var firstAttrCodes = gEditLines[pEditLineIdx].getLastAttrCodes(start);
-			console.print(firstAttrCodes + textToWrite);
-		}
-		lengthWritten = strip_ctrl(textToWrite).length;
-	}
+   if (typeof(pIndex) != "number")
+      return 0;
+   var useColors = true;
+   var start = 0;
+   var length = -1;
+   if (typeof(pUseColors) == "boolean")
+      useColors = pUseColors;
+   if (typeof(pStart) == "number")
+      start = pStart;
+   if (typeof(pLength) == "number")
+      length = pLength;
+   // Validation of variable values
+   if (pIndex < 0)
+      pIndex = 0;
+   else if (pIndex >= gEditLines.length)
+   {
+      // Before returning, write spaces for the length specified so
+      // that the screen is updated correctly
+      for (var i = 0; i < length; ++i)
+         console.print(" ");
+      return length;
+   }
+   if (start < 0)
+      start = 0;
+   else if (start >= gEditLines[pIndex].text.length)
+   {
+      // Before returning, write spaces for the length specified so
+      // that the screen is updated correctly
+      for (var i = 0; i < length; ++i)
+         console.print(" ");
+      return length;
+   }
+   //if (length > (gEditLines[pIndex].text.length - start))
+   //   length = gEditLines[pIndex].text.length - start;
 
-	return lengthWritten;
+   var lengthWritten = 0;
+   if (useColors)
+   {
+   }
+   else
+   {
+      // Don't use the line colors
+      // Cases where the start index is at the beginning of the line
+      if (start == 0)
+      {
+         // Simplest case: start is 0 and length is negative -
+         // Just print the entire line.
+         lengthWritten = gEditLines[pIndex].text.length;
+         if (length <= 0)
+            console.print(gEditLines[pIndex].text);
+         else
+         {
+            var textToWrite = gEditLines[pIndex].text.substr(start, length);
+            console.print(textToWrite);
+            lengthWritten = textToWrite.length;
+         }
+      }
+      else
+      {
+         // Start is > 0
+         var textToWrite = "";
+         if (length <= 0)
+            textToWrite = gEditLines[pIndex].text.substr(start);
+         else
+            textToWrite = gEditLines[pIndex].text.substr(start, length);
+         console.print(textToWrite);
+         lengthWritten = textToWrite.length;
+      }
+   }
+   return lengthWritten;
 }
 
 // Lists the text replacements configured in SlyEdit using a scrollable list box.
 function listTextReplacements()
 {
-	if (gNumTxtReplacements == 0)
-	{
-		var originalCurpos = console.getxy();
-		writeMsgOntBtmHelpLineWithPause("\1n\1h\1yThere are no text replacements.", ERRORMSG_PAUSE_MS);
-		console.print(chooseEditColor()); // Make sure the edit color is correct
-		console.gotoxy(originalCurpos);
-		return;
-	}
+   if (gNumTxtReplacements == 0)
+   {
+      var originalCurpos = console.getxy();
+      writeMsgOntBtmHelpLineWithPause("nhyThere are no text replacements.", ERRORMSG_PAUSE_MS);
+      console.print(chooseEditColor()); // Make sure the edit color is correct
+      console.gotoxy(originalCurpos);
+      return;
+   }
 
-	// Calculate the text width for each column, which will then be used to
-	// calculate the width of the box.  For the width of the box, we need to
-	// subtract at least 3 from the edit area with to accomodate the box's side
-	// borders and the space between the text columns.
-	var txtWidth = Math.floor((gEditWidth - 10)/2);
-
-	// In order to be able to navigate forward and backwards through the text
-	// replacements, we need to copy them into an array, since gTxtReplacements
-	// is an object and not navigable both ways.  This will also allow us to easily
-	// know how many text replacements there are (using the .length property of
-	// the array).
-	// For speed, create this only once.
-	if (typeof(listTextReplacements.txtReplacementArr) == "undefined")
-	{
-		var txtReplacementObj = null;
-		listTextReplacements.txtReplacementArr = new Array();
-		for (var prop in gTxtReplacements)
-		{
-			txtReplacementObj = new Object();
-			txtReplacementObj.originalText = prop;
-			txtReplacementObj.replacement = gTxtReplacements[prop];
-			listTextReplacements.txtReplacementArr.push(txtReplacementObj);
-		}
-	}
+   // Calculate the text width for each column, which will then be used to
+   // calculate the width of the box.  For the width of the box, we need to
+   // subtract at least 3 from the edit area with to accomodate the box's side
+   // borders and the space between the text columns.
+   var txtWidth = Math.floor((gEditWidth - 10)/2);
+
+   // In order to be able to navigate forward and backwards through the text
+   // replacements, we need to copy them into an array, since gTxtReplacements
+   // is an object and not navigable both ways.  This will also allow us to easily
+   // know how many text replacements there are (using the .length property of
+   // the array).
+   // For speed, create this only once.
+   if (typeof(listTextReplacements.txtReplacementArr) == "undefined")
+   {
+      var txtReplacementObj = null;
+      listTextReplacements.txtReplacementArr = new Array();
+      for (var prop in gTxtReplacements)
+      {
+         txtReplacementObj = new Object();
+         txtReplacementObj.originalText = prop;
+         txtReplacementObj.replacement = gTxtReplacements[prop];
+         listTextReplacements.txtReplacementArr.push(txtReplacementObj);
+      }
+   }
 
-	// We'll want to have an object with the box dimensions.
-	var boxInfo = new Object();
+   // We'll want to have an object with the box dimensions.
+   var boxInfo = new Object();
 
-	// Construct the top & bottom border strings if they don't exist already.
-	if (typeof(listTextReplacements.topBorder) == "undefined")
-	{
-		listTextReplacements.topBorder = "\1n" + gConfigSettings.genColors.listBoxBorder
-		                               + UPPER_LEFT_SINGLE + "\1n" + gConfigSettings.genColors.listBoxBorderText + "Text"
-		                               + "\1n" + gConfigSettings.genColors.listBoxBorder;
-		for (var i = 0; i < (txtWidth-3); ++i)
-			listTextReplacements.topBorder += HORIZONTAL_SINGLE;
-		listTextReplacements.topBorder += "\1n" + gConfigSettings.genColors.listBoxBorderText
-		                               + "Replacement" + "\1n" + gConfigSettings.genColors.listBoxBorder;
-		for (var i = 0; i < (txtWidth-11); ++i)
-			listTextReplacements.topBorder += HORIZONTAL_SINGLE;
-		listTextReplacements.topBorder += UPPER_RIGHT_SINGLE;
-	}
-	boxInfo.width = strip_ctrl(listTextReplacements.topBorder).length;
-	if (typeof(listTextReplacements.bottomBorder) == "undefined")
-	{
-		var numReplacementsStr = "Total: " + listTextReplacements.txtReplacementArr.length;
-		listTextReplacements.bottomBorder = "\1n" + gConfigSettings.genColors.listBoxBorder
-		                                  + LOWER_LEFT_SINGLE + "\1n" + gConfigSettings.genColors.listBoxBorderText
-		                                  + UP_ARROW + ", " + DOWN_ARROW + ", ESC/Ctrl-T/C=Close" + "\1n"
-		                                  + gConfigSettings.genColors.listBoxBorder;
-		var maxNumChars = boxInfo.width - numReplacementsStr.length - 28;
-		for (var i = 0; i < maxNumChars; ++i)
-			listTextReplacements.bottomBorder += HORIZONTAL_SINGLE;
-		listTextReplacements.bottomBorder += RIGHT_T_SINGLE + "\1n"
-		                                  + gConfigSettings.genColors.listBoxBorderText + numReplacementsStr + "\1n"
-		                                  + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE;
-		listTextReplacements.bottomBorder += LOWER_RIGHT_SINGLE;
-	}
-	// printf format strings for the list
-	if (typeof(listTextReplacements.listFormatStr) == "undefined")
-	{
-		listTextReplacements.listFormatStr = "\1n" + gConfigSettings.genColors.listBoxItemText
-		                                   + "%-" + txtWidth + "s %-" + txtWidth + "s";
-	}
-	if (typeof(listTextReplacements.listFormatStrNormalAttr) == "undefined")
-		listTextReplacements.listFormatStrNormalAttr = "\1n%-" + txtWidth + "s %-" + txtWidth + "s";
-
-	// Limit the box height to up to 12 lines.
-	boxInfo.height = gNumTxtReplacements + 2;
-	if (boxInfo.height > 12)
-		boxInfo.height = 12;
-	boxInfo.topLeftX = gEditLeft + Math.floor((gEditWidth/2) - (boxInfo.width/2));
-	boxInfo.topLeftY = gEditTop + Math.floor((gEditHeight/2) - (boxInfo.height/2));
-
-	// Draw the top & bottom box borders for the list of text replacements
-	var originalCurpos = console.getxy();
-	console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY);
-	console.print(listTextReplacements.topBorder);
-	console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY+boxInfo.height-1);
-	console.print(listTextReplacements.bottomBorder);
-	// Draw the side borders
-	console.print("\1n" + gConfigSettings.genColors.listBoxBorder);
-	for (var i = 0; i < boxInfo.height-2; ++i)
-	{
-		console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY+i+1);
-		console.print(VERTICAL_SINGLE);
-		console.gotoxy(boxInfo.topLeftX+boxInfo.width-1, boxInfo.topLeftY+i+1);
-		console.print(VERTICAL_SINGLE);
-	}
+   // Construct the top & bottom border strings if they don't exist already.
+   if (typeof(listTextReplacements.topBorder) == "undefined")
+   {
+      listTextReplacements.topBorder = "n" + gConfigSettings.genColors.listBoxBorder
+        + UPPER_LEFT_SINGLE + "n" + gConfigSettings.genColors.listBoxBorderText + "Text"
+        + "n" + gConfigSettings.genColors.listBoxBorder;
+      for (var i = 0; i < (txtWidth-3); ++i)
+         listTextReplacements.topBorder += HORIZONTAL_SINGLE;
+      listTextReplacements.topBorder += "n" + gConfigSettings.genColors.listBoxBorderText
+        + "Replacement" + "n" + gConfigSettings.genColors.listBoxBorder;
+      for (var i = 0; i < (txtWidth-11); ++i)
+         listTextReplacements.topBorder += HORIZONTAL_SINGLE;
+      listTextReplacements.topBorder += UPPER_RIGHT_SINGLE;
+   }
+   boxInfo.width = strip_ctrl(listTextReplacements.topBorder).length;
+   if (typeof(listTextReplacements.bottomBorder) == "undefined")
+   {
+      var numReplacementsStr = "Total: " + listTextReplacements.txtReplacementArr.length;
+      listTextReplacements.bottomBorder = "n" + gConfigSettings.genColors.listBoxBorder
+        + LOWER_LEFT_SINGLE + "n" + gConfigSettings.genColors.listBoxBorderText
+        + UP_ARROW + ", " + DOWN_ARROW + ", ESC/Ctrl-T/C=Close" + "n"
+        + gConfigSettings.genColors.listBoxBorder;
+      var maxNumChars = boxInfo.width - numReplacementsStr.length - 28;
+      for (var i = 0; i < maxNumChars; ++i)
+         listTextReplacements.bottomBorder += HORIZONTAL_SINGLE;
+      listTextReplacements.bottomBorder += RIGHT_T_SINGLE + "n"
+        + gConfigSettings.genColors.listBoxBorderText + numReplacementsStr + "n"
+        + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE;
+      listTextReplacements.bottomBorder += LOWER_RIGHT_SINGLE;
+   }
+   // printf format strings for the list
+   if (typeof(listTextReplacements.listFormatStr) == "undefined")
+   {
+      listTextReplacements.listFormatStr = "n" + gConfigSettings.genColors.listBoxItemText
+        + "%-" + txtWidth + "s %-" + txtWidth + "s";
+   }
+   if (typeof(listTextReplacements.listFormatStrNormalAttr) == "undefined")
+      listTextReplacements.listFormatStrNormalAttr = "n%-" + txtWidth + "s %-" + txtWidth + "s";
+
+   // Limit the box height to up to 12 lines.
+   boxInfo.height = gNumTxtReplacements + 2;
+   if (boxInfo.height > 12)
+      boxInfo.height = 12;
+   boxInfo.topLeftX = gEditLeft + Math.floor((gEditWidth/2) - (boxInfo.width/2));
+   boxInfo.topLeftY = gEditTop + Math.floor((gEditHeight/2) - (boxInfo.height/2));
+
+   // Draw the top & bottom box borders for the list of text replacements
+   var originalCurpos = console.getxy();
+   console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY);
+   console.print(listTextReplacements.topBorder);
+   console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY+boxInfo.height-1);
+   console.print(listTextReplacements.bottomBorder);
+   // Draw the side borders
+   console.print("n" + gConfigSettings.genColors.listBoxBorder);
+   for (var i = 0; i < boxInfo.height-2; ++i)
+   {
+      console.gotoxy(boxInfo.topLeftX, boxInfo.topLeftY+i+1);
+      console.print(VERTICAL_SINGLE);
+      console.gotoxy(boxInfo.topLeftX+boxInfo.width-1, boxInfo.topLeftY+i+1);
+      console.print(VERTICAL_SINGLE);
+   }
 
-	// Set up some variables for the user input loop
-	const numItemsPerPage = boxInfo.height - 2;
-	const numPages = Math.ceil(listTextReplacements.txtReplacementArr.length / numItemsPerPage);
-	// For the horizontal location of the page number text for the box border:
-	// Based on the fact that there can be up to 9999 text replacements and 10
-	// per page, there will be up to 1000 pages of replacements.  To write the
-	// text, we'll want to be 20 characters to the left of the end of the border
-	// of the box.
-	const pageNumTxtStartX = boxInfo.topLeftX + boxInfo.width - 20;
-	var pageNum = 0;
-	var startArrIndex = 0;
-	var endArrIndex = 0; // One past the last array item
-	var screenY = 0;
-	// User input loop (also drawing the list of items)
-	var continueOn = true;
-	var refreshList = true; // For screen redraw optimizations
-	while (continueOn)
-	{
-		if (refreshList)
-		{
-			// Write the list of items for the current page
-			startArrIndex = pageNum * numItemsPerPage;
-			endArrIndex = startArrIndex + numItemsPerPage;
-			if (endArrIndex > listTextReplacements.txtReplacementArr.length)
-				endArrIndex = listTextReplacements.txtReplacementArr.length;
-			screenY = boxInfo.topLeftY + 1;
-			for (var i = startArrIndex; i < endArrIndex; ++i)
-			{
-				console.gotoxy(boxInfo.topLeftX+1, screenY);
-				printf(listTextReplacements.listFormatStr,
-				       listTextReplacements.txtReplacementArr[i].originalText.substr(0, txtWidth),
-				       listTextReplacements.txtReplacementArr[i].replacement.substr(0, txtWidth));
-				++screenY;
-			}
-			// If the current screen row is below the bottom row inside the box,
-			// continue and write blank lines to the bottom of the inside of the box
-			// to blank out any text that might still be there.
-			while (screenY < boxInfo.topLeftY+boxInfo.height-1)
-			{
-				console.gotoxy(boxInfo.topLeftX+1, screenY);
-				printf(listTextReplacements.listFormatStrNormalAttr, "", "");
-				++screenY;
-			}
+   // Set up some variables for the user input loop
+   const numItemsPerPage = boxInfo.height - 2;
+   const numPages = Math.ceil(listTextReplacements.txtReplacementArr.length / numItemsPerPage);
+   // For the horizontal location of the page number text for the box border:
+   // Based on the fact that there can be up to 9999 text replacements and 10
+   // per page, there will be up to 1000 pages of replacements.  To write the
+   // text, we'll want to be 20 characters to the left of the end of the border
+   // of the box.
+   const pageNumTxtStartX = boxInfo.topLeftX + boxInfo.width - 20;
+   var pageNum = 0;
+   var startArrIndex = 0;
+   var endArrIndex = 0; // One past the last array item
+   var screenY = 0;
+   // User input loop (also drawing the list of items)
+   var continueOn = true;
+   var refreshList = true; // For screen redraw optimizations
+   while (continueOn)
+   {
+      if (refreshList)
+      {
+         // Write the list of items for the current page
+         startArrIndex = pageNum * numItemsPerPage;
+         endArrIndex = startArrIndex + numItemsPerPage;
+         if (endArrIndex > listTextReplacements.txtReplacementArr.length)
+            endArrIndex = listTextReplacements.txtReplacementArr.length;
+         screenY = boxInfo.topLeftY + 1;
+         for (var i = startArrIndex; i < endArrIndex; ++i)
+         {
+            console.gotoxy(boxInfo.topLeftX+1, screenY);
+            printf(listTextReplacements.listFormatStr,
+                   listTextReplacements.txtReplacementArr[i].originalText.substr(0, txtWidth),
+                   listTextReplacements.txtReplacementArr[i].replacement.substr(0, txtWidth));
+            ++screenY;
+         }
+         // If the current screen row is below the bottom row inside the box,
+         // continue and write blank lines to the bottom of the inside of the box
+         // to blank out any text that might still be there.
+         while (screenY < boxInfo.topLeftY+boxInfo.height-1)
+         {
+            console.gotoxy(boxInfo.topLeftX+1, screenY);
+            printf(listTextReplacements.listFormatStrNormalAttr, "", "");
+            ++screenY;
+         }
 
-			// Update the page number in the top border of the box.
-			console.gotoxy(pageNumTxtStartX, boxInfo.topLeftY);
-			console.print("\1n" + gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE);
-			printf("\1n" + gConfigSettings.genColors.listBoxBorderText + "Page %4d of %4d", pageNum+1, numPages);
-			console.print("\1n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE);
+         // Update the page number in the top border of the box.
+         console.gotoxy(pageNumTxtStartX, boxInfo.topLeftY);
+         console.print("n" + gConfigSettings.genColors.listBoxBorder + RIGHT_T_SINGLE);
+         printf("n" + gConfigSettings.genColors.listBoxBorderText + "Page %4d of %4d", pageNum+1, numPages);
+         console.print("n" + gConfigSettings.genColors.listBoxBorder + LEFT_T_SINGLE);
 
-			// Just for sane appearance: Move the cursor to the first character of
-			// the first row and make it the color for the text replacements.
-			console.gotoxy(boxInfo.topLeftX+1, boxInfo.topLeftY+1);
-			console.print(gConfigSettings.genColors.listBoxItemText);
-		}
+         // Just for sane appearance: Move the cursor to the first character of
+         // the first row and make it the color for the text replacements.
+         console.gotoxy(boxInfo.topLeftX+1, boxInfo.topLeftY+1);
+         console.print(gConfigSettings.genColors.listBoxItemText);
+      }
 
-		// Get a key from the user (upper-case) and take action based upon it.
-		userInput = getUserKey(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings);
-		switch (userInput)
-		{
-			case KEY_UP:
-				// Go up one page
-				refreshList = (pageNum > 0);
-				if (refreshList)
-					--pageNum;
-				break;
-			case KEY_DOWN:
-				// Go down one page
-				refreshList = (pageNum < numPages-1);
-				if (refreshList)
-					++pageNum;
-				break;
-				// Quit for ESC, Ctrl-T, Ctrl-A, and 'C' (close).
-			case KEY_ESC:
-			case CTRL_T:
-			case CTRL_A:
-			case 'C':
-				refreshList = false;
-				continueOn = false;
-				break;
-			default:
-				// Unrecognized command.  Don't refresh the list of the screen.
-				refreshList = false;
-				break;
-		}
-	}
+      // Get a key from the user (upper-case) and take action based upon it.
+      userInput = getUserKey(K_UPPER|K_NOCRLF|K_NOSPIN, gConfigSettings);
+      switch (userInput)
+      {
+         case KEY_UP:
+            // Go up one page
+            refreshList = (pageNum > 0);
+            if (refreshList)
+               --pageNum;
+            break;
+         case KEY_DOWN:
+            // Go down one page
+            refreshList = (pageNum < numPages-1);
+            if (refreshList)
+               ++pageNum;
+            break;
+         // Quit for ESC, Ctrl-T, Ctrl-A, and 'C' (close).
+         case KEY_ESC:
+         case CTRL_T:
+         case CTRL_A:
+         case 'C':
+            refreshList = false;
+            continueOn = false;
+            break;
+         default:
+            // Unrecognized command.  Don't refresh the list of the screen.
+            refreshList = false;
+            break;
+      }
+   }
 
-	// We're done listing the text replacements.
-	// Erase the list box rectangle by re-drawing the message text.  Then, move
-	// the cursor back to where it was originally.
-	var editLineIndexAtSelBoxTopRow = gEditLinesIndex - (originalCurpos.y-boxInfo.topLeftY);
-	displayMessageRectangle(boxInfo.topLeftX, boxInfo.topLeftY, boxInfo.width,
-	                        boxInfo.height, editLineIndexAtSelBoxTopRow, true);
-	console.gotoxy(originalCurpos);
-	console.print(chooseEditColor());
+   // We're done listing the text replacements.
+   // Erase the list box rectangle by re-drawing the message text.  Then, move
+   // the cursor back to where it was originally.
+   var editLineIndexAtSelBoxTopRow = gEditLinesIndex - (originalCurpos.y-boxInfo.topLeftY);
+   displayMessageRectangle(boxInfo.topLeftX, boxInfo.topLeftY, boxInfo.width,
+                           boxInfo.height, editLineIndexAtSelBoxTopRow, true);
+   console.gotoxy(originalCurpos);
+   console.print(chooseEditColor());
 }
 
 // Lets the user manage their preferences/settings.
@@ -6248,7 +5808,6 @@ function doUserSettings(pCurpos, pReturnCursorToOriginalPos)
 
 	if (returnCursorWhenDone)
 		console.gotoxy(originalCurpos);
-	console.print(chooseEditColor());
 }
 
 // Allows the user to select a tagline.  Returns an object with the following
@@ -6323,7 +5882,7 @@ function setQuotePrefix()
 	// to ensure we get the correct name in case anything in the message base
 	// changes after this function is first called.
 	if (!setQuotePrefix.curMsgFromName)
-		setQuotePrefix.curMsgFromName = getFromNameForCurMsg(gMsgAreaNameInfo);
+		setQuotePrefix.curMsgFromName = getFromNameForCurMsg(gMsgAreaInfo);
 
 	gQuotePrefix = " > "; // The default quote prefix
 	// If we're configured to use poster's initials in the
@@ -6337,7 +5896,7 @@ function setQuotePrefix()
 		// a message sub-board), use the gToName value read from the drop file.
 		// Remove any leading, multiple, or trailing spaces.
 		var quotedName = "";
-		if (postingInMsgSubBoard(gMsgAreaName))
+		if (postingInMsgSubBoard(gMsgArea))
 		{
 			quotedName = trimSpaces(setQuotePrefix.curMsgFromName, true, true, true);
 			if (quotedName.length == 0)
@@ -6460,229 +6019,4 @@ function getSignName(pSubCode, pRealNameOnlyFirst, pRealNameForEmail)
 	else
 		signName = trimSpaces(user.alias, true, false, true);
 	return signName;
-}
-
-// Prints a text line and fills the remainder of the line
-// with spaces using the normal attribute.
-//
-// Parameters:
-//  pTextLine: The text line to print
-//  pColor: The color/attribute code to use
-//  pIsQuoteLine: Boolean - Whether or not it's a quote line.  If true,
-//                then any attribtes will be stripped and the passed-in
-//                color (assumed to be the quote line color as applicable)
-//                will be used.
-function printLineAndFillEditWidthRemainder(pTextLine, pColor, pIsQuoteLine)
-{
-	var lineWithoutCtrlChars = strip_ctrl(pTextLine);
-	if (pIsQuoteLine)
-		console.print(pColor + lineWithoutCtrlChars);
-	else
-		console.print(pColor + pTextLine);
-	var printableStrLen = lineWithoutCtrlChars.length;
-	if (printableStrLen < gEditWidth)
-		printf("\1n%" + +(gEditWidth - printableStrLen) + "s", "");
-}
-
-// Returns whether or not text color selection is allowed in a message area.
-//
-// Parameters:
-//  pMsgAreaName: The message area (sub-board) name.  This is read from the drop
-//                file and is also in Synchronet's message area data structures.
-//                This needs to be passed in case the message is being posted as
-//                email rather than on a sub-board.
-//  pSubCode: The internal sub-board code of the sub-board being posted on (or
-//            "mail" for personal email)
-//
-// Return value: Whether or not color selection is allowed in the current message area
-function colorSelectionAllowedInMsgArea(pMsgAreaName, pSubCode)
-{
-	var colorsAllowed = true;
-	if (postingInMsgSubBoard(pMsgAreaName))
-	{
-		// First check the current group short name & description in the list of message group names
-		var curGrpShortNameUpper = msg_area.sub[pSubCode].grp_name.toUpperCase();
-		var grpIdx = msg_area.sub[pSubCode].grp_index;
-		var curGrpDescUpper = msg_area.grp_list[grpIdx].description.toUpperCase();
-		for (var i = 0; i < gConfigSettings.noColorSelectionGrpNames.length; ++i)
-		{
-			if ((gConfigSettings.noColorSelectionGrpNames[i] == curGrpShortNameUpper) || (gConfigSettings.noColorSelectionGrpNames[i] == curGrpDescUpper))
-			{
-				colorsAllowed = false;
-				break;
-			}
-		}
-		// If the group name wasn't found, then check the current sub-board code
-		// in the list of sub-board codes
-		if (colorsAllowed)
-		{
-			var subBoardCodeLower = pSubCode.toLowerCase(); // Should be lower case anyway, but just in case..
-			for (var i = 0; i < gConfigSettings.noColorSelectionSubBoardCodes.length; ++i)
-			{
-				if (gConfigSettings.noColorSelectionSubBoardCodes[i] == subBoardCodeLower)
-				{
-					colorsAllowed = false;
-					break;
-				}
-			}
-		}
-	}
-	return colorsAllowed;
-}
-
-// Returns whether or not color code selection is allowed according to the settings,
-// and in the current message area and in the text line.
-//
-// Parameters:
-//  pMsgAreaName: The message area (sub-board) name.  This is read from the drop
-//                file and is also in Synchronet's message area data structures.
-//                This needs to be passed in case the message is being posted as
-//                email rather than on a sub-board.
-//  pSubCode: The internal sub-board code of the sub-board being posted on (or
-//            "mail" for personal email)
-//
-// Return value: An object with the following properties:
-//               colorsAllowed: Boolean - Whether or not colors are allowed
-//               errorMsg: A string containing an error message if colors aren't allowed
-function colorSelectionAllowedInSettingsAndLine(pMsgAreaName, pSubCode)
-{
-	var retObj = {
-		colorsAllowed: (gConfigSettings.allowColorSelection && colorSelectionAllowedInMsgArea(pMsgAreaName, pSubCode)
-		                && !gEditLines[gEditLinesIndex].isQuoteLine),
-		errorMsg: ""
-	};
-	if (!retObj.colorsAllowed)
-	{
-		if (gEditLines[gEditLinesIndex].isQuoteLine)
-			retObj.errorMsg = "Can't change quote line colors";
-		else
-			retObj.errorMsg = "Changing colors is not allowed in this message area";
-	}
-
-	return retObj;
-}
-
-// Returns whether or not message text colors should be converted to ANSI
-// in a message sub-board
-//
-// Parameters:
-//  pMsgAreaName: The message area name.  This needs to be passed in case
-//                the message is being posted as internal email and the
-//                group name isn't available in Sychronet's data structures
-//  pSubCode: The internal code of the sub-board
-//
-// Return value: Boolean - Whether or not to convert text colors to ANSI in
-//               the sub-board
-function shouldConvertMsgColorsToANSIInMsgArea(pMsgAreaName, pSubCode)
-{
-	var convertToANSI = false;
-	if (postingInMsgSubBoard(pMsgAreaName))
-	{
-		// First check the current group name in the list of message group names
-		var curGrpShortNameUpper = msg_area.sub[pSubCode].grp_name.toUpperCase();
-		var grpIdx = msg_area.sub[pSubCode].grp_index;
-		var curGrpDescUpper = msg_area.grp_list[grpIdx].description.toUpperCase();
-		for (var i = 0; i < gConfigSettings.cvtColorToANSIGrpNames.length; ++i)
-		{
-			if ((gConfigSettings.cvtColorToANSIGrpNames[i] == curGrpShortNameUpper) || (gConfigSettings.cvtColorToANSIGrpNames[i] == curGrpDescUpper))
-			{
-				convertToANSI = true;
-				break;
-			}
-		}
-		// If the group name wasn't found, then check the current sub-board code
-		// in the list of sub-board codes
-		if (!convertToANSI)
-		{
-			var subBoardCodeLower = pSubCode.toLowerCase(); // Should be lower case anyway, but just in case..
-			for (var i = 0; i < gConfigSettings.cvtColorToANSISubBoardCodes.length; ++i)
-			{
-				if (gConfigSettings.cvtColorToANSISubBoardCodes[i] == subBoardCodeLower)
-				{
-					convertToANSI = true;
-					break;
-				}
-			}
-		}
-	}
-	else
-	{
-		// See if the sub-board name (probably "ELECTRONIC MAIL") is in the list
-		var msgAreaNameUpper = pMsgAreaName.toUpperCase();
-		for (var i = 0; (i < gConfigSettings.cvtColorToANSIGrpNames.length) && !convertToANSI; ++i)
-			convertToANSI = (gConfigSettings.cvtColorToANSIGrpNames[i] == msgAreaNameUpper);
-	}
-	return convertToANSI;
-}
-
-// Concatenates all message edit lines together.
-//
-// Parameters:
-//  pEditLines: An array of TextLine objects (such as gEditLines, for instance)
-//  pSigInfo: An object containing the user's signature
-//  pStripCtrl: Boolean - Whether or not to strip control characters from the text
-//
-// Return value: A string containing all message text concatenated from pEditLines
-function concatMsgEditLines(pEditLines, pSigInfo, pStripCtrl)
-{
-	var stripCtrlChars = (typeof(pStripCtrl) == "boolean" ? pStripCtrl : false);
-	var msgText = "";
-	// Append each line to msgText.  Then,
-	//  - If using Synchronet 3.15 or higher:
-	//    Depending on whether the line has a hard newline
-	//    or a soft newline, append a "\r\n" or a " \n", as
-	//    per Synchronet's standard as of 3.15.
-	//  - Otherwise (Synchronet 3.14 and below):
-	//    Just append a "\r\n" to the line
-	if (system.version_num >= 31500)
-	{
-		var useHardNewline = false;
-		if (stripCtrlChars)
-		{
-			for (var i = 0; i < pEditLines.length; ++i)
-			{
-				// Use a hard newline if the current edit line has one or if this is
-				// the last line of the message.
-				useHardNewline = (pEditLines[i].hardNewlineEnd || (i == pEditLines.length-1));
-				msgText += strip_ctrl(pEditLines[i].text) + (useHardNewline ? "\r\n" : " \n");
-			}
-		}
-		else // Don't strip control characters
-		{
-			for (var i = 0; i < pEditLines.length; ++i)
-			{
-				// Use a hard newline if the current edit line has one or if this is
-				// the last line of the message.
-				useHardNewline = (pEditLines[i].hardNewlineEnd || (i == pEditLines.length-1));
-				msgText += pEditLines[i].text + (useHardNewline ? "\r\n" : " \n");
-			}
-		}
-	}
-	else // Synchronet 3.14 and below
-	{
-		if (stripCtrlChars)
-		{
-			for (var i = 0; i < pEditLines.length; ++i)
-				msgText += strip_ctrl(pEditLines[i].text) + "\r\n";
-		}
-		else // Don't strip control characters
-		{
-			for (var i = 0; i < pEditLines.length; ++i)
-				msgText += pEditLines[i].text + "\r\n";
-		}
-	}
-
-	// Read the user's signature, in case they have one
-	var msgSigInfo = (typeof(pSigInfo) == "object" ? pSigInfo : readUserSigFile());
-	// If the user has not chosen to auto-sign messages, then also append their
-	// signature to the message now.
-	if (!gUserSettings.autoSignMessages)
-	{
-		// Append a blank line to separate the message & signature.
-		// Note: msgText already has a newline at the end, so
-		// we don't have to append one here; just append the signature.
-		if (msgSigInfo.sigContents.length > 0)
-			msgText += msgSigInfo.sigContents + "\r\n";
-	}
-	return msgText;
 }
\ No newline at end of file
diff --git a/exec/SlyEdit_DCTStuff.js b/exec/SlyEdit_DCTStuff.js
index fc3478ece6..5b43efa2a1 100644
--- a/exec/SlyEdit_DCTStuff.js
+++ b/exec/SlyEdit_DCTStuff.js
@@ -12,7 +12,7 @@
  * 2009-08-22 Eric Oulashin     Version 1.00
  *                              Initial public release
  * 2009-12-03 Eric Oulashin     Added support for color schemes.
- *                              Added readDCTColorConfig().
+ *                              Added readColorConfig().
  * 2009-12-31 Eric Oulashin     Updated promptYesNo_DCTStyle()
  *                              so that the return variable,
  *                              userResponse, defaults to the
@@ -37,13 +37,13 @@
  *                              of the theme filename, since the path is
  *                              now set in ReadSlyEditConfigFile() in
  *                              SlyEdit_Misc.js.
- * 2013-01-19 Eric Oulashin     Updated readDCTColorConfig() to move the
+ * 2013-01-19 Eric Oulashin     Updated readColorConfig() to move the
  *                              general color settings to gConfigSettings.genColors.*
  * 2013-01-24 Eric Oulashin     Updated doDCTMenu() to include an option
  *                              for cross-posting on the File menu.
- * 2013-08-23 Eric Oulashin     Updated readDCTColorConfig() with the new general color
+ * 2013-08-23 Eric Oulashin     Updated readColorConfig() with the new general color
  *                              configuration settings.
- * 2013-08-28 Eric Oulashin     Simplified readDCTColorConfig() by having it call
+ * 2013-08-28 Eric Oulashin     Simplified readColorConfig() by having it call
  *                              moveGenColorsToGenSettings() (defined in
  *                              SlyEdit_Misc.js) to move the general colors
  *                              into the genColors array in the configuration
@@ -86,7 +86,7 @@ var DCTMENU_CROSS_POST = 12;
 var DCTMENU_LIST_TXT_REPLACEMENTS = 13;
 
 // Read the color configuration file
-readDCTColorConfig(gConfigSettings.DCTColors.ThemeFilename);
+readColorConfig(gConfigSettings.DCTColors.ThemeFilename);
 
 ///////////////////////////////////////////////////////////////////////////////////
 // Functions
@@ -95,16 +95,16 @@ readDCTColorConfig(gConfigSettings.DCTColors.ThemeFilename);
 //
 // Parameters:
 //  pFilename: The name of the color configuration file
-function readDCTColorConfig(pFilename)
+function readColorConfig(pFilename)
 {
-	var colors = readValueSettingConfigFile(pFilename, 512, true);
-	if (colors != null)
-	{
-		gConfigSettings.DCTColors = colors;
-		// Move the general color settings into gConfigSettings.genColors.*
-		if (EDITOR_STYLE == "DCT")
-			moveGenColorsToGenSettings(gConfigSettings.DCTColors, gConfigSettings);
-	}
+   var colors = readValueSettingConfigFile(pFilename, 512);
+   if (colors != null)
+   {
+      gConfigSettings.DCTColors = colors;
+      // Move the general color settings into gConfigSettings.genColors.*
+      if (EDITOR_STYLE == "DCT")
+        moveGenColorsToGenSettings(gConfigSettings.DCTColors, gConfigSettings);
+   }
 }
 
 // Re-draws the screen, in the style of DCTEdit.
@@ -163,7 +163,7 @@ function redrawScreen_DCTStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEd
 
 	// Message area
 	fieldWidth = (console.screen_columns * (27/80)).toFixed(0);
-	screenText = gMsgAreaName.substr(0, fieldWidth);
+	screenText = gMsgArea.substr(0, fieldWidth);
 	var startX = console.screen_columns - fieldWidth - 9;
 	console.gotoxy(startX, lineNum);
 	console.print(gConfigSettings.DCTColors.TopLabelColor + "Area" +
@@ -374,64 +374,53 @@ function DisplayTextAreaBottomBorder_DCTStyle(pLineNum, pUseQuotes, pEditLeft, p
 // Parameters:
 //  pLineNum: The line number on the screen at which to draw the help line
 //  pUsingQuotes: Boolean - Whether or not message quoting is enabled.
-//  pFillRestOfLine: Optional boolean - Whether or not to fill the rest of the line.
-//                   Defaults to false.
-function DisplayBottomHelpLine_DCTStyle(pLineNum, pUsingQuotes, pFillRestOfLine)
+function DisplayBottomHelpLine_DCTStyle(pLineNum, pUsingQuotes)
 {
-	// For efficiency, define the help line variable only once.
-	if (typeof(DisplayBottomHelpLine_DCTStyle.helpText) == "undefined")
-	{
-		DisplayBottomHelpLine_DCTStyle.helpText = gConfigSettings.DCTColors.BottomHelpBrackets +
-		                                          "[" + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" +
-		                                          gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR +
-		                                          gConfigSettings.DCTColors.BottomHelpKeys + "Z" +
-		                                          gConfigSettings.DCTColors.BottomHelpBrackets + "]\1n " +
-		                                          gConfigSettings.DCTColors.BottomHelpKeyDesc + "Save\1n      " +
-		                                          gConfigSettings.DCTColors.BottomHelpBrackets + "[" +
-		                                          gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" +
-		                                          gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR +
-		                                          gConfigSettings.DCTColors.BottomHelpKeys + "A" +
-		                                          gConfigSettings.DCTColors.BottomHelpBrackets + "]\1n " +
-		                                          gConfigSettings.DCTColors.BottomHelpKeyDesc + "Abort";
-		// If we can allow message quoting, then add a text to show Ctrl-Q for
-		// quoting.
-		if (pUsingQuotes)
-			DisplayBottomHelpLine_DCTStyle.helpText += "\1n      " +
-			                                           gConfigSettings.DCTColors.BottomHelpBrackets + "[" +
-			                                           gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" +
-			                                           gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR +
-			                                           gConfigSettings.DCTColors.BottomHelpKeys + "Q" +
-			                                           gConfigSettings.DCTColors.BottomHelpBrackets + "]\1n " +
-			                                           gConfigSettings.DCTColors.BottomHelpKeyDesc + "Quote";
-		DisplayBottomHelpLine_DCTStyle.helpText += "\1n      " +
-		                                           gConfigSettings.DCTColors.BottomHelpBrackets + "[" +
-		                                           gConfigSettings.DCTColors.BottomHelpKeys + "ESC" +
-		                                           gConfigSettings.DCTColors.BottomHelpBrackets + "]\1n " +
-		                                           gConfigSettings.DCTColors.BottomHelpKeyDesc + "Menu";
-		// Center the text by padding it in the front with spaces.  This is done instead
-		// of using console.center() because console.center() will output a newline,
-		// which would not be good on the last line of the screen.
-		var numSpaces = (console.screen_columns/2).toFixed(0)
-		              - (strip_ctrl(DisplayBottomHelpLine_DCTStyle.helpText).length/2).toFixed(0);
-		for (var i = 0; i < numSpaces; ++i)
-			DisplayBottomHelpLine_DCTStyle.helpText = " " + DisplayBottomHelpLine_DCTStyle.helpText;
-	}
+   // For efficiency, define the help line variable only once.
+   if (typeof(DisplayBottomHelpLine_DCTStyle.helpText) == "undefined")
+   {
+      DisplayBottomHelpLine_DCTStyle.helpText = gConfigSettings.DCTColors.BottomHelpBrackets +
+                     "[" + gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" +
+                     gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR +
+                     gConfigSettings.DCTColors.BottomHelpKeys + "Z" +
+                     gConfigSettings.DCTColors.BottomHelpBrackets + "]n " +
+                     gConfigSettings.DCTColors.BottomHelpKeyDesc + "Saven      " +
+                     gConfigSettings.DCTColors.BottomHelpBrackets + "[" +
+                     gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" +
+                     gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR +
+                     gConfigSettings.DCTColors.BottomHelpKeys + "A" +
+                     gConfigSettings.DCTColors.BottomHelpBrackets + "]n " +
+                     gConfigSettings.DCTColors.BottomHelpKeyDesc + "Abort";
+      // If we can allow message quoting, then add a text to show Ctrl-Q for
+      // quoting.
+      if (pUsingQuotes)
+         DisplayBottomHelpLine_DCTStyle.helpText += "n      " +
+                          gConfigSettings.DCTColors.BottomHelpBrackets + "[" +
+                          gConfigSettings.DCTColors.BottomHelpKeys + "CTRL" +
+                          gConfigSettings.DCTColors.BottomHelpFill + DOT_CHAR +
+                          gConfigSettings.DCTColors.BottomHelpKeys + "Q" +
+                          gConfigSettings.DCTColors.BottomHelpBrackets + "]n " +
+                          gConfigSettings.DCTColors.BottomHelpKeyDesc + "Quote";
+      DisplayBottomHelpLine_DCTStyle.helpText += "n      " +
+                     gConfigSettings.DCTColors.BottomHelpBrackets + "[" +
+                     gConfigSettings.DCTColors.BottomHelpKeys + "ESC" +
+                     gConfigSettings.DCTColors.BottomHelpBrackets + "]n " +
+                     gConfigSettings.DCTColors.BottomHelpKeyDesc + "Menu";
+      // Center the text by padding it in the front with spaces.  This is done instead
+      // of using console.center() because console.center() will output a newline,
+      // which would not be good on the last line of the screen.
+      var numSpaces = (console.screen_columns/2).toFixed(0)
+                     - (strip_ctrl(DisplayBottomHelpLine_DCTStyle.helpText).length/2).toFixed(0);
+      for (var i = 0; i < numSpaces; ++i)
+         DisplayBottomHelpLine_DCTStyle.helpText = " " + DisplayBottomHelpLine_DCTStyle.helpText;
+   }
 
-	// Display the help line on the screen
-	var lineNum = console.screen_rows;
+   // Display the help line on the screen
+   var lineNum = console.screen_rows;
 	if ((typeof(pLineNum) != "undefined") && (pLineNum != null))
 		lineNum = pLineNum;
-	console.gotoxy(1, lineNum);
+   console.gotoxy(1, lineNum);
 	console.print(DisplayBottomHelpLine_DCTStyle.helpText);
-	// Fill the rest of the line (less 1 character) if we're told to do so
-	var fillRestOfLine = (typeof(pFillRestOfLine) == "boolean" ? pFillRestOfLine : false);
-	if (fillRestOfLine)
-	{
-		// The remainder length has 1 subtracted from it so that we don't output a newline/CR
-		var remainderLen = console.screen_columns - strip_ctrl(DisplayBottomHelpLine_DCTStyle.helpText).length - 1;
-		if (remainderLen > 0)
-			printf("\1n%" + remainderLen + "s", "");
-	}
 }
 
 // Updates the insert mode displayd on the screen, for DCT Edit style.
diff --git a/exec/SlyEdit_IceStuff.js b/exec/SlyEdit_IceStuff.js
index 5607adc222..e3fb7e0846 100644
--- a/exec/SlyEdit_IceStuff.js
+++ b/exec/SlyEdit_IceStuff.js
@@ -13,7 +13,7 @@
  *                              Initial public release
  * 2009-12-03 Eric Oulashin     Added support for color schemes.
  *                              Added displayIceYesNoText() and
- *                              readIceColorConfig().
+ *                              readColorConfig().
  * 2010-01-02 Eric Oulashin     Removed abortConfirm_DCTStyle(),
  *                              since it's no longer used anymore.
  * 2011-02-02 Eric Oulashin     Moved the time displaying code into
@@ -23,13 +23,13 @@
  *                              of the theme filename, since the path is
  *                              now set in ReadSlyEditConfigFile() in
  *                              SlyEdit_Misc.js.
- * 2013-01-19 Eric Oulashin     Updated readIceColorConfig() to move the
+ * 2013-01-19 Eric Oulashin     Updated readColorConfig() to move the
  *                              general color settings to gConfigSettings.genColors.*
  * 2013-01-25 Eric Oulashin     Updated doIceESCMenu() to include an option
  *                              for cross-posting, when allowed.
- * 2013-08-23 Eric Oulashin     Updated readIceColorConfig() with the new general color
+ * 2013-08-23 Eric Oulashin     Updated readColorConfig() with the new general color
  *                              configuration settings.
- * 2013-08-28 Eric Oulashin     Simplified readIceColorConfig() by having it call
+ * 2013-08-28 Eric Oulashin     Simplified readColorConfig() by having it call
  *                              moveGenColorsToGenSettings() (defined in
  *                              SlyEdit_Misc.js) to move the general colors
  *                              into the genColors array in the configuration
@@ -48,7 +48,7 @@
  *                              to use the new gConfigSettings.iceColors.menuOptClassicColors
  *                              configuration setting for new vs. classic Ice-style
  *                              menu option colors.
- * 2013-10-17 Eric Oulashin     Bug fix: Updated readIceColorConfig() to make a backup of
+ * 2013-10-17 Eric Oulashin     Bug fix: Updated readColorConfig() to make a backup of
  *                              the menuOptClassicColors setting and set it back in the
  *                              iceColors object after reading & setting the colors.
  *                              Bug fix in DisplayTextAreaBottomBorder_IceStyle() in
@@ -70,7 +70,7 @@ var ICE_ESC_MENU_HELP = 4;
 var ICE_ESC_MENU_CROSS_POST = 5;
 
 // Read the color configuration file
-readIceColorConfig(gConfigSettings.iceColors.ThemeFilename);
+readColorConfig(gConfigSettings.iceColors.ThemeFilename);
 
 
 ///////////////////////////////////////////////////////////////////////////////////
@@ -80,20 +80,20 @@ readIceColorConfig(gConfigSettings.iceColors.ThemeFilename);
 //
 // Parameters:
 //  pFilename: The name of the color configuration file
-function readIceColorConfig(pFilename)
+function readColorConfig(pFilename)
 {
-	var colors = readValueSettingConfigFile(pFilename, 512, true);
-	if (colors != null)
-	{
-		// Make a backup of the menuOptClassicColors setting so we can set it
-		// back in the Ice color settings object after setting the colors.
-		var useClassicColorsBackup = gConfigSettings.iceColors.menuOptClassicColors;
-		gConfigSettings.iceColors = colors;
-		// Move the general color settings into gConfigSettings.genColors.*
-		if (EDITOR_STYLE == "ICE")
-			moveGenColorsToGenSettings(gConfigSettings.iceColors, gConfigSettings);
-		gConfigSettings.iceColors.menuOptClassicColors = useClassicColorsBackup;
-	}
+   var colors = readValueSettingConfigFile(pFilename, 512);
+   if (colors != null)
+   {
+      // Make a backup of the menuOptClassicColors setting so we can set it
+      // back in the Ice color settings object after setting the colors.
+      var useClassicColorsBackup = gConfigSettings.iceColors.menuOptClassicColors;
+      gConfigSettings.iceColors = colors;
+      // Move the general color settings into gConfigSettings.genColors.*
+      if (EDITOR_STYLE == "ICE")
+         moveGenColorsToGenSettings(gConfigSettings.iceColors, gConfigSettings);
+      gConfigSettings.iceColors.menuOptClassicColors = useClassicColorsBackup;
+   }
 }
 
 // Re-draws the screen, in the style of IceEdit.
@@ -109,7 +109,7 @@ function readIceColorConfig(pFilename)
 //  pEditLinesIndex: The index of the message line at the top of the edit area
 //  pDisplayEditLines: The function that displays the edit lines
 function redrawScreen_IceStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEditColor,
-                               pInsertMode, pUseQuotes, pEditLinesIndex, pDisplayEditLines)
+                                pInsertMode, pUseQuotes, pEditLinesIndex, pDisplayEditLines)
 {
 	// Top header
 	// Generate & display the top border line (Note: Generate this
@@ -235,7 +235,8 @@ function redrawScreen_IceStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEd
       // The message area name should be centered on the line.  So, based on its
       // length (up to 35 characters), figure out its starting position before
       // printing it.
-      var msgAreaName = gMsgAreaName.substr(0, 35); // The 35 used to be 20
+      //var msgAreaName = gMsgArea.substr(0, 20); // Used to be 20 characters
+      var msgAreaName = gMsgArea.substr(0, 35);
       // 2 is subtracted from the starting position to leave room for the
       // block character and the space.
       var startPos = (console.screen_columns/2).toFixed(0) - (msgAreaName.length/2).toFixed(0) - 2;
@@ -390,48 +391,37 @@ function DisplayTextAreaBottomBorder_IceStyle(pLineNum, pUseQuotes, pEditLeft, p
 //
 // Parameters:
 //  pLineNum: The line number on the screen where the text should be placed
-//  pUsingQuotes: Boolean - Whether or not message quoting is enabled.  This is
-//                only here to match the DCT-style function.
-//  pFillRestOfLine: Optional boolean - Whether or not to fill the rest of the line.
-//                   Defaults to false.
-function DisplayBottomHelpLine_IceStyle(pLineNum, pUsingQuotes, pFillRestOfLine)
+//  The following are not used and are only here to match the DCT-style function:
+//   pUsingQuotes: Boolean - Whether or not message quoting is enabled.
+function DisplayBottomHelpLine_IceStyle(pLineNum, pUsingQuotes)
 {
-	// Construct the help text only once
-	if (typeof(DisplayBottomHelpLine_IceStyle.helpText) == "undefined")
-	{
-		// This line contains the copyright mesage & ESC key help
-		var screenText = iceText(EDITOR_PROGRAM_NAME + " v", "\1w") + "\1c\1h"
-		               + EDITOR_VERSION.toString() + "   "
-		               + iceText("Copyright", "\1w") + " \1c\1h2018 "
-		               + iceText("Eric Oulashin", "\1w") + " \1n\1b" + DOT_CHAR + " "
-		               + iceText("Press ESCape For Help", "\1w");
-		// Calculate the starting position to center the help text, and front-pad
-		// DisplayBottomHelpLine_IceStyle.helpText with that many spaces.
-		var xPos = (console.screen_columns / 2).toFixed(0)
-		         - (strip_ctrl(screenText).length / 2).toFixed(0);
-		DisplayBottomHelpLine_IceStyle.helpText = "";
-		for (var i = 0; i < xPos; ++i)
-			DisplayBottomHelpLine_IceStyle.helpText += " ";
-		DisplayBottomHelpLine_IceStyle.helpText += screenText;
-	}
+   // Construct the help text only once
+   if (typeof(DisplayBottomHelpLine_IceStyle.helpText) == "undefined")
+   {
+      // This line contains the copyright mesage & ESC key help
+      var screenText = iceText(EDITOR_PROGRAM_NAME + " v", "w") + "ch"
+                      + EDITOR_VERSION.toString() + "   "
+                      + iceText("Copyright", "w") + " ch2017 "
+                      + iceText("Eric Oulashin", "w") + " nb" + DOT_CHAR + " "
+                      + iceText("Press ESCape For Help", "w");
+      // Calculate the starting position to center the help text, and front-pad
+      // DisplayBottomHelpLine_IceStyle.helpText with that many spaces.
+      var xPos = (console.screen_columns / 2).toFixed(0)
+                - (strip_ctrl(screenText).length / 2).toFixed(0);
+      DisplayBottomHelpLine_IceStyle.helpText = "";
+      for (var i = 0; i < xPos; ++i)
+         DisplayBottomHelpLine_IceStyle.helpText += " ";
+      DisplayBottomHelpLine_IceStyle.helpText += screenText;
+   }
 
-	// If pLineNum is not specified, then default to the last line
+   // If pLineNum is not specified, then default to the last line
 	// on the screen.
 	var lineNum = console.screen_rows;
 	if ((typeof(pLineNum) != "undefined") && (pLineNum != null))
 		lineNum = pLineNum;
-	// Display the help text on the screen
+   // Display the help text on the screen
 	console.gotoxy(1, lineNum);
 	console.print(DisplayBottomHelpLine_IceStyle.helpText);
-	// Fill the rest of the line (less 1 character) if we're told to do so
-	var fillRestOfLine = (typeof(pFillRestOfLine) == "boolean" ? pFillRestOfLine : false);
-	if (fillRestOfLine)
-	{
-		// The remainder length has 1 subtracted from it so that we don't output a newline/CR
-		var remainderLen = console.screen_columns - strip_ctrl(DisplayBottomHelpLine_IceStyle.helpText).length - 1;
-		if (remainderLen > 0)
-			printf("\1n%" + remainderLen + "s", "");
-	}
 }
 
 // Updates the insert mode displayd on the screen, for Ice Edit style.
diff --git a/exec/SlyEdit_Misc.js b/exec/SlyEdit_Misc.js
index c63dae379a..a17b573b4d 100644
--- a/exec/SlyEdit_Misc.js
+++ b/exec/SlyEdit_Misc.js
@@ -34,11 +34,10 @@
  *                              handle situations when it wraps text into the
  *                              next line when that next line is blank - Ensuring
  *                              it adds a blank line below that.
- * 2018-01-27 Eric Oulashin     Added removeStrayANSIOneChars()
  */
  
-load("text.js");
-
+ load("text.js");
+ 
 // Note: These variables are declared with "var" instead of "const" to avoid
 // multiple declaration errors when this file is loaded more than once.
 
@@ -157,11 +156,6 @@ var gDDML_DROP_FILE_NAME = system.node_dir + "DDML_SyncSMBInfo.txt";
 
 var gUserSettingsFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".SlyEdit_Settings";
 
-// A regular expression for matching a Synchronet color/attribute code (case-insensitive)
-var gSyncAttrRegex = /[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]/i;
-// A regular expression to match all only Synchronet attribute codes in a string
-var gOnlySyncAttrsInStrRegex = /^([krgybmcw01234567hinpq,;\.dtl<>\[\]asz])+$/i;
-
 ///////////////////////////////////////////////////////////////////////////////////
 // Object/class stuff
 
@@ -191,21 +185,19 @@ function TextLine(pText, pHardNewlineEnd, pIsQuoteLine)
 	if ((pIsQuoteLine != null) && (typeof(pIsQuoteLine) == "boolean"))
 		this.isQuoteLine = pIsQuoteLine;
 
+	// NEW & EXPERIMENTAL:
+	// For color support
+	this.attrs = new Array(); // An array of attributes for the line
 	// Functions
-	this.displayLength = TextLine_DisplayLength;
+	this.length = TextLine_Length;
 	this.print = TextLine_Print;
-	this.doMacroTxtReplacement = TextLine_DoMacroTxtReplacement;
-	this.getWord = TextLine_GetWord;
-	this.substrWithSyncColorCodes = TextLine_SubstrWithSyncColorCodes;
-	this.getLastAttrCodes = TextLine_GetLastAttrCodes;
-	this.getAttrsAndIndexesBeforeIdx = TextLine_GetAttrsAndIndexesBeforeIdx;
-	this.displayIdxToActualIdx = TextLine_DisplayIdxToActualIdx;
+	this.doMacroTxtReplacement = TextLine_doMacroTxtReplacement;
+	this.getWord = TextLine_getWord;
 }
 // For the TextLine class: Returns the length of the text.
-function TextLine_DisplayLength()
+function TextLine_Length()
 {
-	//return this.text.length;
-	return strip_ctrl(this.text).length;
+	return this.text.length;
 }
 // For  the TextLine class: Prints the text line, using its text attributes.
 //
@@ -226,19 +218,11 @@ function TextLine_Print(pClearToEOL)
 //  pCharIndex: The current character index in the text line
 //  pUseRegex: Whether or not to treat the text replacement search string as a
 //             regular expression.
-//  pAllowAttrCodes: Boolean - Whether or not to allow attribute codes in
-//                   the replacement strings
-//  pTextAttrs: The text attribute(s) for the message text.  If there are
-//              any color/attribute codes in a replacement text, this will
-//              be appended to the end so the rest of the message will be
-//              the color it should be.
 //
 // Return value: An object containing the following properties:
 //               textLineIndex: The updated text line index (integer)
 //               wordLenDiff: The change in length of the word that
 //                            was replaced (integer)
-//               displayableWordLenDiff: The change in display length of the
-//                                       word that was replaced (integer)
 //               wordStartIdx: The index of the first character in the word.
 //                             Only valid if a word was found.  Otherwise, this
 //                             will be 0.
@@ -250,19 +234,15 @@ function TextLine_Print(pClearToEOL)
 //                           replaced or 0 if no word was found.
 //               madeTxtReplacement: Whether or not a text replacement was made
 //                                   (boolean)
-function TextLine_DoMacroTxtReplacement(pTxtReplacements, pCharIndex, pUseRegex, pAllowAttrCodes, pTextAttrs)
+function TextLine_doMacroTxtReplacement(pTxtReplacements, pCharIndex, pUseRegex)
 {
-	var retObj = {
-		textLineIndex: pCharIndex,
-		wordLenDiff: 0,
-		displayableWordLenDiff: 0,
-		wordStartIdx: 0,
-		newTextEndIdx: 0,
-		newTextLen: 0,
-		madeTxtReplacement: false
-	};
-
-	var allowAttrCodes = (typeof(pAllowAttrCodes) == "boolean" ? pAllowAttrCodes : false);
+	var retObj = new Object();
+	retObj.textLineIndex = pCharIndex;
+	retObj.wordLenDiff = 0;
+	retObj.wordStartIdx = 0;
+	retObj.newTextEndIdx = 0;
+	retObj.newTextLen = 0;
+	retObj.madeTxtReplacement = false;
 
 	var wordObj = this.getWord(retObj.textLineIndex);
 	if (wordObj.foundWord)
@@ -284,17 +264,7 @@ function TextLine_DoMacroTxtReplacement(pTxtReplacements, pCharIndex, pUseRegex,
 				if (pTxtReplacements.hasOwnProperty(prop))
 				{
 					var regex = new RegExp(prop);
-					if (allowAttrCodes)
-					{
-						txtReplacement = wordObj.word.replace(regex, pTxtReplacements[prop]);
-						// If the replacement has any Synchronet attribute codes, append the
-						// given text attributes to it so the rest of the message is back to
-						// normal colors.  // TODO: Dealing with high vs. normal?
-						if (gSyncAttrRegex.test(txtReplacement))
-							txtReplacement += "\1n" + pTextAttrs;
-					}
-					else
-						txtReplacement = wordObj.word.replace(regex, strip_ctrl(pTxtReplacements[prop]));
+					txtReplacement = wordObj.word.replace(regex, pTxtReplacements[prop]);
 					retObj.madeTxtReplacement = (txtReplacement != wordObj.word);
 					// If a text replacement was made, then check and see if the first
 					// letter in the original text was uppercase, and if so, make the
@@ -327,31 +297,20 @@ function TextLine_DoMacroTxtReplacement(pTxtReplacements, pCharIndex, pUseRegex,
 			wordObj.word = wordObj.word.toUpperCase();
 			if (pTxtReplacements.hasOwnProperty(wordObj.word))
 			{
-				if (allowAttrCodes)
-				{
-					txtReplacement = pTxtReplacements[wordObj.word];
-					// If the replacement has any Synchronet attribute codes, append the
-					// given text attributes to it so the rest of the message is back to
-					// normal colors.  // TODO: Dealing with high vs. normal?
-					if (gSyncAttrRegex.test(txtReplacement))
-						txtReplacement += "\1n" + pTextAttrs;
-				}
-				else
-					txtReplacement = strip_ctrl(pTxtReplacements[wordObj.word]);
+				txtReplacement = pTxtReplacements[wordObj.word];
 				retObj.madeTxtReplacement = true;
 			}
 		}
 		if (retObj.madeTxtReplacement)
 		{
 			if (firstCharUpper)
-				txtReplacement = txtReplacement.charAt(0).toUpperCase() + txtReplacement.substr(1);
+			txtReplacement = txtReplacement.charAt(0).toUpperCase() + txtReplacement.substr(1);
 			this.text = this.text.substr(0, wordObj.startIdx) + txtReplacement + this.text.substr(wordObj.endIndex+1);
 			// Based on the difference in word length, update the data that
 			// matters (retObj.textLineIndex, which keeps track of the index of the current line).
 			// Note: The horizontal cursor position variable should be replaced after calling this
 			// function.
 			retObj.wordLenDiff = txtReplacement.length - wordObj.word.length;
-			retObj.displayableWordLenDiff = strip_ctrl(txtReplacement).length - strip_ctrl(wordObj.word).length;
 			retObj.textLineIndex += retObj.wordLenDiff;
 			retObj.newTextEndIdx = wordObj.endIndex + retObj.wordLenDiff;
 			retObj.newTextLen = txtReplacement.length;
@@ -378,7 +337,7 @@ function TextLine_DoMacroTxtReplacement(pTxtReplacements, pCharIndex, pUseRegex,
 //               startIdx: The index of the first character of the word (integer)
 //               endIndex: The index of the last character of the word (integer)
 //                         This includes any control/color codes, etc.
-function TextLine_GetWord(pCharIndex)
+function TextLine_getWord(pCharIndex)
 {
 	var retObj = {
 		foundWord: false,
@@ -415,85 +374,7 @@ function TextLine_GetWord(pCharIndex)
 	retObj.plainWord = strip_ctrl(retObj.word);
 	return retObj;
 }
-// For the TextLine class: Gets a substring, handling Synchronet color codes.
-// This calls substrWithSyncColorCodes() with the object's text line and
-// returns the same value as that function.
-//
-// Parameters:
-//  pStartIdx: The index of where to start in the string, as it appears
-//             on the screen
-//  pLen: The length of the substring to get.  Optional - If not specified,
-//        the rest of the string will be used.
-//
-// Return value: An object with the following properties:
-//               strSub: The substring from the string, including any Synchronet
-//                       color codes that may exist preceding the word at pStartIdx
-//               startIdx: The actual index in the string where the substring starts
-//                         (-1 if an error occurred)
-//               endIdx: The actual index in the string where the substring ends.
-//                       This is the index of the last character in the substring.
-//                       Will be -1 if an error occurred.
-//               len: The actual length of the substring
-//               printableLen: The length of the string as it would appear on the screen
-//                             (i.e., its length without Synchronet attribute codes)
-//               syncAttrStartIdx: The starting index of Synchronet attributes
-//                                 (-1 if not found or an error occurred)
-//               syncAttrEndIdx: The ending index of Synchronet attributes, including
-//                               the last character (-1 if not found or an error occurred)
-function TextLine_SubstrWithSyncColorCodes(pStartIdx, pLen)
-{
-	return substrWithSyncColorCodes(this.text, pStartIdx, pLen);
-}
-// For the TextLine class: Returns the last attribute codes appearing in the line
-// before a given index, as a string.  If there are none, it will just be a
-// blank string.  The index is optional - If not provided, this method will use
-// the end index of the string.
-//
-// Parameters:
-//  pIdx: Optional - The end index of the string to use.  If not provided, this will
-//        use the last index of the string.
-//
-// Return value: Any attribute codes appearing before the given index (or last index)
-//               of the string, as a string.
-function TextLine_GetLastAttrCodes(pIdx)
-{
-	var endIdx = (typeof(pIdx) == "number" ? pIdx : this.text.length-1);
-	if (endIdx < 0)
-		return "";
-	if (endIdx >= this.text.length)
-		endIdx =  this.text.length - 1;
-	return getAttrsBeforeStrIdx(this.text, endIdx);
-}
-// For the TextLine class: Returns an object with information about any Synchronet
-// color/attribute codes found in the text line before a given index.
-//
-// Parameters:
-//  pIdx: The index in the text line to search before
-//
-// Return value: An object containing the following properties:
-//               attrStr: A string containing any Synchronet attribute codes
-//                        found before the given index in the given string.  If
-//                        none are found, this string will be empty.
-//               syncAttrStartIdx: The index of where the attribute codes start, or
-//                                 -1 if none were found
-//               syncAttrEndIdx: The index of the last character of the attribute
-//                               codes, or -1 if none were found
-function TextLine_GetAttrsAndIndexesBeforeIdx(pIdx)
-{
-	return getAttrsAndIndexesBeforeStrIdx(this.text, pIdx);
-}
 
-// For the TextLine class: Converts a printable display index to the actual index
-// of the text line, ignoring Synchronet attribute codes in the string.
-//
-// Parameters:
-//  pDisplayIdx: The string index as displayed on the screen
-//
-// Return value: The index in the actual string.  0 on error
-function TextLine_DisplayIdxToActualIdx(pDisplayIdx)
-{
-	return strDisplayIdxToActualIdx(this.text, pDisplayIdx);
-}
 
 // AbortConfirmFuncParams constructor: This object contains parameters used by
 // the abort confirmation function (actually, there are separate ones for
@@ -534,200 +415,202 @@ function ChoiceScrollbox_MinWidth()
 function ChoiceScrollbox(pLeftX, pTopY, pWidth, pHeight, pTopBorderText, pSlyEdCfgObj,
                          pAddTCharsAroundTopText, pReplaceTopTextSpacesWithBorderChars)
 {
-	// The default is to add left & right T characters around the top border
-	// text.  But also use pAddTCharsAroundTopText if it's a boolean.
-	var addTopTCharsAroundText = true;
-	if (typeof(pAddTCharsAroundTopText) == "boolean")
-		addTopTCharsAroundText = pAddTCharsAroundTopText;
-	// If pReplaceTopTextSpacesWithBorderChars is true, then replace the spaces
-	// in pTopBorderText with border characters.
-	if (pReplaceTopTextSpacesWithBorderChars)
-	{
-		var startIdx = 0;
-		var firstSpcIdx = pTopBorderText.indexOf(" ", 0);
-		// Look for the first non-space after firstSpaceIdx
-		var nonSpcIdx = -1;
-		for (var i = firstSpcIdx; (i < pTopBorderText.length) && (nonSpcIdx == -1); ++i)
-		{
-			if (pTopBorderText.charAt(i) != " ")
-				nonSpcIdx = i;
-		}
-		var firstStrPart = "";
-		var lastStrPart = "";
-		var numSpaces = 0;
-		while ((firstSpcIdx > -1) && (nonSpcIdx > -1))
-		{
-			firstStrPart = pTopBorderText.substr(startIdx, (firstSpcIdx-startIdx));
-			lastStrPart = pTopBorderText.substr(nonSpcIdx);
-			numSpaces = nonSpcIdx - firstSpcIdx;
-			if (numSpaces > 0)
-			{
-				pTopBorderText = firstStrPart + "\1n" + pSlyEdCfgObj.genColors.listBoxBorder;
-				for (var i = 0; i < numSpaces; ++i)
-					pTopBorderText += HORIZONTAL_SINGLE;
-				pTopBorderText += "\1n" + pSlyEdCfgObj.genColors.listBoxBorderText + lastStrPart;
-			}
+   // The default is to add left & right T characters around the top border
+   // text.  But also use pAddTCharsAroundTopText if it's a boolean.
+   var addTopTCharsAroundText = true;
+   if (typeof(pAddTCharsAroundTopText) == "boolean")
+      addTopTCharsAroundText = pAddTCharsAroundTopText;
+   // If pReplaceTopTextSpacesWithBorderChars is true, then replace the spaces
+   // in pTopBorderText with border characters.
+   if (pReplaceTopTextSpacesWithBorderChars)
+   {
+      var startIdx = 0;
+      var firstSpcIdx = pTopBorderText.indexOf(" ", 0);
+      // Look for the first non-space after firstSpaceIdx
+      var nonSpcIdx = -1;
+      for (var i = firstSpcIdx; (i < pTopBorderText.length) && (nonSpcIdx == -1); ++i)
+      {
+         if (pTopBorderText.charAt(i) != " ")
+            nonSpcIdx = i;
+      }
+      var firstStrPart = "";
+      var lastStrPart = "";
+      numSpaces = 0;
+      while ((firstSpcIdx > -1) && (nonSpcIdx > -1))
+      {
+         firstStrPart = pTopBorderText.substr(startIdx, (firstSpcIdx-startIdx));
+         lastStrPart = pTopBorderText.substr(nonSpcIdx);
+         numSpaces = nonSpcIdx - firstSpcIdx;
+         if (numSpaces > 0)
+         {
+            pTopBorderText = firstStrPart + "n" + pSlyEdCfgObj.genColors.listBoxBorder;
+            for (var i = 0; i < numSpaces; ++i)
+               pTopBorderText += HORIZONTAL_SINGLE;
+            pTopBorderText += "n" + pSlyEdCfgObj.genColors.listBoxBorderText + lastStrPart;
+         }
 
-			// Look for the next space and non-space character after that.
-			firstSpcIdx = pTopBorderText.indexOf(" ", nonSpcIdx);
-			// Look for the first non-space after firstSpaceIdx
-			nonSpcIdx = -1;
-			for (var i = firstSpcIdx; (i < pTopBorderText.length) && (nonSpcIdx == -1); ++i)
-			{
-				if (pTopBorderText.charAt(i) != " ")
-					nonSpcIdx = i;
-			}
-		}
-	}
+         // Look for the next space and non-space character after that.
+         firstSpcIdx = pTopBorderText.indexOf(" ", nonSpcIdx);
+         // Look for the first non-space after firstSpaceIdx
+         nonSpcIdx = -1;
+         for (var i = firstSpcIdx; (i < pTopBorderText.length) && (nonSpcIdx == -1); ++i)
+         {
+            if (pTopBorderText.charAt(i) != " ")
+               nonSpcIdx = i;
+         }
+      }
+   }
 
-	this.SlyEdCfgObj = pSlyEdCfgObj;
+   this.SlyEdCfgObj = pSlyEdCfgObj;
 
-	var minWidth = ChoiceScrollbox_MinWidth();
+   var minWidth = ChoiceScrollbox_MinWidth();
 
-	this.dimensions = new Object();
-	this.dimensions.topLeftX = pLeftX;
-	this.dimensions.topLeftY = pTopY;
-	// Make sure the width is the minimum width
-	if ((pWidth < 0) || (pWidth < minWidth))
-		this.dimensions.width = minWidth;
-	else
-		this.dimensions.width = pWidth;
-	this.dimensions.height = pHeight;
-	this.dimensions.bottomRightX = this.dimensions.topLeftX + this.dimensions.width - 1;
-	this.dimensions.bottomRightY = this.dimensions.topLeftY + this.dimensions.height - 1;
-
-	// The text item array and member variables relating to it and the items
-	// displayed on the screen during the input loop
-	this.txtItemList = new Array();
-	this.chosenTextItemIndex = -1;
-	this.topItemIndex = 0;
-	this.bottomItemIndex = 0;
-
-	// Top border string
-	var innerBorderWidth = this.dimensions.width - 2;
-	// Calculate the maximum top border text length to account for the left/right
-	// T chars and "Page #### of ####" text
-	var maxTopBorderTextLen = innerBorderWidth - (pAddTCharsAroundTopText ? 21 : 19);
-	if (strip_ctrl(pTopBorderText).length > maxTopBorderTextLen)
-	pTopBorderText = pTopBorderText.substr(0, maxTopBorderTextLen);
-		this.topBorder = "\1n" + pSlyEdCfgObj.genColors.listBoxBorder + UPPER_LEFT_SINGLE;
-	if (addTopTCharsAroundText)
-		this.topBorder += RIGHT_T_SINGLE;
-	this.topBorder += "\1n" + pSlyEdCfgObj.genColors.listBoxBorderText
-	               + pTopBorderText + "\1n" + pSlyEdCfgObj.genColors.listBoxBorder;
-	if (addTopTCharsAroundText)
-		this.topBorder += LEFT_T_SINGLE;
-	const topBorderTextLen = strip_ctrl(pTopBorderText).length;
-	var numHorizBorderChars = innerBorderWidth - topBorderTextLen - 20;
-	if (addTopTCharsAroundText)
-		numHorizBorderChars -= 2;
-	for (var i = 0; i <= numHorizBorderChars; ++i)
-		this.topBorder += HORIZONTAL_SINGLE;
-	this.topBorder += RIGHT_T_SINGLE + "\1n" + pSlyEdCfgObj.genColors.listBoxBorderText
-	               + "Page    1 of    1" + "\1n" + pSlyEdCfgObj.genColors.listBoxBorder + LEFT_T_SINGLE
-	               + UPPER_RIGHT_SINGLE;
-
-	// Bottom border string
-	this.btmBorderNavText = "nhcb, cb, cNy)bext, cPy)brev, "
-	                      + "cFy)birst, cLy)bast, cHOMEb, cENDb, cEntery=bSelect, "
-	                      + "cESCnc/hcQy=bEnd";
-	this.bottomBorder = "n" + pSlyEdCfgObj.genColors.listBoxBorder + LOWER_LEFT_SINGLE
-	                  + RIGHT_T_SINGLE + this.btmBorderNavText + "n" + pSlyEdCfgObj.genColors.listBoxBorder
-	                  + LEFT_T_SINGLE;
-	var numCharsRemaining = this.dimensions.width - strip_ctrl(this.btmBorderNavText).length - 6;
-	for (var i = 0; i < numCharsRemaining; ++i)
-		this.bottomBorder += HORIZONTAL_SINGLE;
-	this.bottomBorder += LOWER_RIGHT_SINGLE;
-
-	// Item format strings
-	this.listIemFormatStr = "n" + pSlyEdCfgObj.genColors.listBoxItemText + "%-" + +(this.dimensions.width-2) + "s";
-	this.listIemHighlightFormatStr = "n" + pSlyEdCfgObj.genColors.listBoxItemHighlight + "%-" + +(this.dimensions.width-2) + "s";
-
-	// Key functionality override function pointers
-	this.enterKeyOverrideFn = null;
-
-	// inputLoopeExitKeys is an object containing additional keypresses that will
-	// exit the input loop.
-	this.inputLoopExitKeys = new Object();
-
-	// "Class" functions
-	this.addTextItem = ChoiceScrollbox_AddTextItem; // Returns the index of the item
-	this.getTextItem = ChoiceScrollbox_GetTextIem;
-	this.replaceTextItem = ChoiceScrollbox_ReplaceTextItem;
-	this.delTextItem = ChoiceScrollbox_DelTextItem;
-	this.chgCharInTextItem = ChoiceScrollbox_ChgCharInTextItem;
-	this.getChosenTextItemIndex = ChoiceScrollbox_GetChosenTextItemIndex;
-	this.setItemArray = ChoiceScrollbox_SetItemArray; // Sets the item array; returns whether or not it was set.
-	this.clearItems = ChoiceScrollbox_ClearItems; // Empties the array of items
-	this.setEnterKeyOverrideFn = ChoiceScrollbox_SetEnterKeyOverrideFn;
-	this.clearEnterKeyOverrideFn = ChoiceScrollbox_ClearEnterKeyOverrideFn;
-	this.addInputLoopExitKey = ChoiceScrollbox_AddInputLoopExitKey;
-	this.setBottomBorderText = ChoiceScrollbox_SetBottomBorderText;
-	this.drawBorder = ChoiceScrollbox_DrawBorder;
-	this.refreshItemCharOnScreen = ChoiceScrollbox_RefreshItemCharOnScreen;
-	// Does the input loop.  Returns an object with the following properties:
-	//  itemWasSelected: Boolean - Whether or not an item was selected
-	//  selectedIndex: The index of the selected item
-	//  selectedItem: The text of the selected item
-	//  lastKeypress: The last key pressed by the user
-	this.doInputLoop = ChoiceScrollbox_DoInputLoop;
+   this.dimensions = new Object();
+   this.dimensions.topLeftX = pLeftX;
+   this.dimensions.topLeftY = pTopY;
+   // Make sure the width is the minimum width
+   if ((pWidth < 0) || (pWidth < minWidth))
+      this.dimensions.width = minWidth;
+   else
+      this.dimensions.width = pWidth;
+   this.dimensions.height = pHeight;
+   this.dimensions.bottomRightX = this.dimensions.topLeftX + this.dimensions.width - 1;
+   this.dimensions.bottomRightY = this.dimensions.topLeftY + this.dimensions.height - 1;
+
+   // The text item array and member variables relating to it and the items
+   // displayed on the screen during the input loop
+   this.txtItemList = new Array();
+   this.chosenTextItemIndex = -1;
+   this.topItemIndex = 0;
+   this.bottomItemIndex = 0;
+
+   // Top border string
+   var innerBorderWidth = this.dimensions.width - 2;
+   // Calculate the maximum top border text length to account for the left/right
+   // T chars and "Page #### of ####" text
+   var maxTopBorderTextLen = innerBorderWidth - (pAddTCharsAroundTopText ? 21 : 19);
+   if (strip_ctrl(pTopBorderText).length > maxTopBorderTextLen)
+      pTopBorderText = pTopBorderText.substr(0, maxTopBorderTextLen);
+   this.topBorder = "n" + pSlyEdCfgObj.genColors.listBoxBorder + UPPER_LEFT_SINGLE;
+   if (addTopTCharsAroundText)
+      this.topBorder += RIGHT_T_SINGLE;
+   this.topBorder += "n" + pSlyEdCfgObj.genColors.listBoxBorderText
+     + pTopBorderText + "n" + pSlyEdCfgObj.genColors.listBoxBorder;
+   if (addTopTCharsAroundText)
+      this.topBorder += LEFT_T_SINGLE;
+   const topBorderTextLen = strip_ctrl(pTopBorderText).length;
+   var numHorizBorderChars = innerBorderWidth - topBorderTextLen - 20;
+   if (addTopTCharsAroundText)
+      numHorizBorderChars -= 2;
+   for (var i = 0; i <= numHorizBorderChars; ++i)
+      this.topBorder += HORIZONTAL_SINGLE;
+   this.topBorder += RIGHT_T_SINGLE + "n" + pSlyEdCfgObj.genColors.listBoxBorderText
+     + "Page    1 of    1" + "n" + pSlyEdCfgObj.genColors.listBoxBorder + LEFT_T_SINGLE
+     + UPPER_RIGHT_SINGLE;
+
+   // Bottom border string
+   this.btmBorderNavText = "nhcb, cb, cNy)bext, cPy)brev, "
+     + "cFy)birst, cLy)bast, cHOMEb, cENDb, cEntery=bSelect, "
+     + "cESCnc/hcQy=bEnd";
+   this.bottomBorder = "n" + pSlyEdCfgObj.genColors.listBoxBorder + LOWER_LEFT_SINGLE
+     + RIGHT_T_SINGLE + this.btmBorderNavText + "n" + pSlyEdCfgObj.genColors.listBoxBorder
+     + LEFT_T_SINGLE;
+   var numCharsRemaining = this.dimensions.width - strip_ctrl(this.btmBorderNavText).length - 6;
+   for (var i = 0; i < numCharsRemaining; ++i)
+      this.bottomBorder += HORIZONTAL_SINGLE;
+   this.bottomBorder += LOWER_RIGHT_SINGLE;
+
+   // Item format strings
+   this.listIemFormatStr = "n" + pSlyEdCfgObj.genColors.listBoxItemText + "%-"
+                          + +(this.dimensions.width-2) + "s";
+   this.listIemHighlightFormatStr = "n" + pSlyEdCfgObj.genColors.listBoxItemHighlight + "%-"
+                          + +(this.dimensions.width-2) + "s";
+
+   // Key functionality override function pointers
+   this.enterKeyOverrideFn = null;
+
+   // inputLoopeExitKeys is an object containing additional keypresses that will
+   // exit the input loop.
+   this.inputLoopExitKeys = new Object();
+
+   // "Class" functions
+   this.addTextItem = ChoiceScrollbox_AddTextItem; // Returns the index of the item
+   this.getTextItem = ChoiceScrollbox_GetTextIem;
+   this.replaceTextItem = ChoiceScrollbox_ReplaceTextItem;
+   this.delTextItem = ChoiceScrollbox_DelTextItem;
+   this.chgCharInTextItem = ChoiceScrollbox_ChgCharInTextItem;
+   this.getChosenTextItemIndex = ChoiceScrollbox_GetChosenTextItemIndex;
+   this.setItemArray = ChoiceScrollbox_SetItemArray; // Sets the item array; returns whether or not it was set.
+   this.clearItems = ChoiceScrollbox_ClearItems; // Empties the array of items
+   this.setEnterKeyOverrideFn = ChoiceScrollbox_SetEnterKeyOverrideFn;
+   this.clearEnterKeyOverrideFn = ChoiceScrollbox_ClearEnterKeyOverrideFn;
+   this.addInputLoopExitKey = ChoiceScrollbox_AddInputLoopExitKey;
+   this.setBottomBorderText = ChoiceScrollbox_SetBottomBorderText;
+   this.drawBorder = ChoiceScrollbox_DrawBorder;
+   this.refreshItemCharOnScreen = ChoiceScrollbox_RefreshItemCharOnScreen;
+   // Does the input loop.  Returns an object with the following properties:
+   //  itemWasSelected: Boolean - Whether or not an item was selected
+   //  selectedIndex: The index of the selected item
+   //  selectedItem: The text of the selected item
+   //  lastKeypress: The last key pressed by the user
+   this.doInputLoop = ChoiceScrollbox_DoInputLoop;
 }
 function ChoiceScrollbox_AddTextItem(pTextLine, pStripCtrl)
 {
-	var stripCtrl = true;
-	if (typeof(pStripCtrl) == "boolean")
-		stripCtrl = pStripCtrl;
+   var stripCtrl = true;
+   if (typeof(pStripCtrl) == "boolean")
+      stripCtrl = pStripCtrl;
 
-	if (stripCtrl)
-		this.txtItemList.push(strip_ctrl(pTextLine));
-	else
-		this.txtItemList.push(pTextLine);
-	// Return the index of the added item
-	return this.txtItemList.length-1;
+   if (stripCtrl)
+      this.txtItemList.push(strip_ctrl(pTextLine));
+   else
+      this.txtItemList.push(pTextLine);
+   // Return the index of the added item
+   return this.txtItemList.length-1;
 }
 function ChoiceScrollbox_GetTextIem(pItemIndex)
 {
-	if (typeof(pItemIndex) != "number")
-		return "";
-	if ((pItemIndex < 0) || (pItemIndex >= this.txtItemList.length))
-		return "";
+   if (typeof(pItemIndex) != "number")
+      return "";
+   if ((pItemIndex < 0) || (pItemIndex >= this.txtItemList.length))
+      return "";
 
-	return this.txtItemList[pItemIndex];
+   return this.txtItemList[pItemIndex];
 }
 function ChoiceScrollbox_ReplaceTextItem(pItemIndexOrStr, pNewItem)
 {
-	if (typeof(pNewItem) != "string")
-		return false;
+   if (typeof(pNewItem) != "string")
+      return false;
 
-	// Find the item index
-	var itemIndex = -1;
-	if (typeof(pItemIndexOrStr) == "number")
-	{
-		if ((pItemIndexOrStr < 0) || (pItemIndexOrStr >= this.txtItemList.length))
-			return false;
-		else
-			itemIndex = pItemIndexOrStr;
-	}
-	else if (typeof(pItemIndexOrStr) == "string")
-	{
-		itemIndex = -1;
-		for (var i = 0; (i < this.txtItemList.length) && (itemIndex == -1); ++i)
-		{
-			if (this.txtItemList[i] == pItemIndexOrStr)
-				itemIndex = i;
-		}
-	}
-	else
-		return false;
+   // Find the item index
+   var itemIndex = -1;
+   if (typeof(pItemIndexOrStr) == "number")
+   {
+      if ((pItemIndexOrStr < 0) || (pItemIndexOrStr >= this.txtItemList.length))
+         return false;
+      else
+         itemIndex = pItemIndexOrStr;
+   }
+   else if (typeof(pItemIndexOrStr) == "string")
+   {
+      itemIndex = -1;
+      for (var i = 0; (i < this.txtItemList.length) && (itemIndex == -1); ++i)
+      {
+         if (this.txtItemList[i] == pItemIndexOrStr)
+            itemIndex = i;
+      }
+   }
+   else
+      return false;
 
-	// Replace the item
-	var replacedIt = false;
-	if ((itemIndex > -1) && (itemIndex < this.txtItemList.length))
-	{
-		this.txtItemList[itemIndex] = pNewItem;
-			replacedIt = true;
-	}
-	return replacedIt;
+   // Replace the item
+   var replacedIt = false;
+   if ((itemIndex > -1) && (itemIndex < this.txtItemList.length))
+   {
+      this.txtItemList[itemIndex] = pNewItem;
+      replacedIt = true;
+   }
+   return replacedIt;
 }
 function ChoiceScrollbox_DelTextItem(pItemIndexOrStr)
 {
@@ -845,75 +728,75 @@ function ChoiceScrollbox_AddInputLoopExitKey(pKeypress)
 }
 function ChoiceScrollbox_SetBottomBorderText(pText, pAddTChars, pAutoStripIfTooLong)
 {
-	if (typeof(pText) != "string")
-		return;
+   if (typeof(pText) != "string")
+      return;
 
-	const innerWidth = (pAddTChars ? this.dimensions.width-4 : this.dimensions.width-2);
+   const innerWidth = (pAddTChars ? this.dimensions.width-4 : this.dimensions.width-2);
 
-	if (pAutoStripIfTooLong)
-	{
-		if (strip_ctrl(pText).length > innerWidth)
-			pText = pText.substr(0, innerWidth);
-	}
+   if (pAutoStripIfTooLong)
+   {
+      if (strip_ctrl(pText).length > innerWidth)
+         pText = pText.substr(0, innerWidth);
+   }
 
-	// Re-build the bottom border string based on the new text
-	this.bottomBorder = "n" + this.SlyEdCfgObj.genColors.listBoxBorder + LOWER_LEFT_SINGLE;
-	if (pAddTChars)
-		this.bottomBorder += RIGHT_T_SINGLE;
-	if (pText.indexOf("n") != 0)
-		this.bottomBorder += "n";
-	this.bottomBorder += pText + "n" + this.SlyEdCfgObj.genColors.listBoxBorder;
-		if (pAddTChars)
-	this.bottomBorder += LEFT_T_SINGLE;
-	var numCharsRemaining = this.dimensions.width - strip_ctrl(this.bottomBorder).length - 3;
-	for (var i = 0; i < numCharsRemaining; ++i)
-		this.bottomBorder += HORIZONTAL_SINGLE;
-	this.bottomBorder += LOWER_RIGHT_SINGLE;
+   // Re-build the bottom border string based on the new text
+   this.bottomBorder = "n" + this.SlyEdCfgObj.genColors.listBoxBorder + LOWER_LEFT_SINGLE;
+   if (pAddTChars)
+      this.bottomBorder += RIGHT_T_SINGLE;
+   if (pText.indexOf("n") != 0)
+      this.bottomBorder += "n";
+   this.bottomBorder += pText + "n" + this.SlyEdCfgObj.genColors.listBoxBorder;
+   if (pAddTChars)
+      this.bottomBorder += LEFT_T_SINGLE;
+   var numCharsRemaining = this.dimensions.width - strip_ctrl(this.bottomBorder).length - 3;
+   for (var i = 0; i < numCharsRemaining; ++i)
+      this.bottomBorder += HORIZONTAL_SINGLE;
+   this.bottomBorder += LOWER_RIGHT_SINGLE;
 }
 function ChoiceScrollbox_DrawBorder()
 {
-	console.gotoxy(this.dimensions.topLeftX, this.dimensions.topLeftY);
-	console.print(this.topBorder);
-	// Draw the side border characters
-	var screenRow = this.dimensions.topLeftY + 1;
-	for (var screenRow = this.dimensions.topLeftY+1; screenRow <= this.dimensions.bottomRightY-1; ++screenRow)
-	{
-		console.gotoxy(this.dimensions.topLeftX, screenRow);
-		console.print(VERTICAL_SINGLE);
-		console.gotoxy(this.dimensions.bottomRightX, screenRow);
-		console.print(VERTICAL_SINGLE);
-	}
-	// Draw the bottom border
-	console.gotoxy(this.dimensions.topLeftX, this.dimensions.bottomRightY);
-	console.print(this.bottomBorder);
+   console.gotoxy(this.dimensions.topLeftX, this.dimensions.topLeftY);
+   console.print(this.topBorder);
+   // Draw the side border characters
+   var screenRow = this.dimensions.topLeftY + 1;
+   for (var screenRow = this.dimensions.topLeftY+1; screenRow <= this.dimensions.bottomRightY-1; ++screenRow)
+   {
+      console.gotoxy(this.dimensions.topLeftX, screenRow);
+      console.print(VERTICAL_SINGLE);
+      console.gotoxy(this.dimensions.bottomRightX, screenRow);
+      console.print(VERTICAL_SINGLE);
+   }
+   // Draw the bottom border
+   console.gotoxy(this.dimensions.topLeftX, this.dimensions.bottomRightY);
+   console.print(this.bottomBorder);
 }
 function ChoiceScrollbox_RefreshItemCharOnScreen(pItemIndex, pCharIndex)
 {
-	if ((typeof(pItemIndex) != "number") || (typeof(pCharIndex) != "number"))
-		return;
-	if ((pItemIndex < 0) || (pItemIndex >= this.txtItemList.length) ||
-	    (pItemIndex < this.topItemIndex) || (pItemIndex > this.bottomItemIndex))
-	{
-		return;
-	}
-	if ((pCharIndex < 0) || (pCharIndex >= this.txtItemList[pItemIndex].length))
-		return;
-
-	// Save the current cursor position so that we can restore it later
-	const originalCurpos = console.getxy();
-	// Go to the character's position on the screen and set the highlight or
-	// normal color, depending on whether the item is the currently selected item,
-	// then print the character on the screen.
-	const charScreenX = this.dimensions.topLeftX + 1 + pCharIndex;
-	const itemScreenY = this.dimensions.topLeftY + 1 + (pItemIndex - this.topItemIndex);
-	console.gotoxy(charScreenX, itemScreenY);
-	if (pItemIndex == this.chosenTextItemIndex)
-		console.print(this.SlyEdCfgObj.genColors.listBoxItemHighlight);
-	else
-		console.print(this.SlyEdCfgObj.genColors.listBoxItemText);
-	console.print(this.txtItemList[pItemIndex].charAt(pCharIndex));
-	// Move the cursor back to where it was originally
-	console.gotoxy(originalCurpos);
+   if ((typeof(pItemIndex) != "number") || (typeof(pCharIndex) != "number"))
+      return;
+   if ((pItemIndex < 0) || (pItemIndex >= this.txtItemList.length) ||
+       (pItemIndex < this.topItemIndex) || (pItemIndex > this.bottomItemIndex))
+   {
+      return;
+   }
+   if ((pCharIndex < 0) || (pCharIndex >= this.txtItemList[pItemIndex].length))
+      return;
+
+   // Save the current cursor position so that we can restore it later
+   const originalCurpos = console.getxy();
+   // Go to the character's position on the screen and set the highlight or
+   // normal color, depending on whether the item is the currently selected item,
+   // then print the character on the screen.
+   const charScreenX = this.dimensions.topLeftX + 1 + pCharIndex;
+   const itemScreenY = this.dimensions.topLeftY + 1 + (pItemIndex - this.topItemIndex);
+   console.gotoxy(charScreenX, itemScreenY);
+   if (pItemIndex == this.chosenTextItemIndex)
+      console.print(this.SlyEdCfgObj.genColors.listBoxItemHighlight);
+   else
+      console.print(this.SlyEdCfgObj.genColors.listBoxItemText);
+   console.print(this.txtItemList[pItemIndex].charAt(pCharIndex));
+   // Move the cursor back to where it was originally
+   console.gotoxy(originalCurpos);
 }
 function ChoiceScrollbox_DoInputLoop(pDrawBorder)
 {
@@ -1224,12 +1107,17 @@ function ChoiceScrollbox_DoInputLoop(pDrawBorder)
 function randomDimBrightString(pString, pColor)
 {
 	// Return if an invalid string is passed in.
+	if (pString == null)
+		return "";
 	if (typeof(pString) != "string")
 		return "";
 
-	// Set the color.  Default to green.
-	var color = (typeof(pColor) == "string" ? pColor : "\1g");
-	return(randomTwoColorString(pString, "\1n" + color, "\1n\1h" + color));
+   // Set the color.  Default to green.
+	var color = "g";
+	if ((pColor != null) && (typeof(pColor) != "undefined"))
+      color = pColor;
+
+   return(randomTwoColorString(pString, "n" + color, "nh" + color));
 }
 
 // This function takes a string and returns a copy of the string
@@ -1239,18 +1127,21 @@ function randomDimBrightString(pString, pColor)
 //  pString: The string to convert
 //  pColor11: The first color to use (Synchronet color code)
 //  pColor12: The second color to use (Synchronet color code)
-//
-// Return value: The string passed in, with the 2 colors used randomly for
-//               each character
 function randomTwoColorString(pString, pColor1, pColor2)
 {
 	// Return if an invalid string is passed in.
+	if (pString == null)
+		return "";
 	if (typeof(pString) != "string")
 		return "";
 
 	// Set the colors.  Default to green.
-	var color1 = (typeof(pColor1) == "string" ? pColor1 : "\1n\1g");
-	var color2 = (typeof(pColor2) == "string" ? pColor2 : "\1n\1g\1h");
+	var color1 = "ng";
+	if ((pColor1 != null) && (typeof(pColor1) != "undefined"))
+      color1 = pColor1;
+   var color2 = "ngh";
+	if ((pColor2 != null) && (typeof(pColor2) != "undefined"))
+      color2 = pColor2;
 
 	// Create a copy of the string without any control characters,
 	// and then add our coloring to it.
@@ -1263,7 +1154,7 @@ function randomTwoColorString(pString, pColor1, pColor2)
 		// Determine if this character should be useColor1
 		useColor1 = (Math.floor(Math.random()*2) == 1);
 		if (useColor1 != oldUseColor1)
-			returnString += (useColor1 ? color1 : color2);
+         returnString += (useColor1 ? color1 : color2);
 
 		// Append the character from pString.
 		returnString += pString.charAt(i);
@@ -1277,22 +1168,26 @@ function randomTwoColorString(pString, pColor1, pColor2)
 // Returns the current time as a string, to be displayed on the screen.
 function getCurrentTimeStr()
 {
-	return strftime("%I:%M%p", time()).replace("AM", "a").replace("PM", "p");
+	var timeStr = strftime("%I:%M%p", time());
+	timeStr = timeStr.replace("AM", "a");
+	timeStr = timeStr.replace("PM", "p");
+	
+	return timeStr;
 }
 
 // Returns whether or not a character is printable.
 function isPrintableChar(pText)
 {
-	// Make sure pText is valid and is a string.
-	if (typeof(pText) != "string")
-		return false;
-	if (pText.length == 0)
-		return false;
+   // Make sure pText is valid and is a string.
+   if (typeof(pText) != "string")
+      return false;
+   if (pText.length == 0)
+      return false;
 
-	// Make sure the character is a printable ASCII character in the range of 32 to 254,
-	// except for 127 (delete).
-	var charCode = pText.charCodeAt(0);
-	return ((charCode > 31) && (charCode < 255) && (charCode != 127));
+   // Make sure the character is a printable ASCII character in the range of 32 to 254,
+   // except for 127 (delete).
+   var charCode = pText.charCodeAt(0);
+   return ((charCode > 31) && (charCode < 255) && (charCode != 127));
 }
 
 // Removes multiple, leading, and/or trailing spaces
@@ -1381,8 +1276,11 @@ function displayHelpHeader()
 //  pClear: Whether or not to clear the screen first
 //  pPause: Whether or not to pause at the end
 //  pCanCrossPost: Whether or not cross-posting is enabled
-//  pConfigSettings: The SlyEdit configuration settings object
-function displayCommandList(pDisplayHeader, pClear, pPause, pCanCrossPost, pConfigSettings)
+//  pIsSysop: Whether or not the user is the sysop.
+//  pTxtReplacments: Whether or not the text replacements feature is enabled
+//  pUserSettings: Whether or not the user settings feature is enabled
+function displayCommandList(pDisplayHeader, pClear, pPause, pCanCrossPost, pIsSysop,
+                             pTxtReplacments, pUserSettings)
 {
 	if (pClear)
 		console.clear("\1n");
@@ -1392,7 +1290,7 @@ function displayCommandList(pDisplayHeader, pClear, pPause, pCanCrossPost, pConf
 		console.crlf();
 	}
 
-	var isSysop = (pConfigSettings.userIsSysop != null ? pConfigSettings.userIsSysop : user.compare_ars("SYSOP"));
+	var isSysop = (pIsSysop != null ? pIsSysop : user.compare_ars("SYSOP"));
 
 	// This function displays a key and its description with formatting & colors.
 	//
@@ -1428,9 +1326,9 @@ function displayCommandList(pDisplayHeader, pClear, pPause, pCanCrossPost, pConf
 	displayCmdKeyFormattedDouble("Ctrl-G", "General help", "/A", "Abort", true);
 	displayCmdKeyFormattedDouble("Ctrl-L", "Command key list (this list)", "/S", "Save", true);
 	displayCmdKeyFormattedDouble("Ctrl-R", "Program information", "/Q", "Quote message", true);
-	if (pConfigSettings.enableTextReplacements)
+	if (pTxtReplacments)
 		displayCmdKeyFormattedDouble("Ctrl-T", "List text replacements", "/T", "List text replacements", true);
-	if (pConfigSettings.allowUserSettings)
+	if (pUserSettings)
 		displayCmdKeyFormattedDouble("", "", "/U", "Your settings", true);
 	if (pCanCrossPost)
 		displayCmdKeyFormattedDouble("", "", "/C", "Cross-post selection", true);
@@ -1447,18 +1345,12 @@ function displayCommandList(pDisplayHeader, pClear, pPause, pCanCrossPost, pConf
 		displayCmdKeyFormattedDouble("ESC", "Command menu", "Ctrl-C", "Cross-post selection", true);
 	else
 		displayCmdKeyFormatted("ESC", "Command menu", true);
-	var nextKeys = [];
-	if (pConfigSettings.allowUserSettings)
-		nextKeys.push({keyStr: "Ctrl-U", desc: "Your settings"});
-	if (gConfigSettings.allowColorSelection)
-		nextKeys.push({keyStr: "Ctrl-K", desc: "Choose text color"});
-	if (nextKeys.length == 1)
-		displayCmdKeyFormatted(nextKeys[0].keyStr, nextKeys[0].desc, true);
-	else if (nextKeys.length == 2)
-		displayCmdKeyFormattedDouble(nextKeys[0].keyStr, nextKeys[0].desc, nextKeys[1].keyStr, nextKeys[1].desc, true);
 	if (isSysop)
 		displayCmdKeyFormattedDouble("Ctrl-O", "Import a file", "Ctrl-X", "Export to file", true);
 
+	if (pUserSettings)
+		displayCmdKeyFormatted("Ctrl-U", "Your settings", true);
+
 	if (pPause)
 	{
 		// TODO: I doubt this needs consolePauseWithESCChars() anymore..
@@ -1627,6 +1519,7 @@ function promptYesNo(pQuestion, pDefaultYes, pBoxTitle, pIceRefreshForBothAnswer
 function ReadSlyEditConfigFile()
 {
 	var cfgObj = new Object(); // Configuration object
+
 	cfgObj.userIsSysop = user.compare_ars("SYSOP"); // Whether or not the user is a sysop
 	// Default settings
 	cfgObj.thirdPartyLoadOnStart = new Array();
@@ -1638,10 +1531,6 @@ function ReadSlyEditConfigFile()
 	cfgObj.inputTimeoutMS = 300000;
 	cfgObj.reWrapQuoteLines = true;
 	cfgObj.allowColorSelection = true;
-	cfgObj.noColorSelectionGrpNames = [];
-	cfgObj.noColorSelectionSubBoardCodes = [];
-	cfgObj.cvtColorToANSIGrpNames = [];
-	cfgObj.cvtColorToANSISubBoardCodes = [];
 	cfgObj.useQuoteLineInitials = true;
 	cfgObj.indentQuoteLinesWithInitials = true;
 	cfgObj.allowCrossPosting = true;
@@ -1685,6 +1574,8 @@ function ReadSlyEditConfigFile()
 	cfgObj.iceColors.menuOptClassicColors = true;
 	// Ice color theme file
 	cfgObj.iceColors.ThemeFilename = genFullPathCfgFilename("SlyIceColors_BlueIce.cfg", gStartupPath);
+	// Text edit color
+	cfgObj.iceColors.TextEditColor = "\1n\1w";
 	// Quote line color
 	cfgObj.iceColors.QuoteLineColor = "\1n\1c";
 	// Ice colors for the quote window
@@ -1714,6 +1605,8 @@ function ReadSlyEditConfigFile()
 	cfgObj.DCTColors = new Object();
 	// DCT color theme file
 	cfgObj.DCTColors.ThemeFilename = genFullPathCfgFilename("SlyDCTColors_Default.cfg", gStartupPath);
+	// Text edit color
+	cfgObj.DCTColors.TextEditColor = "\1n\1w";
 	// Quote line color
 	cfgObj.DCTColors.QuoteLineColor = "\1n\1c";
 	// DCT colors for the border stuff
@@ -1847,49 +1740,6 @@ function ReadSlyEditConfigFile()
 						cfgObj.reWrapQuoteLines = (valueUpper == "TRUE");
 					else if (settingUpper == "ALLOWCOLORSELECTION")
 						cfgObj.allowColorSelection = (valueUpper == "TRUE");
-					else if (settingUpper == "NOCOLORSELECTIONGRPNAMES")
-					{
-						// Message group names for message groups where text
-						// color selection isn't allowed.  Split on commas.
-						// I was originally going to have this be a list of
-						// numbers for the group numbers/indexes and check
-						// against msg_area.grp_list, but that group list could
-						// be different for different users, depending on access
-						// requirements.
-						cfgObj.noColorSelectionGrpNames = valueUpper.split(",");
-					}
-					else if (settingUpper == "NOCOLORSELECTIONSUBBOARDCODES")
-					{
-						// Sub-board codes for sub-boards where text color selection
-						// isn't allowed.  Split on commas, and convert all to
-						// lowercase, since sub-board codes need to be lowercase.
-						var values = value.toLowerCase().split(",");
-						for (var i = 0; i < values.length; ++i)
-						{
-							if (msg_area.sub.hasOwnProperty(values[i]))
-								cfgObj.noColorSelectionSubBoardCodes.push(values[i]);
-						}
-					}
-					else if (settingUpper == "CVTCOLORTOANSIGRPNAMES")
-					{
-						if ((value == "*") || (valueUpper == "ALL"))
-						{
-							for (var i = 0; i < msg_area.grp_list.length; ++i)
-								cfgObj.cvtColorToANSIGrpNames.push(msg_area.grp_list[i].name.toUpperCase());
-							cfgObj.cvtColorToANSIGrpNames.push("ELECTRONIC MAIL");
-						}
-						else
-							cfgObj.cvtColorToANSIGrpNames = valueUpper.split(",");
-					}
-					else if (settingUpper == "CVTCOLORTOANSISUBBOARDCODES")
-					{
-						var values = value.toLowerCase().split(",");
-						for (var i = 0; i < values.length; ++i)
-						{
-							if (msg_area.sub.hasOwnProperty(values[i]))
-								cfgObj.cvtColorToANSISubBoardCodes.push(values[i]);
-						}
-					}
 					else if (settingUpper == "USEQUOTELINEINITIALS")
 						cfgObj.useQuoteLineInitials = (valueUpper == "TRUE");
 					else if (settingUpper == "INDENTQUOTELINESWITHINITIALS")
@@ -1965,75 +1815,73 @@ function ReadSlyEditConfigFile()
 //  pFilename: The name of the configuration file.
 //  pLineReadLen: The maximum number of characters to read from each
 //                line.  This is optional; if not specified, then up
-//                to 2048 characters will be read from each line.
-//  pCvtSyncColorAttrChar: Optional boolean - Whether or not to convert
-//                         \1 in color values to ASCII char 1 in color values
+//                to 512 characters will be read from each line.
 //
 // Return value: An Object containing the value=setting pairs.  If the
 //               file can't be opened or no settings can be read, then
 //               this function will return null.
-function readValueSettingConfigFile(pFilename, pLineReadLen, pCvtSyncColorAttrChar)
+function readValueSettingConfigFile(pFilename, pLineReadLen)
 {
-	var retObj = null;
+   var retObj = null;
 
-	var cfgFile = new File(pFilename);
-	if (cfgFile.open("r"))
-	{
-		var cvtSyncColorAttrChar = (typeof(pCvtSyncColorAttrChar) == "boolean" ? pCvtSyncColorAttrChar : false);
-		var numCharsPerLine = (typeof(pLineReadLen) == "number" ? pLineReadLen : 2048);
-
-		var fileLine = null;     // A line read from the file
-		var equalsPos = 0;       // Position of a = in the line
-		var commentPos = 0;      // Position of the start of a comment
-		var setting = null;      // A setting name (string)
-		var settingUpper = null; // Upper-case setting name
-		var value = null;        // A value for a setting (string)
-		var valueUpper = null;   // Upper-cased value
-		while (!cfgFile.eof)
-		{
-			// Read the next line from the config file.
-			fileLine = cfgFile.readln(numCharsPerLine);
-
-			// fileLine should be a string, but I've seen some cases
-			// where it isn't, so check its type.
-			if (typeof(fileLine) != "string")
-				continue;
-
-			// If the line starts with with a semicolon (the comment
-			// character) or is blank, then skip it.
-			if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
-				continue;
+   var cfgFile = new File(pFilename);
+   if (cfgFile.open("r"))
+   {
+      // Set the number of characters to read per line.
+      var numCharsPerLine = 512;
+      if (pLineReadLen != null)
+         numCharsPerLine = pLineReadLen;
+
+      var fileLine = null;     // A line read from the file
+      var equalsPos = 0;       // Position of a = in the line
+      var commentPos = 0;      // Position of the start of a comment
+      var setting = null;      // A setting name (string)
+      var settingUpper = null; // Upper-case setting name
+      var value = null;        // A value for a setting (string)
+      var valueUpper = null;   // Upper-cased value
+      while (!cfgFile.eof)
+      {
+         // Read the next line from the config file.
+         fileLine = cfgFile.readln(numCharsPerLine);
 
-			// If the line has a semicolon anywhere in it, then remove
-			// everything from the semicolon onward.
-			commentPos = fileLine.indexOf(";");
-			if (commentPos > -1)
-				fileLine = fileLine.substr(0, commentPos);
+         // fileLine should be a string, but I've seen some cases
+         // where it isn't, so check its type.
+         if (typeof(fileLine) != "string")
+            continue;
 
-			// Look for an equals sign, and if found, separate the line
-			// into the setting name (before the =) and the value (after the
-			// equals sign).
-			equalsPos = fileLine.indexOf("=");
-			if (equalsPos > 0)
-			{
-				// If retObj hasn't been created yet, then create it.
-				if (retObj == null)
-					retObj = new Object();
+         // If the line starts with with a semicolon (the comment
+         // character) or is blank, then skip it.
+         if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
+            continue;
 
-				// Read the setting & value, and trim leading & trailing spaces.  Then
-				// set the value in retObj.
-				setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
-				value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
-				if (cvtSyncColorAttrChar) // Replace \1 with the SOH character, for Sync color codes
-					value = value.replace(/\\1/g, "\1");
-				retObj[setting] = value;
-			}
-		}
+         // If the line has a semicolon anywhere in it, then remove
+         // everything from the semicolon onward.
+         commentPos = fileLine.indexOf(";");
+         if (commentPos > -1)
+            fileLine = fileLine.substr(0, commentPos);
+
+         // Look for an equals sign, and if found, separate the line
+         // into the setting name (before the =) and the value (after the
+         // equals sign).
+         equalsPos = fileLine.indexOf("=");
+         if (equalsPos > 0)
+         {
+            // If retObj hasn't been created yet, then create it.
+            if (retObj == null)
+               retObj = new Object();
+
+            // Read the setting & value, and trim leading & trailing spaces.  Then
+            // set the value in retObj.
+            setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
+            value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
+            retObj[setting] = value;
+         }
+      }
 
-		cfgFile.close();
-	}
+      cfgFile.close();
+   }
 
-	return retObj;
+   return retObj;
 }
 
 // Splits a string up by a maximum length, preserving whole words.
@@ -2111,25 +1959,24 @@ function splitStrStable(pStr, pMaxLen)
 // Return value: The spliced string
 function spliceIntoStr(pStr, pIndex, pStr2)
 {
-	// Error checking
-	var typeofPStr = typeof(pStr);
-	var typeofPStr2 = typeof(pStr2);
-	if ((typeofPStr != "string") && (typeofPStr2 != "string"))
-		return "";
-	else if ((typeofPStr == "string") && (typeofPStr2 != "string"))
-		return pStr;
-	else if ((typeofPStr != "string") && (typeofPStr2 == "string"))
-		return pStr2;
-	// If pIndex is beyond the last index of pStr, then just return the
-	// two strings concatenated.
-	if (pIndex >= pStr.length)
-		return (pStr + pStr2);
-	// If pIndex is below 0, then just return pStr2 + pStr.
-	else if (pIndex < 0)
-		return (pStr2 + pStr);
-
-	//return (pStr.substr(0, pIndex) + pStr2 + pStr.substr(pIndex));
-	return (pStr.substr(0, pIndex) + pStr2 + pStr.substr(pIndex));
+   // Error checking
+   var typeofPStr = typeof(pStr);
+   var typeofPStr2 = typeof(pStr2);
+   if ((typeofPStr != "string") && (typeofPStr2 != "string"))
+      return "";
+   else if ((typeofPStr == "string") && (typeofPStr2 != "string"))
+      return pStr;
+   else if ((typeofPStr != "string") && (typeofPStr2 == "string"))
+      return pStr2;
+   // If pIndex is beyond the last index of pStr, then just return the
+   // two strings concatenated.
+   if (pIndex >= pStr.length)
+      return (pStr + pStr2);
+   // If pIndex is below 0, then just return pStr2 + pStr.
+   else if (pIndex < 0)
+      return (pStr2 + pStr);
+
+   return (pStr.substr(0, pIndex) + pStr2 + pStr.substr(pIndex));
 }
 
 // Fixes the text lines in the gEditLines array so that they all
@@ -2144,177 +1991,170 @@ function spliceIntoStr(pStr, pIndex, pStr2)
 // Return value: Boolean - Whether or not any text was changed.
 function reAdjustTextLines(pTextLineArray, pStartIndex, pEndIndex, pEditWidth)
 {
-	// Returns without doing anything if any of the parameters are not
-	// what they should be. (Note: Not checking pTextLineArray for now..)
-	if ((typeof(pStartIndex) != "number") || (typeof(pEndIndex) != "number") || (typeof(pEditWidth) != "number"))
-		return false;
-	// Range checking
-	if ((pStartIndex < 0) || (pStartIndex >= pTextLineArray.length))
-		return false;
-	if ((pEndIndex <= pStartIndex) || (pEndIndex < 0))
-		return false;
-	if (pEndIndex > pTextLineArray.length)
-		pEndIndex = pTextLineArray.length;
-	if (pEditWidth <= 5)
-		return false;
-
-	var textChanged = false; // We'll return this at the end of the function
+   // Returns without doing anything if any of the parameters are not
+   // what they should be. (Note: Not checking pTextLineArray for now..)
+   if (typeof(pStartIndex) != "number")
+      return false;
+   if (typeof(pEndIndex) != "number")
+      return false;
+   if (typeof(pEditWidth) != "number")
+      return false;
+   // Range checking
+   if ((pStartIndex < 0) || (pStartIndex >= pTextLineArray.length))
+      return false;
+   if ((pEndIndex <= pStartIndex) || (pEndIndex < 0))
+      return false;
+   if (pEndIndex > pTextLineArray.length)
+      pEndIndex = pTextLineArray.length;
+   if (pEditWidth <= 5)
+      return false;
 
-	var nextLineIndex = 0;
-	var numCharsToRemove = 0;
-	var splitIndex = 0;
-	var spaceFound = false;      // Whether or not a space was found in a text line
-	var splitIndexOriginal = 0;
-	var tempText = null;
-	var appendedNewLine = false; // If we appended another line
-	for (var i = pStartIndex; i < pEndIndex; ++i)
-	{
-		// As an extra precaution, check to make sure this array element is defined.
-		if (pTextLineArray[i] == undefined)
-			continue;
+   var textChanged = false; // We'll return this upon function exit.
 
-		nextLineIndex = i + 1;
-		// If the line's text is longer or equal to the edit width, then if
-		// possible, move the last word to the beginning of the next line.
-		if (pTextLineArray[i].displayLength() >= pEditWidth)
-		{
-			numCharsToRemove = pTextLineArray[i].displayLength() - pEditWidth + 1;
-			splitIndex = pTextLineArray[i].displayIdxToActualIdx(pTextLineArray[i].displayLength() - numCharsToRemove);
-			splitIndexOriginal = splitIndex;
-			// If the character in the text line at splitIndex is not a space,
-			// then look for a space before splitIndex.
-			spaceFound = (pTextLineArray[i].text.charAt(splitIndex) == " ");
-			if (!spaceFound)
-			{
-				splitIndex = pTextLineArray[i].text.lastIndexOf(" ", splitIndex-1);
-				spaceFound = (splitIndex > -1);
-				if (!spaceFound)
-					splitIndex = splitIndexOriginal;
-				// If the character at splitIndex is the 2nd character in a Synchronet
-				// attribute code, then adjust splitIndex accordingly.
-				if ((splitIndex > 0) && gSyncAttrRegex.test(pTextLineArray[i].text.substr(splitIndex-1, 2)))
-					--splitIndex;
-			}
+   var nextLineIndex = 0;
+   var charsToRemove = 0;
+   var splitIndex = 0;
+   var spaceFound = false;      // Whether or not a space was found in a text line
+   var splitIndexOriginal = 0;
+   var tempText = null;
+   var appendedNewLine = false; // If we appended another line
+   for (var i = pStartIndex; i < pEndIndex; ++i)
+   {
+      // As an extra precaution, check to make sure this array element is defined.
+      if (pTextLineArray[i] == undefined)
+         continue;
+
+      nextLineIndex = i + 1;
+      // If the line's text is longer or equal to the edit width, then if
+      // possible, move the last word to the beginning of the next line.
+      if (pTextLineArray[i].text.length >= pEditWidth)
+      {
+         charsToRemove = pTextLineArray[i].text.length - pEditWidth + 1;
+         splitIndex = pTextLineArray[i].text.length - charsToRemove;
+         splitIndexOriginal = splitIndex;
+         // If the character in the text line at splitIndex is not a space,
+         // then look for a space before splitIndex.
+         spaceFound = (pTextLineArray[i].text.charAt(splitIndex) == " ");
+         if (!spaceFound)
+         {
+            splitIndex = pTextLineArray[i].text.lastIndexOf(" ", splitIndex-1);
+            spaceFound = (splitIndex > -1);
+            if (!spaceFound)
+               splitIndex = splitIndexOriginal;
+         }
+         tempText = pTextLineArray[i].text.substr(spaceFound ? splitIndex+1 : splitIndex);
+         pTextLineArray[i].text = pTextLineArray[i].text.substr(0, splitIndex);
+         textChanged = true;
+         // If we're on the last line, or if the current line has a hard
+         // newline or is a quote line, then append a new line below.
+         appendedNewLine = false;
+         if ((nextLineIndex == pTextLineArray.length) || pTextLineArray[i].hardNewlineEnd ||
+             isQuoteLine(pTextLineArray, i))
+         {
+            pTextLineArray.splice(nextLineIndex, 0, new TextLine());
+            pTextLineArray[nextLineIndex].hardNewlineEnd = pTextLineArray[i].hardNewlineEnd;
+            pTextLineArray[i].hardNewlineEnd = false;
+            pTextLineArray[nextLineIndex].isQuoteLine = pTextLineArray[i].isQuoteLine;
+            appendedNewLine = true;
+         }
 
-			var tempText = pTextLineArray[i].text.substr(splitIndex);
-			// If the substring contains a space at the beginning or the end, then
-			// remove the space.
-			if (tempText.search(/^ /) == 0)
-				tempText = tempText.substr(1);
-			else if (tempText.search(/ $/) == tempText.length-1)
-				tempText = tempText.substr(0, tempText.length-1);
-			pTextLineArray[i].text = pTextLineArray[i].text.substr(0, splitIndex);
-
-			textChanged = true;
-			// If we're on the last line, or if the current line has a hard
-			// newline or is a quote line, then append a new line below.
-			appendedNewLine = false;
-			if ((nextLineIndex == pTextLineArray.length) || pTextLineArray[i].hardNewlineEnd || isQuoteLine(pTextLineArray, i))
-			{
-				pTextLineArray.splice(nextLineIndex, 0, new TextLine());
-				pTextLineArray[nextLineIndex].hardNewlineEnd = pTextLineArray[i].hardNewlineEnd;
-				pTextLineArray[i].hardNewlineEnd = false;
-				pTextLineArray[nextLineIndex].isQuoteLine = pTextLineArray[i].isQuoteLine;
-				appendedNewLine = true;
-			}
+         // Move the text around and adjust the line properties.
+         if (appendedNewLine)
+            pTextLineArray[nextLineIndex].text = tempText;
+         else
+         {
+            // If we're in insert mode, then insert the text at the beginning of
+            // the next line.  Otherwise, overwrite the text in the next line.
+            if (inInsertMode())
+               pTextLineArray[nextLineIndex].text = tempText + " " + pTextLineArray[nextLineIndex].text;
+            else
+            {
+               // We're in overwrite mode, so overwite the first part of the next
+               // line with tempText.
+               if (pTextLineArray[nextLineIndex].text.length < tempText.length)
+                  pTextLineArray[nextLineIndex].text = tempText;
+               else
+               {
+                  pTextLineArray[nextLineIndex].text = tempText
+                                           + pTextLineArray[nextLineIndex].text.substr(tempText.length);
+               }
+            }
+         }
+      }
+      else
+      {
+         // pTextLineArray[i].text.length is < pEditWidth, so try to bring up text
+         // from the next line.
 
-			// Move the text around and adjust the line properties.
-			if (appendedNewLine)
-				pTextLineArray[nextLineIndex].text = tempText;
-			else
-			{
-				// If we're in insert mode, then insert the text at the beginning of
-				// the next line.  Otherwise, overwrite the text in the next line.
-				if (inInsertMode())
-					pTextLineArray[nextLineIndex].text = tempText + " " + pTextLineArray[nextLineIndex].text;
-				else
-				{
-					// We're in overwrite mode, so overwrite the first part of the next
-					// line with tempText.
-					if (pTextLineArray[nextLineIndex].text.length < tempText.length)
-						pTextLineArray[nextLineIndex].text = tempText;
-					else
-						pTextLineArray[nextLineIndex].text = tempText + pTextLineArray[nextLineIndex].text.substr(tempText.length);
-				}
-			}
-		}
-		else
-		{
-			// pTextLineArray[i].text.length is < pEditWidth, so try to bring up text
-			// from the next line.
+         // Only do it if the line doesn't have a hard newline and it's not a
+         // quote line and there is a next line.
+         if (!pTextLineArray[i].hardNewlineEnd && !isQuoteLine(pTextLineArray, i) &&
+             (i < pTextLineArray.length-1))
+         {
+            if (pTextLineArray[nextLineIndex].text.length > 0)
+            {
+               splitIndex = pEditWidth - pTextLineArray[i].text.length - 2;
+               // If splitIndex is negative, that means the entire next line
+               // can fit on the current line.
+               if ((splitIndex < 0) || (splitIndex > pTextLineArray[nextLineIndex].text.length))
+                  splitIndex = pTextLineArray[nextLineIndex].text.length;
+               else
+               {
+                  // If the character in the next line at splitIndex is not a
+                  // space, then look for a space before it.
+                  if (pTextLineArray[nextLineIndex].text.charAt(splitIndex) != " ")
+                     splitIndex = pTextLineArray[nextLineIndex].text.lastIndexOf(" ", splitIndex);
+                  // If no space was found, then skip to the next line (we don't
+                  // want to break up words from the next line).
+                  if (splitIndex == -1)
+                     continue;
+               }
 
-			// Only do it if the line doesn't have a hard newline and it's not a
-			// quote line and there is a next line.
-			if (!pTextLineArray[i].hardNewlineEnd && !isQuoteLine(pTextLineArray, i) && (i < pTextLineArray.length-1))
-			{
-				if (pTextLineArray[nextLineIndex].text.length > 0)
-				{
-					splitIndex = pEditWidth - pTextLineArray[i].text.length - 2;
-					// If splitIndex is negative, that means the entire next line
-					// can fit on the current line.
-					if ((splitIndex < 0) || (splitIndex > pTextLineArray[nextLineIndex].text.length))
-						splitIndex = pTextLineArray[nextLineIndex].text.length;
-					else
-					{
-						// If the character in the next line at splitIndex is not a
-						// space, then look for a space before it.
-						if (pTextLineArray[nextLineIndex].text.charAt(splitIndex) != " ")
-							splitIndex = pTextLineArray[nextLineIndex].text.lastIndexOf(" ", splitIndex);
-						// If no space was found, then skip to the next line (we don't
-						// want to break up words from the next line).
-						if (splitIndex == -1)
-							continue;
-					}
-					// If the character at splitIndex is the 2nd character in a Synchronet
-					// attribute code, then adjust splitIndex accordingly.
-					if ((splitIndex > 0) && gSyncAttrRegex.test(pTextLineArray[nextLineIndex].text.substr(splitIndex-1, 2)))
-						--splitIndex;
-
-					// Get the text to bring up to the current line.
-					// If the current line does not end with a space and the next line
-					// does not start with a space, then add a space between this line
-					// and the next line's text.  This is done to avoid joining words
-					// accidentally.
-					var tempText = "";
-					if ((pTextLineArray[i].text.charAt(pTextLineArray[i].text.length-1) != " ") &&
-					    (pTextLineArray[nextLineIndex].text.substr(0, 1) != " "))
-					{
-						tempText = " ";
-					}
-					tempText += pTextLineArray[nextLineIndex].text.substr(0, splitIndex);
-					// Move the text from the next line to the current line, if the current
-					// line has room for it.
-					if (pTextLineArray[i].text.length + tempText.length < pEditWidth)
-					{
-						pTextLineArray[i].text += tempText;
-						pTextLineArray[nextLineIndex].text = pTextLineArray[nextLineIndex].text.substr(splitIndex+1);
-						textChanged = true;
+               // Get the text to bring up to the current line.
+               // If the current line does not end with a space and the next line
+               // does not start with a space, then add a space between this line
+               // and the next line's text.  This is done to avoid joining words
+               // accidentally.
+               tempText = "";
+               if ((pTextLineArray[i].text.charAt(pTextLineArray[i].text.length-1) != " ") &&
+                   (pTextLineArray[nextLineIndex].text.substr(0, 1) != " "))
+               {
+                  tempText = " ";
+               }
+               tempText += pTextLineArray[nextLineIndex].text.substr(0, splitIndex);
+               // Move the text from the next line to the current line, if the current
+               // line has room for it.
+               if (pTextLineArray[i].text.length + tempText.length < pEditWidth)
+               {
+                  pTextLineArray[i].text += tempText;
+                  pTextLineArray[nextLineIndex].text = pTextLineArray[nextLineIndex].text.substr(splitIndex+1);
+                  textChanged = true;
 
-						// If the next line is now blank, then remove it.
-						if (pTextLineArray[nextLineIndex].text.length == 0)
-						{
-							// The current line should take on the next line's
-							// hardnewlineEnd property before removing the next line.
-							pTextLineArray[i].hardNewlineEnd = pTextLineArray[nextLineIndex].hardNewlineEnd;
-							pTextLineArray.splice(nextLineIndex, 1);
-						}
-					}
-				}
-				else
-				{
-					// The next line's text string is blank.  If its hardNewlineEnd
-					// property is false, then remove the line.
-					if (!pTextLineArray[nextLineIndex].hardNewlineEnd)
-					{
-						pTextLineArray.splice(nextLineIndex, 1);
-						textChanged = true;
-					}
-				}
-			}
-		}
-	}
+                  // If the next line is now blank, then remove it.
+                  if (pTextLineArray[nextLineIndex].text.length == 0)
+                  {
+                     // The current line should take on the next line's
+                     // hardnewlineEnd property before removing the next line.
+                     pTextLineArray[i].hardNewlineEnd = pTextLineArray[nextLineIndex].hardNewlineEnd;
+                     pTextLineArray.splice(nextLineIndex, 1);
+                  }
+               }
+            }
+            else
+            {
+               // The next line's text string is blank.  If its hardNewlineEnd
+               // property is false, then remove the line.
+               if (!pTextLineArray[nextLineIndex].hardNewlineEnd)
+               {
+                  pTextLineArray.splice(nextLineIndex, 1);
+                  textChanged = true;
+               }
+            }
+         }
+      }
+   }
 
-	return textChanged;
+   return textChanged;
 }
 
 // Returns indexes of the first unquoted text line and the next
@@ -2393,7 +2233,13 @@ function isQuoteLine(pLineArray, pLineIndex)
 
    var lineIsQuoteLine = false;
    if (typeof(pLineArray[pLineIndex]) != "undefined")
+   {
+      /*
+      lineIsQuoteLine = ((pLineArray[pLineIndex].isQuoteLine) ||
+                     (/^ *>/.test(pLineArray[pLineIndex].text)));
+      */
       lineIsQuoteLine = (pLineArray[pLineIndex].isQuoteLine);
+   }
    return lineIsQuoteLine;
 }
 
@@ -2409,73 +2255,73 @@ function isQuoteLine(pLineArray, pLineIndex)
 //            control character)
 function toggleAttr(pAttrType, pAttrs, pNewAttr)
 {
-	// Removes an attribute from an attribute string, if it
-	// exists.  Returns the new attribute string.
-	function removeAttrIfExists(pAttrs, pNewAttr)
-	{
-		var index = pAttrs.search(pNewAttr);
-		if (index > -1)
-			pAttrs = pAttrs.replace(pNewAttr, "");
-		return pAttrs;
-	}
-
-	// Convert pAttrs and pNewAttr to all uppercase for ease of searching
-	pAttrs = pAttrs.toUpperCase();
-	pNewAttr = pNewAttr.toUpperCase();
-
-	// If pAttrs starts with the normal attribute, then
-	// remove it (we'll put it back on later).
-	var normalAtStart = false;
-	if (pAttrs.search(/^N/) == 0)
-	{
-		normalAtStart = true;
-		pAttrs = pAttrs.substr(2);
-	}
+   // Removes an attribute from an attribute string, if it
+   // exists.  Returns the new attribute string.
+   function removeAttrIfExists(pAttrs, pNewAttr)
+   {
+      var index = pAttrs.search(pNewAttr);
+      if (index > -1)
+         pAttrs = pAttrs.replace(pNewAttr, "");
+      return pAttrs;
+   }
 
-	// Prepend the attribute control character to the new attribute
-	var newAttr = "" + pNewAttr;
+   // Convert pAttrs and pNewAttr to all uppercase for ease of searching
+   pAttrs = pAttrs.toUpperCase();
+   pNewAttr = pNewAttr.toUpperCase();
 
-	// Set a regex for searching & replacing
-	var regex = "";
-	switch (pAttrType)
-	{
-		case FORE_ATTR: // Foreground attribute
-			regex = /K|R|G|Y|B|M|C|W/g;
-			break;
-		case BKG_ATTR: // Background attribute
-			regex = /0|1|2|3|4|5|6|7/g;
-			break;
-		case SPECIAL_ATTR: // Special attribute
-			//regex = /H|I|N/g;
-			index = pAttrs.search(newAttr);
-			if (index > -1)
-			pAttrs = pAttrs.replace(newAttr, "");
-			else
-			pAttrs += newAttr;
-			break;
-		default:
-			break;
-	}
+   // If pAttrs starts with the normal attribute, then
+   // remove it (we'll put it back on later).
+   var normalAtStart = false;
+   if (pAttrs.search(/^N/) == 0)
+   {
+      normalAtStart = true;
+      pAttrs = pAttrs.substr(2);
+   }
 
-	// If regex is not blank, then search & replace on it in
-	// pAttrs.
-	if (regex != "")
-	{
-		pAttrs = removeAttrIfExists(pAttrs, newAttr);
-		// If the regex is found, then replace it.  Otherwise,
-		// add pNewAttr to the attribute string.
-		if (pAttrs.search(regex) > -1)
-			pAttrs = pAttrs.replace(regex, "" + pNewAttr);
-		else
-			pAttrs += "" + pNewAttr;
-	}
+   // Prepend the attribute control character to the new attribute
+   var newAttr = "" + pNewAttr;
+
+   // Set a regex for searching & replacing
+   var regex = "";
+   switch (pAttrType)
+   {
+      case FORE_ATTR: // Foreground attribute
+         regex = /K|R|G|Y|B|M|C|W/g;
+         break;
+      case BKG_ATTR: // Background attribute
+         regex = /0|1|2|3|4|5|6|7/g;
+         break;
+      case SPECIAL_ATTR: // Special attribute
+         //regex = /H|I|N/g;
+         index = pAttrs.search(newAttr);
+         if (index > -1)
+            pAttrs = pAttrs.replace(newAttr, "");
+         else
+            pAttrs += newAttr;
+         break;
+      default:
+         break;
+   }
+
+   // If regex is not blank, then search & replace on it in
+   // pAttrs.
+   if (regex != "")
+   {
+      pAttrs = removeAttrIfExists(pAttrs, newAttr);
+      // If the regex is found, then replace it.  Otherwise,
+      // add pNewAttr to the attribute string.
+      if (pAttrs.search(regex) > -1)
+         pAttrs = pAttrs.replace(regex, "" + pNewAttr);
+      else
+         pAttrs += "" + pNewAttr;
+   }
 
-	// If pAttrs started with the normal attribute, then
-	// put it back on.
-	if (normalAtStart)
-		pAttrs = "N" + pAttrs;
+   // If pAttrs started with the normal attribute, then
+   // put it back on.
+   if (normalAtStart)
+      pAttrs = "N" + pAttrs;
 
-	return pAttrs;
+   return pAttrs;
 }
 
 // This function wraps an array of strings based on a line width.
@@ -3679,38 +3525,37 @@ function postMsgToSubBoard(pSubBoardCode, pTo, pSubj, pMessage, pFromUserNum)
 //               sigContents: String - The user's message signature
 function readUserSigFile()
 {
-	var retObj = {
-		sigFileExists: false,
-		sigContents: ""
-	};
-
-	// The user signature files are located in sbbs/data/user, and the filename
-	// is the user number (zero-padded up to 4 digits) + .sig
-	var userSigFilename = backslash(system.data_dir + "user") + format("%04d.sig", user.number);
-	retObj.sigFileExists = file_exists(userSigFilename);
-	if (retObj.sigFileExists)
-	{
-		var msgSigFile = new File(userSigFilename);
-		if (msgSigFile.open("r"))
-		{
-			var fileLine = ""; // A line read from the file
-			while (!msgSigFile.eof)
-			{
-				fileLine = msgSigFile.readln(2048);
-				// fileLine should be a string, but I've seen some cases
-				// where for some reason it isn't.  If it's not a string,
-				// then continue onto the next line.
-				if (typeof(fileLine) != "string")
-					continue;
-
-				retObj.sigContents += fileLine + "\r\n";
-			}
+  var retObj = new Object();
+  retObj.sigFileExists = false;
+  retObj.sigContents = "";
+
+  // The user signature files are located in sbbs/data/user, and the filename
+  // is the user number (zero-padded up to 4 digits) + .sig
+  var userSigFilename = backslash(system.data_dir + "user") + format("%04d.sig", user.number);
+  retObj.sigFileExists = file_exists(userSigFilename);
+  if (retObj.sigFileExists)
+  {
+    var msgSigFile = new File(userSigFilename);
+    if (msgSigFile.open("r"))
+    {
+      var fileLine = ""; // A line read from the file
+      while (!msgSigFile.eof)
+      {
+        fileLine = msgSigFile.readln(2048);
+        // fileLine should be a string, but I've seen some cases
+        // where for some reason it isn't.  If it's not a string,
+        // then continue onto the next line.
+        if (typeof(fileLine) != "string")
+          continue;
+
+        retObj.sigContents += fileLine + "\r\n";
+      }
 
-			msgSigFile.close();
-		}
-	}
+      msgSigFile.close();
+    }
+  }
 
-	return retObj;
+  return retObj;
 }
 
 // Returns the sub-board code and group index for the first sub-board
@@ -3760,80 +3605,73 @@ function getFirstPostableSubInfo()
 // Return value: The number of text replacements added to the array.
 function populateTxtReplacements(pArray, pRegex)
 {
-	var numTxtReplacements = 0;
-
-	// Note: Limited to words without spaces.
-	// Open the word replacements configuration file
-	var wordReplacementsFilename = genFullPathCfgFilename("SlyEdit_TextReplacements.cfg", gStartupPath);
-	var arrayPopulated = false;
-	var wordFile = new File(wordReplacementsFilename);
-	if (wordFile.open("r"))
-	{
-		var fileLine = null;      // A line read from the file
-		var equalsPos = 0;        // Position of a = in the line
-		var wordToSearch = null; // A word to be replaced
-		var wordToSearchUpper = null;
-		var substWord = null;    // The word to substitute
-		// This tests numTxtReplacements < 9999 so that the 9999th one is the last
-		// one read.
-		while (!wordFile.eof && (numTxtReplacements < 9999))
-		{
-			// Read the next line from the file.
-			fileLine = wordFile.readln(2048);
+   var numTxtReplacements = 0;
+
+   // Note: Limited to words without spaces.
+   // Open the word replacements configuration file
+   var wordReplacementsFilename = genFullPathCfgFilename("SlyEdit_TextReplacements.cfg", gStartupPath);
+   var arrayPopulated = false;
+   var wordFile = new File(wordReplacementsFilename);
+   if (wordFile.open("r"))
+   {
+      var fileLine = null;      // A line read from the file
+      var equalsPos = 0;        // Position of a = in the line
+      var wordToSearch = null; // A word to be replaced
+      var wordToSearchUpper = null;
+      var substWord = null;    // The word to substitue
+      // This tests numTxtReplacements < 9999 so that the 9999th one is the last
+      // one read.
+      while (!wordFile.eof && (numTxtReplacements < 9999))
+      {
+         // Read the next line from the config file.
+         fileLine = wordFile.readln(2048);
 
-			// fileLine should be a string, but I've seen some cases
-			// where for some reason it isn't.  If it's not a string,
-			// then continue onto the next line.
-			if (typeof(fileLine) != "string")
-				continue;
-			// If the line starts with with a semicolon (the comment
-			// character) or is blank, then skip it.
-			if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
-				continue;
+         // fileLine should be a string, but I've seen some cases
+         // where for some reason it isn't.  If it's not a string,
+         // then continue onto the next line.
+         if (typeof(fileLine) != "string")
+            continue;
+         // If the line starts with with a semicolon (the comment
+         // character) or is blank, then skip it.
+         if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
+            continue;
 
-			// Look for an equals sign, and if found, separate the line
-			// into the setting name (before the =) and the value (after the
-			// equals sign).
-			equalsPos = fileLine.indexOf("=");
-			if (equalsPos <= 0)
-				continue; // = not found or is at the beginning, so go on to the next line
-
-			// Replace \1 with the SOH character so it can be used for
-			// Synchronet color/attribute codes
-			fileLine = fileLine.replace(/\\1/g, "\1");
-
-			// Extract the word to search and substitution word from the line.  If
-			// not using regular expressions, then convert the word to search to
-			// all uppercase for case-insensitive searching.
-			wordToSearch = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
-			wordToSearchUpper = wordToSearch.toUpperCase();
-			substWord = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
-			// Make sure substWord only contains printable characters.  If not, then
-			// skip this one.
-			var substIsPrintable = true;
-			for (var i = 0; (i < substWord.length) && substIsPrintable; ++i)
-			{
-				var currentChar = substWord.charAt(i);
-				substIsPrintable = (isPrintableChar(currentChar) || (currentChar == "\1"));
-			}
-			if (!substIsPrintable)
-				continue;
+         // Look for an equals sign, and if found, separate the line
+         // into the setting name (before the =) and the value (after the
+         // equals sign).
+         equalsPos = fileLine.indexOf("=");
+         if (equalsPos <= 0)
+            continue; // = not found or is at the beginning, so go on to the next line
+
+         // Extract the word to search and substitution word from the line.  If
+         // not using regular expressions, then convert the word to search to
+         // all uppercase for case-insensitive searching.
+         wordToSearch = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
+         wordToSearchUpper = wordToSearch.toUpperCase();
+         substWord = strip_ctrl(trimSpaces(fileLine.substr(equalsPos+1), true, false, true));
+         // Make sure substWord only contains printable characters.  If not, then
+         // skip this one.
+         var substIsPrintable = true;
+         for (var i = 0; (i < substWord.length) && substIsPrintable; ++i)
+            substIsPrintable = isPrintableChar(substWord.charAt(i));
+         if (!substIsPrintable)
+            continue;
 
-			// And add the search word and replacement text to pArray.
-			if (wordToSearchUpper != substWord.toUpperCase())
-			{
-				if (pRegex)
-					pArray[wordToSearch] = substWord;
-				else
-					pArray[wordToSearchUpper] = substWord;
-				++numTxtReplacements;
-			}
-		}
+         // And add the search word and replacement text to pArray.
+         if (wordToSearchUpper != substWord.toUpperCase())
+         {
+            if (pRegex)
+               pArray[wordToSearch] = substWord;
+            else
+               pArray[wordToSearchUpper] = substWord;
+            ++numTxtReplacements;
+         }
+      }
 
-		wordFile.close();
-	}
+      wordFile.close();
+   }
 
-	return numTxtReplacements;
+   return numTxtReplacements;
 }
 
 function moveGenColorsToGenSettings(pColorsArray, pCfgObj)
@@ -4515,637 +4353,25 @@ function getKeyWithESCChars(pGetKeyMode, pCfgObj)
 	return userInput;
 }
 
-// Returns the index of the first Synchronet attribute code before a given index
-// in a string.
-//
-// Parameters:
-//  pStr: The string to search in
-//  pIdx: The index to search back from
-//  pSeriesOfAttrs: Optional boolean - Whether or not to look for a series of
-//                  attributes.  Defaults to false (look for just one attribute).
-//  pOnlyInWord: Optional boolean - Whether or not to look only in the current word
-//               (with words separated by whitespace).  Defaults to false.
-//
-// Return value: The index of the first Synchronet attribute code before the given
-//               index in the string, or -1 if there is none or if the parameters
-//               are invalid
-function strIdxOfSyncAttrBefore(pStr, pIdx, pSeriesOfAttrs, pOnlyInWord)
-{
-	if (typeof(pStr) != "string")
-		return -1;
-	if (typeof(pIdx) != "number")
-		return -1;
-	if ((pIdx < 0) || (pIdx >= pStr.length))
-		return -1;
-
-	var seriesOfAttrs = (typeof(pSeriesOfAttrs) == "boolean" ? pSeriesOfAttrs : false);
-	var onlyInWord = (typeof(pOnlyInWord) == "boolean" ? pOnlyInWord : false);
-
-	var attrCodeIdx = pStr.lastIndexOf("\1", pIdx-1);
-	if (attrCodeIdx > -1)
-	{
-		// If we are to only check the current word, then continue only if
-		// there isn't a space between the attribute code and the given index.
-		if (onlyInWord)
-		{
-			if (pStr.lastIndexOf(" ", pIdx-1) >= attrCodeIdx)
-				attrCodeIdx = -1;
-		}
-	}
-	if (attrCodeIdx > -1)
-	{
-		var syncAttrRegexWholeWord = /^\1[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i;
-		if (syncAttrRegexWholeWord.test(pStr.substr(attrCodeIdx, 2)))
-		{
-			if (seriesOfAttrs)
-			{
-				for (var i = attrCodeIdx - 2; i >= 0; i -= 2)
-				{
-					if (syncAttrRegexWholeWord.test(pStr.substr(i, 2)))
-						attrCodeIdx = i;
-					else
-						break;
-				}
-			}
-		}
-		else
-			attrCodeIdx = -1;
-	}
-	return attrCodeIdx;
-}
-
-// Gets a substring for a string, including preceding Synchronet color codes
-// for the word at the given start index.
-//
-// Parameters:
-//  pStr: The string to get the substring from
-//  pStartIdx: The index in the string where the substring should start
-//  pLen: The length of the substring to get.  Optional - If not specified,
-//        the rest of the string will be used.
-//
-// Return value: An object with the following properties:
-//               strSub: The substring from the string, including any Synchronet
-//                       color codes that may exist preceding the word at pStartIdx
-//               startIdx: The actual index in the string where the substring starts
-//                         (-1 if an error occurred)
-//               endIdx: The actual index in the string where the substring ends.
-//                       This is the index of the last character in the substring.
-//                       Will be -1 if an error occurred.
-//               len: The actual length of the substring
-//               printableLen: The length of the string as it would appear on the screen
-//                             (i.e., its length without Synchronet attribute codes)
-//               syncAttrStartIdx: The starting index of Synchronet attributes
-//                                 (-1 if not found or an error occurred)
-//               syncAttrEndIdx: The ending index of Synchronet attributes, including
-//                               the last character (-1 if not found or an error occurred)
-function substrWithSyncColorCodes(pStr, pStartIdx, pLen)
-{
-	var retObj = {
-		strSub: "",
-		startIdx: -1,
-		endIdx: -1,
-		len: 0,
-		printableLen: 0,
-		syncAttrStartIdx: -1,
-		syncAttrEndIdx: -1,
-		clear: function() {
-			this.strSub = "";
-			this.startIdx = -1;
-			this.endIdx = -1;
-			this.len = 0;
-			this.printableLen = 0;
-			this.syncAttrStartIdx = -1;
-			this.syncAttrEndIdx = -1;
-		}
-	};
-
-	if (typeof(pStr) != "string")
-		return retObj;
-	if (typeof(pStartIdx) != "number")
-		return retObj;
-	if ((pStartIdx < 0) || (pStartIdx >= pStr.length))
-		return retObj;
-
-	var substrLen = (typeof(pLen) == "number" ? pLen : pStr.length-pStartIdx);
-	if (substrLen > pStr.length)
-		substrLen = pStr.length - pStartIdx + 1;
-
-	// If there are Synchronet attribute codes at the given index, then find out
-	// where the attribute codes end and extend the length to include those with
-	// the printable text.
-	retObj.startIdx = pStartIdx;
-	retObj.len = substrLen;
-	if (pStr.substr(pStartIdx).search(gSyncAttrRegex) == 0)
-	{
-		retObj.syncAttrStartIdx = pStartIdx;
-		retObj.syncAttrEndIdx = regexLastIndexOf(pStr, gSyncAttrRegex, pStartIdx, pStartIdx+substrLen);
-		if (retObj.syncAttrEndIdx > -1)
-		{
-			++retObj.syncAttrEndIdx; // This index should be the last character in the Sync attributes
-			retObj.len += (retObj.syncAttrEndIdx - pStartIdx + 1);
-		}
-	}
-	else
-	{
-		var attrInfo = getAttrsAndIndexesBeforeStrIdx(pStr, pStartIdx);
-		retObj.syncAttrStartIdx = attrInfo.syncAttrStartIdx;
-		retObj.syncAttrEndIdx = attrInfo.syncAttrEndIdx;
-		if ((retObj.syncAttrStartIdx > -1) && (retObj.syncAttrEndIdx > -1))
-		{
-			// Map the given start index to the actual index in the string
-			retObj.startIdx = strDisplayIdxToActualIdx(pStr, pStartIdx);
-			// Older code which has a bug - Doesn't map a 'on-screen' string
-			// index to an actual string index correctly if an attribute code
-			// happens to appear in the string right before pStartIdx:
-			/*
-			if (retObj.syncAttrEndIdx == pStartIdx-1)
-			{
-				var lenDiff = retObj.syncAttrEndIdx - retObj.syncAttrStartIdx + 1;
-				retObj.startIdx -= lenDiff;
-				retObj.len += lenDiff;
-			}
-			else
-				retObj.startIdx = strDisplayIdxToActualIdx(pStr, pStartIdx);
-			*/
-		}
-		else
-		{
-			// Found no attribute codes before the start index.
-			// See if there are any Synchronet attribute codes between the
-			// start index & within the length.  If so, then adjust the length
-			// accordingly: Add Synchronet attribute code lengths to the substring
-			// length to account for them in the string.
-			var endIdx = pStartIdx + substrLen;
-			var i = pStartIdx;
-			while (i < endIdx)
-			{
-				if (gSyncAttrRegex.test(pStr.substr(i, 2)))
-				{
-					i += 2;
-					retObj.len += 2;
-				}
-				else
-					++i;
-			}
-		}
-	}
-	retObj.endIdx = retObj.startIdx + retObj.len - 1;
-	retObj.strSub = pStr.substr(retObj.startIdx, retObj.len);
-	retObj.printableLen = strip_ctrl(retObj.strSub).length;
-	return retObj;
-}
-
-// Returns the index of the last-occurring instance of a given regular expression
-// in a string.
-//
-// Parameters:
-//  pStr: The string to look in
-//  pRegex: The regular expression to look for
-//  pStartIdx: Optional - The index of the starting character in the string
-//  pEndIdx: Optional - The index of the last character in the string to check
-//
-// Return value: The index of the last-occuring instance of the given regular
-//               expression, or -1 if not found.
-function regexLastIndexOf(pStr, pRegex, pStartIdx, pEndIdx)
-{
-	if (typeof(pStr) != "string")
-		return -1;
-	var startIdx = (typeof(pEndIdx) == "number" ? pStartIdx : 0);
-	if ((startIdx < 0) || (startIdx >= pStr.length))
-		return -1;
-	var endIdx = (typeof(pEndIdx) == "number" ? pEndIdx : pStr.length-1);
-	if ((endIdx < 0) || (endIdx >= pStr.length))
-		return -1;
-
-	var lastIdx = -1;
-	for (var i = endIdx; (i >= startIdx) && (lastIdx == -1); --i)
-	{
-		if (pStr.substr(i).search(pRegex) == 0)
-			lastIdx = i;
-	}
-	return lastIdx;
-}
-
-// Finds the index of the next occurrance of a regular expression in
-// a string.
-//
-// Parameters:
-//  pStr: The string to search
-//  pRegex: The regular expression to search for
-//  pStartIdx: Optional - The index of where to start in the string.
-//             If not specified, this value will be 0.
-//
-// Return value: The index of the next occurance of the regular expression, or
-//               -1 if not found
-function strSearchNext(pStr, pRegex, pStartIdx)
-{
-	if (typeof(pStr) != "string")
-		return -1;
-	var startIdx = (typeof(pStartIdx) == "number" ? pStartIdx : 0);
-	if ((startIdx < 0) || (startIdx >= pStr.length))
-		return -1;
-
-	var subStr = (startIdx == 0 ? pStr : pStr.substr(startIdx));
-	var searchIdx = subStr.search(pRegex);
-	if (searchIdx > -1)
-		searchIdx += startIdx;
-	return searchIdx;
-}
-
-// Returns a string with any Synchronet color/attribute codes found in a string
-// before a given index.
-//
-// Parameters:
-//  pStr: The string to search in
-//  pIdx: The index in the string to search before
-//
-// Return value: A string containing any Synchronet attribute codes found before
-//               the given index in the given string
-function getAttrsBeforeStrIdx(pStr, pIdx)
-{
-	if (typeof(pStr) != "string")
-		return "";
-	if (typeof(pIdx) != "number")
-		return "";
-	if (pIdx < 0)
-		return "";
-
-	var idx = (pIdx < pStr.length ? pIdx : pStr.length-1);
-	var attrStartIdx = strIdxOfSyncAttrBefore(pStr, idx, true, false);
-	var attrEndIdx = strIdxOfSyncAttrBefore(pStr, idx, false, false); // Start of 2-character code
-	var attrsStr = "";
-	if ((attrStartIdx > -1) && (attrEndIdx > -1))
-		attrsStr = pStr.substring(attrStartIdx, attrEndIdx+2);
-	return attrsStr;
-}
-
-// Returns an object with information about any group of Synchronet color/attribute codes
-// found in a string before a given index.
-//
-// Parameters:
-//  pStr: The string to search in
-//  pIdx: The index in the string to search before
-//
-// Return value: An object containing the following properties:
-//               attrStr: A string containing any Synchronet attribute codes
-//                        found before the given index in the given string.  If
-//                        none are found, this string will be empty.
-//               syncAttrStartIdx: The index of where the attribute codes start, or
-//                                 -1 if none were found
-//               syncAttrEndIdx: The index of the last character of the attribute
-//                               codes, or -1 if none were found
-function getAttrsAndIndexesBeforeStrIdx(pStr, pIdx)
-{
-	var retObj = {
-		attrStr: "",
-		syncAttrStartIdx: -1,
-		syncAttrEndIdx: -1
-	};
-
-	if ((typeof(pStr) != "string") || (typeof(pIdx) != "number"))
-		return retObj;
-	if (pIdx < 0)
-		return retObj;
-
-	var idx = (pIdx < pStr.length ? pIdx : pStr.length-1);
-
-	// Look for indexes of any Synchronet attribute codes before the given
-	// index.  If there are some, then set the start index after the attribute
-	// codes end.
-	retObj.syncAttrStartIdx = strIdxOfSyncAttrBefore(pStr, idx, /*false*/true, false);
-	if ((retObj.syncAttrStartIdx > -1) && (retObj.syncAttrStartIdx < idx))
-	{
-		retObj.syncAttrEndIdx = regexLastIndexOf(pStr, gSyncAttrRegex, retObj.syncAttrStartIdx, idx);
-		if (retObj.syncAttrEndIdx == -1)
-			retObj.syncAttrEndIdx = regexLastIndexOf(pStr, gSyncAttrRegex, retObj.syncAttrStartIdx, retObj.syncAttrStartIdx+substrLen);
-		if (retObj.syncAttrEndIdx > -1)
-		{
-			++retObj.syncAttrEndIdx; // This index should be the last character in the Sync attributes
-			retObj.attrStr = pStr.substring(retObj.syncAttrStartIdx, retObj.syncAttrEndIdx+1);
-		}
-	}
-
-	if (retObj.attrStr.length == 0)
-	{
-		retObj.syncAttrStartIdx = -1;
-		retObj.syncAttrEndIdx = -1;
-	}
-
-	return retObj;
-}
-
-// Converts a printable display index to an actual index of a string,
-// ignoring Synchronet attribute codes in the string.
-//
-// Parameters:
-//  pStr: The string to check
-//  pDisplayIdx: The string index as displayed on the screen
-//
-// Return value: The index in the actual string.  0 on error
-function strDisplayIdxToActualIdx(pStr, pDisplayIdx)
-{
-	if (typeof(pStr) != "string")
-		return 0;
-	if (typeof(pDisplayIdx) != "number")
-		return 0;
-
-	var actualIdx = 0;
-	var strWithoutCtrlChars = strip_ctrl(pStr);
-	// If the given index is one past the last actual index in the string
-	// without control characters, then put actualIndex one past the last
-	// index of the string.
-	if (pDisplayIdx == strWithoutCtrlChars.length)
-		actualIdx = pStr.length;
-	else
-	{
-		var syncAttrIdx = pStr.search(gSyncAttrRegex);
-		if (syncAttrIdx > -1)
-		{
-			// Count the printable characters, ignoring Synchronet attribute codes
-			// I suspect there may be a more efficient way to do this..
-			var numDisplayChars = pDisplayIdx + 1;
-			var numDisplayableCharsSeen = 0;
-			var i = 0;
-			while ((i < pStr.length) && (numDisplayableCharsSeen < numDisplayChars))
-			{
-				if (gSyncAttrRegex.test(pStr.substr(i, 2)))
-					i += 2;
-				else
-				{
-					actualIdx = i;
-					++numDisplayableCharsSeen;
-					++i;
-				}
-			}
-			// Edge case: If pDisplayIdx was one past the last index of the string
-			// displayed on the screen, then increment actualIdx by 1 to fix off-by-one
-			if ((actualIdx == pStr.length-1) && (pDisplayIdx == strWithoutCtrlChars.length))
-				++actualIdx;
-			else if (pDisplayIdx == strWithoutCtrlChars.length - 1)
-				++actualIdx;
-		}
-		else
-			actualIdx = pDisplayIdx;
-	}
-
-	return actualIdx;
-}
-
-///////////////////////////////////////////////////////////////////////////////////////
-// Color/attribute code conversion functions
-
-// Converts Synchronet attribute codes to ANSI
-//
-// Parameters:
-//  pText: A string containing the text to convert
-//
-// Return value: The text with the color codes converted
-function SyncAttrsToANSI(pText)
-{
-	// Attributes
-	var txt = pText.replace(/n/gi, ""); // All attributes off
-	txt = txt.replace(/h/gi, ""); // Bold on (use high intensity)
-	txt = txt.replace(/i/gi, ""); // Blink on
-	// Foreground colors
-	txt = txt.replace(/k/gi, ""); // Black foreground
-	txt = txt.replace(/r/gi, ""); // Red foreground
-	txt = txt.replace(/g/gi, ""); // Green foreground
-	txt = txt.replace(/y/gi, ""); // Yellow foreground
-	txt = txt.replace(/b/gi, ""); // Blue foreground
-	txt = txt.replace(/m/gi, ""); // Magenta foreground
-	txt = txt.replace(/c/gi, ""); // Cyan foreground
-	txt = txt.replace(/w/gi, ""); // White foreground
-	// Background colors
-	txt = txt.replace(/0/g, ""); // Black background
-	txt = txt.replace(/1/g, ""); // Red background
-	txt = txt.replace(/2/g, ""); // Green background
-	txt = txt.replace(/3/g, ""); // Yellow background
-	txt = txt.replace(/4/g, ""); // Blue background
-	txt = txt.replace(/5/g, ""); // Magenta background
-	txt = txt.replace(/6/g, ""); // Cyan background
-	txt = txt.replace(/7/g, ""); // White background
-	return txt;
-}
-
-// Given some text, this converts ANSI color codes to Synchronet codes and
-// removes unwanted ANSI codes (such as cursor movement codes, etc.).
-//
-// Parameters:
-//  pText: A string to process
-//
-// Return value: A version of the string with Synchronet color codes converted to
-//               Synchronet attribute codes and unwanted ANSI codes removed
-function cvtANSIToSyncAndRemoveUnwantedANSI(pText)
-{
-	// Attributes
-	var txt = pText.replace(/\[0[mM]/g, "\1n"); // All attributes off
-	txt = txt.replace(/\[1m/gi, "\1h"); // Bold on (use high intensity)
-	txt = txt.replace(/\[5m/gi, "\1i"); // Blink on
-	// Foreground colors
-	txt = txt.replace(/\[30m/gi, "\1k"); // Black foreground
-	txt = txt.replace(/\[31m/gi, "\1r"); // Red foreground
-	txt = txt.replace(/\[32m/gi, "\1g"); // Green foreground
-	txt = txt.replace(/\[33m/gi, "\1y"); // Yellow foreground
-	txt = txt.replace(/\[34m/gi, "\1b"); // Blue foreground
-	txt = txt.replace(/\[35m/gi, "\1m"); // Magenta foreground
-	txt = txt.replace(/\[36m/gi, "\1c"); // Cyan foreground
-	txt = txt.replace(/\[37m/gi, "\1w"); // White foreground
-	// Background colors
-	txt = txt.replace(/\[40m/gi, "\1" + "0"); // Black background
-	txt = txt.replace(/\[41m/gi, "\1" + "1"); // Red background
-	txt = txt.replace(/\[42m/gi, "\1" + "2"); // Green background
-	txt = txt.replace(/\[43m/gi, "\1" + "3"); // Yellow background
-	txt = txt.replace(/\[44m/gi, "\1" + "4"); // Blue background
-	txt = txt.replace(/\[45m/gi, "\1" + "5"); // Magenta background
-	txt = txt.replace(/\[46m/gi, "\1" + "6"); // Cyan background
-	txt = txt.replace(/\[47m/gi, "\1" + "7"); // White background
-	// Convert ;-delimited modes (such as alue;...;Valuem)
-	txt = ANSIMultiConvertToSyncCodes(txt);
-	// Remove ANSI codes that are not wanted (such as moving the cursor, etc.)
-	txt = txt.replace(/\[[0-9]+a/gi, ""); // Cursor up
-	txt = txt.replace(/\[[0-9]+b/gi, ""); // Cursor down
-	txt = txt.replace(/\[[0-9]+c/gi, ""); // Cursor forward
-	txt = txt.replace(/\[[0-9]+d/gi, ""); // Cursor backward
-	txt = txt.replace(/\[[0-9]+;[0-9]+h/gi, ""); // Cursor position
-	txt = txt.replace(/\[[0-9]+;[0-9]+f/gi, ""); // Cursor position
-	txt = txt.replace(/\[s/gi, ""); // Restore cursor position
-	txt = txt.replace(/\[2j/gi, ""); // Erase display
-	txt = txt.replace(/\[k/gi, ""); // Erase line
-	txt = txt.replace(/\[=[0-9]+h/gi, ""); // Set various screen modes
-	txt = txt.replace(/\[=[0-9]+l/gi, ""); // Reset various screen modes
-	return txt;
-}
-
-// Converts ANSI ;-delimited modes (such as alue;...;Valuem) to Synchronet
-// attribute codes
-//
-// Parameters:
-//  pText: The text with ANSI ;-delimited modes to convert
-//
-// Return value: The text with ANSI ;-delimited modes converted to Synchronet attributes
-function ANSIMultiConvertToSyncCodes(pText)
-{
-	var multiMatches = pText.match(/\[[0-9]+(;[0-9]+)+m/g);
-	if (multiMatches == null)
-		return pText;
-	var updatedText = pText;
-	for (var i = 0; i < multiMatches.length; ++i)
-	{
-		// Copy the string, with the [ removed from the beginning and the
-		// trailing 'm' removed
-		var text = multiMatches[i].substr(2);
-		text = text.substr(0, text.length-1);
-		var codes = text.split(";");
-		var syncCodes = "";
-		for (var idx = 0; idx < codes.length; ++idx)
-		{
-			if (codes[idx] == "0") // All attributes off
-				syncCodes += "\1n";
-			else if (codes[idx] == "1") // Bold on (high intensity)
-				syncCodes += "\1h";
-			else if (codes[idx] == "5") // Blink on
-				syncCodes += "\1i";
-			else if (codes[idx] == "30") // Black foreground
-				syncCodes += "\1k";
-			else if (codes[idx] == "31") // Red foreground
-				syncCodes += "\1r";
-			else if (codes[idx] == "32") // Green foreground
-				syncCodes += "\1g";
-			else if (codes[idx] == "33") // Yellow foreground
-				syncCodes += "\1y";
-			else if (codes[idx] == "34") // Blue foreground
-				syncCodes += "\1b";
-			else if (codes[idx] == "35") // Magenta foreground
-				syncCodes += "\1m";
-			else if (codes[idx] == "36") // Cyan foreground
-				syncCodes += "\1c";
-			else if (codes[idx] == "37") // White foreground
-				syncCodes += "\1w";
-			else if (codes[idx] == "40") // Black background
-				syncCodes += "\1" + "0";
-			else if (codes[idx] == "41") // Red background
-				syncCodes += "\1" + "1";
-			else if (codes[idx] == "42") // Green background
-				syncCodes += "\1" + "2";
-			else if (codes[idx] == "43") // Yellow background
-				syncCodes += "\1" + "3";
-			else if (codes[idx] == "44") // Blue background
-				syncCodes += "\1" + "4";
-			else if (codes[idx] == "45") // Magenta background
-				syncCodes += "\1" + "5";
-			else if (codes[idx] == "46") // Cyan background
-				syncCodes += "\1" + "6";
-			else if (codes[idx] == "47") // White background
-				syncCodes += "\1" + "7";
-		}
-		updatedText = updatedText.replace(multiMatches[i], syncCodes);
-	}
-	return updatedText;
-}
-
-// Color/attribute code conversion functions end
-///////////////////////////////////////////////////////////////////////////////////////
-
-// Finds any Synchronet attributes in an array of TextLine objects before a given
-// text line index and edit line index in the array of lines.
-//
-// Parameters:
-//  pEditLines: An array of TextLine objects
-//  pEditLineIdx: The starting edit line index in the array
-//  pTextLineIdx: The ending text line index in the starting line
-//
-// Return value: A string containing any Synchronet attribute codes found in the
-//               array of text lines before the given indexes
-function findAttrCodesInLinesBeforeIdx(pEditLines, pEditLineIdx, pTextLineIdx)
-{
-	if ((pEditLineIdx < 0) || (pEditLineIdx >= pEditLines.length))
-		return "";
-	if (pTextLineIdx < 0)
-		return "";
-	// Let the text line index be one past the last character but no more than that
-	var textLineIdx = (pTextLineIdx > pEditLines[pEditLineIdx].text.length ? pEditLines[pEditLineIdx].text.length : pTextLineIdx);
-
-	var attrCodes = "";
-	var attrCodeObj = getAttrsAndIndexesBeforeStrIdx(pEditLines[pEditLineIdx].text, textLineIdx-1);
-	var startIdx = attrCodeObj.syncAttrStartIdx;
-	while (startIdx > -1)
-	{
-		attrCodes = attrCodeObj.attrStr + attrCodes;
-		attrCodeObj = getAttrsAndIndexesBeforeStrIdx(pEditLines[pEditLineIdx].text, startIdx);
-		startIdx = attrCodeObj.syncAttrStartIdx;
-	}
-	for (var i = pEditLineIdx - 1; i >= 0; --i)
-	{
-		attrCodeObj = getAttrsAndIndexesBeforeStrIdx(pEditLines[i].text, pEditLines[i].text.length-1);
-		startIdx = attrCodeObj.syncAttrStartIdx;
-		while (startIdx > -1)
-		{
-			attrCodes = attrCodeObj.attrStr + attrCodes;
-			attrCodeObj = getAttrsAndIndexesBeforeStrIdx(pEditLines[i].text, startIdx);
-			startIdx = attrCodeObj.syncAttrStartIdx;
-		}
-	}
-	// TODO: Remove redundancies in attrCodes?
-	return attrCodes;
-}
-
-// Removes stray ASCII-1 characters from a string that aren't part of a Synchronet
-// color code.
-//
-// Parameters:
-//  pStr: The string to remove stray ASCII-1 codes from
-//
-// Return value: The string with the stray characters removed
-function removeStrayANSIOneChars(pStr)
-{
-	var str = pStr;
-	var idx = -1;
-	var startIdx = 0;
-	while ((idx = str.indexOf("\1", startIdx)) > -1)
-	{
-		if (!gSyncAttrRegex.test(str.substr(idx, 2)))
-		{
-			str = str.substr(0, idx) + str.substr(idx+1);
-			startIdx = idx;
-		}
-		else
-			startIdx = idx + 2;
-	}
-	return str;
-}
-
-///////////////////////////////////////////////////////////////////////////////////////
-// Debugging
-
 // This function displays debug text at a given location on the screen, then
 // moves the cursor back to a given location.
 //
 // Parameters:
-//  pDebugX: The X location of where to write the debug text
-//  pDebugY: The Y location of where to write the debug text
+//  pDebugX: The X lcoation of where to write the debug text
+//  pDebugY: The Y lcoation of where to write the debug text
 //  pText: The text to write at the debug location
 //  pOriginalPos: An object with x and y properties containing the original cursor position
 //  pClearDebugLineFirst: Whether or not to clear the debug line before writing the text
 //  pPauseAfter: Whether or not to pause after displaying the text
-//  pClearLineAfter: Optional - Whether or not to clear the line after displaying the text
-function displayDebugText(pDebugX, pDebugY, pText, pOriginalPos, pClearDebugLineFirst, pPauseAfter, pClearLineAfter)
+function displayDebugText(pDebugX, pDebugY, pText, pOriginalPos, pClearDebugLineFirst, pPauseAfter)
 {
 	console.gotoxy(pDebugX, pDebugY);
 	if (pClearDebugLineFirst)
-	{
-		//console.clearline("\1n");
-		console.cleartoeol("\1n");
-	}
+		console.clearline();
 	// Output the text
 	console.print(pText);
 	if (pPauseAfter)
-		console.pause();
-	var clearLineAfter = (typeof(pClearLineAfter) == "boolean" ? pClearLineAfter : false);
-	if (clearLineAfter)
-	{
-		console.gotoxy(pDebugX, pDebugY);
-		console.cleartoeol("\1n");
-	}
+      console.pause();
 	if ((typeof(pOriginalPos) != "undefined") && (pOriginalPos != null))
 		console.gotoxy(pOriginalPos);
-}
+}
\ No newline at end of file
-- 
GitLab