diff --git a/docs/slyedit_readme.txt b/docs/slyedit_readme.txt
index e5be78a682b9e4f2105deb3f48a1e144a3275a7d..d78d93a923de3374cfe757bb0530ab4085ef06c5 100644
--- a/docs/slyedit_readme.txt
+++ b/docs/slyedit_readme.txt
@@ -1,6 +1,6 @@
                          SlyEdit message editor
-                              Version 1.87a
-                        Release date: 2023-12-17
+                              Version 1.88
+                        Release date: 2024-02-07
 
                                   by
 
@@ -23,15 +23,16 @@ Contents
  2. Introduction
  3. Installation & Setup
  4. Features
- 5. Configuration file
- 6. Ice-style Color Theme Settings
- 7. DCT-style Color Theme Settings
- 8. Common colors (appearing in both Ice and DCT color theme files)
- 9. Text replacements (AKA Macros)
-10. User settings
-11. Taglines
-12. Spell check and dictionaries
-13. Version history
+ 5. UTF-8 support and CP437
+ 6. Configuration file
+ 7. Ice-style Color Theme Settings
+ 8. DCT-style Color Theme Settings
+ 9. Common colors (appearing in both Ice and DCT color theme files)
+10. Text replacements (AKA Macros)
+1`. User settings
+12. Taglines
+13. Spell check and dictionaries
+14. Version history
 
 
 1. Disclaimer
@@ -235,7 +236,16 @@ Ctrl-K       : Change text color            
 Ctrl-O       : Import a file                � Ctrl-X  : Export to file
 
 
-5. Configuration file
+5. 5. UTF-8 support and CP437
+=============================
+As of version 1.88, SlyEdit is able to accept UTF-8 character input, but it has
+Synchronet convert the input to CP437 internally. As of this writing, Synchronet
+didn't fully have UTF-8 string support yet.
+Internally, the new (at the time) K_CP437 mode bit is used when accepting user
+input.
+
+
+6. Configuration file
 =====================
 The configuration file, SlyEdit.cfg, is split up into 3 sections -
 Behavior, Ice colors, and DCT colors.  These sections are designated
@@ -427,7 +437,7 @@ Setting                           Description
 -------                           -----------
 ThemeFilename                     The name of the color theme file to use.
                                   Note: DCT-style theme settings are described
-                                  in Section 6: DCT-style Color Theme Settings.
+                                  in Section 8: DCT-style Color Theme Settings.
                                   If no theme file is specified, then default
                                   colors will be used.
 
@@ -442,7 +452,7 @@ High green: gh
 Normal cyan: c
 
 
-6. Ice-style Color Theme Settings
+7. Ice-style Color Theme Settings
 =================================
 Note that you don't need control (Ctrl-A) characters for the color settings;
 just the attribute characters.
@@ -507,7 +517,7 @@ UnselectedOptionBorderColor       The color to use for the borders around
 UnselectedOptionTextColor         The color to use for the text for unselected
                                   multi-choice options
 
-7. DCT-style Color Theme Settings
+8. DCT-style Color Theme Settings
 =================================
 Note that you don't need control (Ctrl-A) characters for the color settings;
 just the attribute characters.
@@ -731,7 +741,7 @@ listBoxItemHighlight              The color to use for the currently selected
                                   item in list boxes (such as the list of text
                                   replacements and the list of tag lines)
 
-9. Text replacements (AKA Macros)
+10. Text replacements (AKA Macros)
 ==================================
 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.
@@ -824,7 +834,7 @@ store it in buffer 1, and in JavaScript (and with SlyEdit's search and
 replace), you would use $1 to refer to the word "darn".  For example, for
 (darn), the replacement $1it would replace the word "darn" with "darnit".
 
-10. User settings
+11. User settings
 =================
 Since version 1.32, SlyEdit has the ability for each user to configure some
 settings for themselves.  The user settings include the following:
@@ -858,7 +868,7 @@ The user settings files will be stored in the sbbs/data/user directory with the
 filename <user number>.SlyEdit_Settings, and the user number will be 0-padded
 up to 4 digits.
 
-11. Taglines
+12. Taglines
 ============
 SlyEdit version 1.32 added the ability for users to optionally choose a tagline
 to be appended to their message upon saving the message.  Each user can
@@ -888,7 +898,7 @@ user's signature (if they have one).  If the MSGINF file does not include the
 7th line, then the tagline will appear before the user's signature.
 
 
-12. Spell check and dictionaries
+13. Spell check and dictionaries
 ================================
 Since version 1.64, SlyEdit has a spell check feature.  Spell check can be
 started by the user with the Ctrl-R hotkey, or by the Edit > Spell Checker
@@ -928,10 +938,27 @@ case, since SlyEdit does case-insensitive matching by converting words in the
 message to lower-case and comparing them with the words in the dictionary.
 
 
-143 Version history
+14. Version history
 ===================
 Version  Date         Description
 -------  ----         -----------
+1.88     2024-02-07   Support for entering UTF-8/Unicode characters; internally
+                      uses K_CP437 to convert to CP437, so the strings are still
+                      in CP437 internally in Synchronet
+1.87a    2023-12-17   Using the msg_area.sub object to check sub-board settings
+                      instead of opening the sub-board (for determining whether
+                      to post with real name)
+1.87     2023-08-15   Improvement to paragraph/line breaks in quote line wrapping
+1.86     2023-07-26   Started refactoring the re-wrapping of quote lines to work
+                      better for the various quote prefixes used in various
+                      messages.
+1.85     2023-05-15   Internal: Refactored readColorConfig() in _DCTStuff.js
+                      and _IceStuff.js.
+                      Removed the readValueSettingConfigFile() function.
+1.84     2023-02-10   Sysops: When importing a file from the BBS machine,
+                      SlyEdit now prompts to send it immediately or not (if not,
+                      edit it before sending).  Sending immediately can be
+                      useful for posting ANSI files unmodified.
 1.83     2022-12-13   Quote lines that are wider than the user's terminal width
                       are now wrapped to the user's terminal width to ensure
 					  all the quote lines are entirely available to be quoted.
diff --git a/exec/SlyEdit.js b/exec/SlyEdit.js
index f92b4d2fb8aefa216f8f8a14671c2153aba5c6cb..1e806b5214e1442c7133e4176d14aa490bd5cb37 100644
--- a/exec/SlyEdit.js
+++ b/exec/SlyEdit.js
@@ -45,10 +45,16 @@
  *                              Using the msg_area.sub object to check sub-board settings instead
  *                              of opening the sub-board (for determining whether to post with
  *                              real name)
+ * 2024-02-07 Eric Oulashin     Version 1.88
+ *                              Support for entering UTF-8/Unicode characters; using K_CP437 to
+ *                              convert to CP437
  */
 
 "use strict";
 
+// TODO: UTF-8 support in FSeditor improved (for Keyop for typing a pound currency sign):
+// https://gitlab.synchro.net/main/sbbs/-/commit/66ed218f8a1032c16a674b62
+
 /* Command-line arguments:
  1 (argv[0]): Filename to read/edit
  2 (argv[1]): Editor mode ("DCT", "ICE", or "RANDOM")
@@ -135,8 +141,8 @@ if (console.screen_columns < 80)
 }
 
 // Version information
-var EDITOR_VERSION = "1.87a";
-var EDITOR_VER_DATE = "2023-12-17";
+var EDITOR_VERSION = "1.88";
+var EDITOR_VER_DATE = "2024-02-07";
 
 
 // Program variables
@@ -1878,6 +1884,10 @@ function doBackspace(pCurpos, pCurrentWordLength)
 		currentWordLength: pCurrentWordLength
 	};
 
+	// If the user's terminal is UTF-8 capable, we'll want to print as UTF-8.
+	//var printMode = (gUserConsoleSupportsUTF8 ? P_UTF8 : P_NONE);
+	var printMode = P_NONE;
+
 	var didBackspace = false;
 	// For later, store a backup of the current edit line index and
 	// cursor position.
@@ -1894,8 +1904,8 @@ function doBackspace(pCurpos, pCurrentWordLength)
 	{
 		if (gTextLineIndex > 0)
 		{
-			console.print(BACKSPACE);
-			console.print(" ");
+			console.print(BACKSPACE, printMode);
+			console.print(" ", printMode);
 			--retObj.x;
 			console.gotoxy(retObj.x, retObj.y);
 
@@ -2350,7 +2360,10 @@ function doPrintableChar(pUserInput, pCurpos, pCurrentWordLength)
 			displayEditLines(retObj.y, gEditLinesIndex, retObj.y, false, true);
 		else
 		{
-			console.print(pUserInput);
+			// If the user's terminal is UTF-8 capable, we'll want to print as UTF-8.
+			//var printMode = (gUserConsoleSupportsUTF8 ? P_UTF8 : P_NONE);
+			var printMode = P_NONE;
+			console.print(pUserInput, printMode);
 			placeCursorAtEnd = false; // Since we just output the character
 		}
 
@@ -3301,6 +3314,10 @@ function displayEditLines(pStartScreenRow, pArrayIndex, pEndScreenRow, pClearRem
 	// pEndScreenRow or gEditBottom.
 	var endScreenRow = (pEndScreenRow != null ? pEndScreenRow : gEditBottom);
 
+	// If the user's terminal is UTF-8 capable, we'll want to print the message text as UTF-8.
+	//var printMode = (gUserConsoleSupportsUTF8 ? P_UTF8 : P_NONE);
+	var printMode = P_NONE;
+
 	// Apply anny attribute codes until the given start array index
 	var currentAttrCodes = getAllEditLineAttrsUntilLineIdx(pArrayIndex);
 	console.print("\x01n" + currentAttrCodes);
@@ -3314,7 +3331,7 @@ function displayEditLines(pStartScreenRow, pArrayIndex, pEndScreenRow, pClearRem
 		if ((gEditAreaBuffer[screenLine] != textLine) || pIgnoreEditAreaBuffer)
 		{
 			// Make sure the text line doesn't exceed the edit width (unlikely)
-			if (console.strlen(textLine) > gEditWidth)
+			if (console.strlen(textLine, printMode) > gEditWidth)
 				textLine = shortenStrWithAttrCodes(textLine, gEditWidth, true);
 			// If the line is a quote line, then apply the quote line color (and strip other
 			// attribute codes from the line)
@@ -3325,7 +3342,7 @@ function displayEditLines(pStartScreenRow, pArrayIndex, pEndScreenRow, pClearRem
 			else if (arrayIndex > 0 && isQuoteLine(gEditLines, arrayIndex-1))
 				textLine = "\x01n" + textLine;
 			console.gotoxy(gEditLeft, screenLine);
-			console.print(textLine);
+			console.print(textLine, printMode);
 			gEditAreaBuffer[screenLine] = textLine;
 			// Clear to the end of the line, to erase any previously written text.
 			console.cleartoeol("\x01n");
@@ -3832,7 +3849,7 @@ function importFile(pCurpos)
 	{
 		// Go to the last row on the screen and prompt the user for a filename
 		var promptText = "\x01n\x01cFile:\x01h";
-		var promptTextLen = strip_ctrl(promptText).length;
+		var promptTextLen = console.strlen(promptText);
 		console.gotoxy(1, console.screen_rows);
 		console.cleartoeol("\x01n");
 		console.print(promptText);
@@ -3985,7 +4002,7 @@ function exportToFile()
 
    // Go to the last row on the screen and prompt the user for a filename
    var promptText = "\x01n\x01cFile:\x01h";
-   var promptTextLen = strip_ctrl(promptText).length;
+   var promptTextLen = console.strlen(promptText);
    console.gotoxy(1, console.screen_rows);
    console.cleartoeol("\x01n");
    console.print(promptText);
@@ -4044,7 +4061,7 @@ function findText(pCurpos)
 
 	// Go to the last row on the screen and prompt the user for text to find
 	var promptText = "\x01n\x01cText:\x01h";
-	var promptTextLen = strip_ctrl(promptText).length;
+	var promptTextLen = console.strlen(promptText);
 	console.gotoxy(1, console.screen_rows);
 	console.cleartoeol("\x01n");
 	console.print(promptText);
@@ -4369,6 +4386,10 @@ function spellCheckWordInLine(pDictionaries, pEditLineIdx, pWordArray, pWordIdx,
 		return retObj;
 	}
 
+	// If the user's terminal is UTF-8 capable, we'll want to count the text as UTF-8.
+	//var textMode = (gUserConsoleSupportsUTF8 ? P_UTF8 : P_NONE);
+	var textMode = P_NONE;
+
 	// Ensure the word doesn't have any whitespace and isn't just whitespace
 	currentWord = trimSpaces(currentWord, true, true, true);
 	if (currentWord.length > 0)
@@ -4430,7 +4451,7 @@ function spellCheckWordInLine(pDictionaries, pEditLineIdx, pWordArray, pWordIdx,
 				//console.gotoxy(retObj.x, retObj.y); // Updated line position
 				console.gotoxy(oldLineX, retObj.y);   // Old line position
 				console.print(gEditLines[pEditLineIdx].substr(true, wordIdxInLine, currentWord.length));
-				retObj.x = wordIdxInLine + strip_ctrl(pWordArray[pWordIdx]).length + 1;
+				retObj.x = wordIdxInLine + console.strlen(pWordArray[pWordIdx], textMode) + 1;
 				// Prompt the user for a corrected word.  If they enter
 				// a new word, then fix it in the text line.
 				var wordCorrectRetObj = inputWordCorrection(currentWord, { x: retObj.x, y: retObj.y }, pEditLineIdx);
@@ -4537,6 +4558,10 @@ function inputWordCorrection(pMisspelledWord, pCurpos, pEditLineIdx)
 
 	var originalCurpos = pCurpos;
 
+	// If the user's terminal is UTF-8 capable, we'll want to count the text as UTF-8.
+	//var textMode = (gUserConsoleSupportsUTF8 ? P_UTF8 : P_NONE);
+	var textMode = P_NONE;
+
 	// Create and display a text area with the misspelled word as the title
 	// and get user input for the corrected word
 	// For the 'text box', ensure the width is at most 80 characters
@@ -4550,7 +4575,7 @@ function inputWordCorrection(pMisspelledWord, pCurpos, pEditLineIdx)
 	var borderLine = "\x01n\x01g" + UPPER_LEFT_SINGLE + RIGHT_T_SINGLE;
 	borderLine += "\x01b\x01h" + pMisspelledWord.substr(0, txtBoxWidth-4);
 	borderLine += "\x01n\x01g" + LEFT_T_SINGLE;
-	var remainingWidth = txtBoxWidth - strip_ctrl(borderLine).length - 1;
+	var remainingWidth = txtBoxWidth - console.strlen(borderLine) - 1;
 	for (var i = 0; i < remainingWidth; ++i)
 		borderLine += HORIZONTAL_SINGLE;
 	borderLine += UPPER_RIGHT_SINGLE + "\x01n";
@@ -4559,7 +4584,7 @@ function inputWordCorrection(pMisspelledWord, pCurpos, pEditLineIdx)
 	// Draw the bottom border of the input box
 	borderLine = "\x01g" + LOWER_LEFT_SINGLE + RIGHT_T_SINGLE;
 	borderLine += "\x01c\x01hEnter\x01y=\x01bNo change\x01n\x01g" + LEFT_T_SINGLE + RIGHT_T_SINGLE + "\x01H\x01cCtrl-C\x01n\x01c/\x01hESC\x01y=\x01bEnd\x01n\x01g" + LEFT_T_SINGLE;
-	var remainingWidth = txtBoxWidth - strip_ctrl(borderLine).length - 1;
+	var remainingWidth = txtBoxWidth - console.strlen(borderLine) - 1;
 	for (var i = 0; i < remainingWidth; ++i)
 		borderLine += HORIZONTAL_SINGLE;
 	borderLine += LOWER_RIGHT_SINGLE + "\x01n";
@@ -4582,6 +4607,7 @@ function inputWordCorrection(pMisspelledWord, pCurpos, pEditLineIdx)
 	var continueOn = true;
 	while(continueOn)
 	{
+		// Note: getKeyWithESCChars() accounts for UTF-8
 		var userInputChar = getKeyWithESCChars(K_NOCRLF|K_NOSPIN, gConfigSettings);
 		switch (userInputChar)
 		{
@@ -4604,7 +4630,7 @@ function inputWordCorrection(pMisspelledWord, pCurpos, pEditLineIdx)
 			default:
 				// Append the character to the new word if the word is less than
 				// the maximum input length
-				if ((strip_ctrl(retObj.newWord).length < maxInputLen) && isPrintableChar(userInputChar))
+				if ((console.strlen(retObj.newWord, textMode) < maxInputLen) && isPrintableChar(userInputChar))
 				{
 					retObj.newWord += userInputChar;
 					console.print(userInputChar);
@@ -4862,7 +4888,7 @@ function displayCrossPostHelp(selBoxUpperLeft, selBoxLowerRight)
       console.print(displayCrossPostHelp.helpLines[i]);
       // If the text line is shorter than the inner width of the box, then
       // blank the rest of the line.
-      lineLen = strip_ctrl(displayCrossPostHelp.helpLines[i]).length;
+      lineLen = console.strlen(displayCrossPostHelp.helpLines[i]);
       if (lineLen < selBoxInnerWidth)
       {
          var numSpaces = selBoxInnerWidth - lineLen;
@@ -5429,6 +5455,10 @@ function printEditLine(pIndex, pUseColors, pStart, pLength)
 	//if (length > (gEditLines[pIndex].text.length - start))
 	//	length = gEditLines[pIndex].text.length - start;
 
+	// If the user's terminal is UTF-8 capable, we'll want to count the text as UTF-8.
+	//var textMode = (gUserConsoleSupportsUTF8 ? P_UTF8 : P_NONE);
+	var textMode = P_NONE;
+
 	var lengthWritten = 0;
 	if (useColors)
 	{
@@ -5437,7 +5467,7 @@ function printEditLine(pIndex, pUseColors, pStart, pLength)
 		//var lineText = substrWithAttrCodes(gEditLines[pIndex].getText(true), start, lineLengthToGet);
 		// The line's substr() will include the necessary attribute codes
 		var lineText = gEditLines[pIndex].substr(true, start, lineLengthToGet);
-		lengthWritten = console.strlen(lineText);
+		lengthWritten = console.strlen(lineText, textMode);
 		console.print(lineText);
 	}
 	else
@@ -5450,11 +5480,11 @@ function printEditLine(pIndex, pUseColors, pStart, pLength)
 			// Just print the entire line.
 			lengthWritten = gEditLines[pIndex].text.length;
 			if (length <= 0)
-				console.print(gEditLines[pIndex].text);
+				console.print(gEditLines[pIndex].text, textMode);
 			else
 			{
 				var textToWrite = gEditLines[pIndex].text.substr(start, length);
-				console.print(textToWrite);
+				console.print(textToWrite, textMode);
 				lengthWritten = textToWrite.length;
 			}
 		}
@@ -5466,8 +5496,8 @@ function printEditLine(pIndex, pUseColors, pStart, pLength)
 				textToWrite = gEditLines[pIndex].text.substr(start);
 			else
 				textToWrite = gEditLines[pIndex].text.substr(start, length);
-			console.print(textToWrite);
-			lengthWritten = textToWrite.length;
+			console.print(textToWrite, textMode);
+			lengthWritten = console.strlen(textToWrite, textMode);
 		}
 	}
 	return lengthWritten;
@@ -5527,7 +5557,7 @@ function listTextReplacements()
 			listTextReplacements.topBorder += HORIZONTAL_SINGLE;
 		listTextReplacements.topBorder += UPPER_RIGHT_SINGLE;
 	}
-	boxInfo.width = strip_ctrl(listTextReplacements.topBorder).length;
+	boxInfo.width = console.strlen(listTextReplacements.topBorder);
 	if (typeof(listTextReplacements.bottomBorder) == "undefined")
 	{
 		var numReplacementsStr = "Total: " + listTextReplacements.txtReplacementArr.length;
diff --git a/exec/SlyEdit_DCTStuff.js b/exec/SlyEdit_DCTStuff.js
index b6194f3ef8875766c692e0aa0ac759b8bf7aea27..f399192471d3436f2916b500234df89a34930550 100644
--- a/exec/SlyEdit_DCTStuff.js
+++ b/exec/SlyEdit_DCTStuff.js
@@ -398,7 +398,7 @@ function DisplayBottomHelpLine_DCTStyle(pLineNum, pUsingQuotes)
 		// 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);
+		var numSpaces = (console.screen_columns/2).toFixed(0) - (console.strlen(DisplayBottomHelpLine_DCTStyle.helpText)/2).toFixed(0);
 		for (var i = 0; i < numSpaces; ++i)
 			DisplayBottomHelpLine_DCTStyle.helpText = " " + DisplayBottomHelpLine_DCTStyle.helpText;
 	}
@@ -448,7 +448,7 @@ function DrawQuoteWindowTopBorder_DCTStyle(pQuoteWinHeight, pEditLeft, pEditRigh
                                 + UPPER_LEFT_SINGLE + HORIZONTAL_SINGLE + " "
                                 + gConfigSettings.DCTColors.QuoteWinBorderTextColor
                                 + "Quote Window " + gConfigSettings.DCTColors.QuoteWinBorderColor;
-      var curLength = strip_ctrl(DrawQuoteWindowTopBorder_DCTStyle.border).length;
+      var curLength = console.strlen(DrawQuoteWindowTopBorder_DCTStyle.border);
       var borderWidth = pEditRight - pEditLeft;
       for (var i = curLength; i < borderWidth; ++i)
          DrawQuoteWindowTopBorder_DCTStyle.border += HORIZONTAL_SINGLE;
@@ -488,7 +488,7 @@ function DrawQuoteWindowBottomBorder_DCTStyle(pEditLeft, pEditRight)
                          + HORIZONTAL_SINGLE + gConfigSettings.DCTColors.QuoteWinBorderTextColor
                          + "[F/L] First/last page";
 						 */
-      var helpTextLen = strip_ctrl(quoteHelpText).length;
+      var helpTextLen = console.strlen(quoteHelpText);
 
       // Figure out the starting horizontal position on the screen so that
       // the quote help text line can be centered.
@@ -1321,7 +1321,7 @@ function DCTMenu_DoInputLoop()
 		// Output this.clearSpaceTopText
 		console.print(this.clearSpaceTopText);
 		// Output the rest of the blank space
-		var textLen = strip_ctrl(this.clearSpaceTopText).length;
+		var textLen = console.strlen(this.clearSpaceTopText);
 		if (textLen < this.width)
 		{
 			var numSpaces = this.width - textLen;
diff --git a/exec/SlyEdit_IceStuff.js b/exec/SlyEdit_IceStuff.js
index fedea197a193124d548d939b8fc3c9357b44aa8d..2096b07d1fe5a3239c74192364ee92dcced8dde8 100644
--- a/exec/SlyEdit_IceStuff.js
+++ b/exec/SlyEdit_IceStuff.js
@@ -232,7 +232,7 @@ function redrawScreen_IceStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEd
       var startPos = (console.screen_columns/2).toFixed(0) - (msgAreaName.length/2).toFixed(0) - 2;
       // Write border characters up to the message area name start position
       screenText = "";
-      for (var i = strip_ctrl(redrawScreen_IceStyle.msgAreaBorder).length; i < startPos; ++i)
+      for (var i = console.strlen(redrawScreen_IceStyle.msgAreaBorder); i < startPos; ++i)
          screenText += HORIZONTAL_SINGLE;
       redrawScreen_IceStyle.msgAreaBorder += randomTwoColorString(screenText,
                                                              gConfigSettings.iceColors.BorderColor1,
@@ -252,7 +252,7 @@ function redrawScreen_IceStyle(pEditLeft, pEditRight, pEditTop, pEditBottom, pEd
       // Write horizontal border characters up until the point where we'll output
       // the node number.
       screenText = "";
-      for (var posX = strip_ctrl(redrawScreen_IceStyle.msgAreaBorder).length; posX < nodeFieldStartPos; ++posX)
+      for (var posX = console.strlen(redrawScreen_IceStyle.msgAreaBorder); posX < nodeFieldStartPos; ++posX)
          screenText += HORIZONTAL_SINGLE;
       redrawScreen_IceStyle.msgAreaBorder += randomTwoColorString(screenText,
                                                             gConfigSettings.iceColors.BorderColor1,
@@ -341,8 +341,8 @@ function DisplayTextAreaBottomBorder_IceStyle(pLineNum, pUseQuotes, pEditLeft, p
       // Append border characters up until the point we'll have to write the CTRL key
       // help text.
       var screenText = "";
-      var endPos = console.screen_columns - strip_ctrl(ctrlKeyHelp).length - 3;
-      var textLen = strip_ctrl(DisplayTextAreaBottomBorder_IceStyle.border).length;
+      var endPos = console.screen_columns - console.strlen(ctrlKeyHelp) - 3;
+      var textLen = console.strlen(DisplayTextAreaBottomBorder_IceStyle.border);
       for (var i = textLen+1; i < endPos; ++i)
          screenText += HORIZONTAL_SINGLE;
       DisplayTextAreaBottomBorder_IceStyle.border += randomTwoColorString(screenText,
@@ -389,7 +389,7 @@ function DisplayBottomHelpLine_IceStyle(pLineNum, pUsingQuotes)
 		// 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);
+		         - (console.strlen(screenText) / 2).toFixed(0);
 		DisplayBottomHelpLine_IceStyle.helpText = "";
 		for (var i = 0; i < xPos; ++i)
 			DisplayBottomHelpLine_IceStyle.helpText += " ";
@@ -527,7 +527,7 @@ function promptYesNo_IceStyle(pQuestion, pDefaultYes)
    displayIceYesNoText(pDefaultYes);
 
    // yesNoX contains the horizontal position for the "Yes" & "No" text.
-   const yesNoX = strip_ctrl(pQuestion).length + 3;
+   const yesNoX = console.strlen(pQuestion) + 3;
 
    // Input loop
    var userInput = "";
diff --git a/exec/SlyEdit_Misc.js b/exec/SlyEdit_Misc.js
index 9f0ea0f1835af1eb1c72b43edfd1f3a9944f655b..ddea1d0276c3a4b7023313595c06d751d6aad752 100644
--- a/exec/SlyEdit_Misc.js
+++ b/exec/SlyEdit_Misc.js
@@ -41,11 +41,13 @@ if (typeof(require) === "function")
 {
 	require("text.js", "Pause");
 	require("key_defs.js", "CTRL_A");
+	require("userdefs.js", "USER_ANSI");
 }
 else
 {
 	load("text.js");
 	load("key_defs.js");
+	load("userdefs.js");
 }
  
 // Note: These variables are declared with "var" instead of "const" to avoid
@@ -146,7 +148,7 @@ var ESC_MENU_USER_SETTINGS = 12;
 var ESC_MENU_SPELL_CHECK = 13;
 
 
-var COPYRIGHT_YEAR = 2022;
+var COPYRIGHT_YEAR = 2024;
 
 // Store the full path & filename of the Digital Distortion Message
 // Lister, since it will be used more than once.
@@ -154,6 +156,12 @@ var gDDML_DROP_FILE_NAME = system.node_dir + "DDML_SyncSMBInfo.txt";
 
 var gUserSettingsFilename = system.data_dir + "user/" + format("%04d", user.number) + ".SlyEdit_Settings";
 
+// See if the user's terminal supports UTF-8 (USER_UTF8 is defined in userdefs.js)
+var gUserConsoleSupportsUTF8 = (typeof(USER_UTF8) != "undefined" ? console.term_supports(USER_UTF8) : false);
+// See if K_CP437 is defined (for the input mode, for UTF-8 terminals).  And cache
+// the result for speed with further calls, since this function will be called repeatedly.
+var g_K_CP437Exists = (typeof(K_CP437) === "number");
+
 ///////////////////////////////////////////////////////////////////////////////////
 // Object/class stuff
 
@@ -271,7 +279,14 @@ function TextLine_length()
 // For the TextLine class: Returns the printed length of the text (without any attribute codes, etc.)
 function TextLine_screenLength()
 {
-	return console.strlen(this.text);
+	// If we need the length as UTF-8 if user's terminal supports it and we're inputting UTF-8?  Maybe not,
+	// since we use K_CP437 in that case to convert to CP437..
+	//str_is_utf8(text)
+	//utf8_get_width(text)
+	// If the user's terminal is UTF-8 capable count the text as UTF-8
+	//var textMode = (gUserConsoleSupportsUTF8 ? P_UTF8 : P_NONE);
+	var textMode = P_NONE;
+	return console.strlen(this.text, textMode);
 }
 // For the TextLine class: Prints the text line, using its text attributes.
 //
@@ -1001,7 +1016,7 @@ function ChoiceScrollbox(pLeftX, pTopY, pWidth, pHeight, pTopBorderText, pSlyEdC
 	// 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)
+	if (console.strlen(pTopBorderText) > maxTopBorderTextLen)
 		pTopBorderText = pTopBorderText.substr(0, maxTopBorderTextLen);
 	this.topBorder = "\x01n" + pSlyEdCfgObj.genColors.listBoxBorder + UPPER_LEFT_SINGLE;
 	if (addTopTCharsAroundText)
@@ -1010,7 +1025,7 @@ function ChoiceScrollbox(pLeftX, pTopY, pWidth, pHeight, pTopBorderText, pSlyEdC
 	               + pTopBorderText + "\x01n" + pSlyEdCfgObj.genColors.listBoxBorder;
 	if (addTopTCharsAroundText)
 		this.topBorder += LEFT_T_SINGLE;
-	const topBorderTextLen = strip_ctrl(pTopBorderText).length;
+	const topBorderTextLen = console.strlen(pTopBorderText);
 	var numHorizBorderChars = innerBorderWidth - topBorderTextLen - 20;
 	if (addTopTCharsAroundText)
 		numHorizBorderChars -= 2;
@@ -1027,7 +1042,7 @@ function ChoiceScrollbox(pLeftX, pTopY, pWidth, pHeight, pTopBorderText, pSlyEdC
 	this.bottomBorder = "\x01n" + pSlyEdCfgObj.genColors.listBoxBorder + LOWER_LEFT_SINGLE
 	                  + RIGHT_T_SINGLE + this.btmBorderNavText + "\x01n" + pSlyEdCfgObj.genColors.listBoxBorder
 	                  + LEFT_T_SINGLE;
-	var numCharsRemaining = this.dimensions.width - strip_ctrl(this.btmBorderNavText).length - 6;
+	var numCharsRemaining = this.dimensions.width - console.strlen(this.btmBorderNavText) - 6;
 	for (var i = 0; i < numCharsRemaining; ++i)
 		this.bottomBorder += HORIZONTAL_SINGLE;
 	this.bottomBorder += LOWER_RIGHT_SINGLE;
@@ -1256,7 +1271,7 @@ function ChoiceScrollbox_SetBottomBorderText(pText, pAddTChars, pAutoStripIfTooL
 
 	if (pAutoStripIfTooLong)
 	{
-		if (strip_ctrl(pText).length > innerWidth)
+		if (console.strlen(pText) > innerWidth)
 			pText = pText.substr(0, innerWidth);
 	}
 
@@ -1269,7 +1284,7 @@ function ChoiceScrollbox_SetBottomBorderText(pText, pAddTChars, pAutoStripIfTooL
 	this.bottomBorder += pText + "\x01n" + this.SlyEdCfgObj.genColors.listBoxBorder;
 	if (pAddTChars)
 		this.bottomBorder += LEFT_T_SINGLE;
-	var numCharsRemaining = this.dimensions.width - strip_ctrl(this.bottomBorder).length - 3;
+	var numCharsRemaining = this.dimensions.width - console.strlen(this.bottomBorder) - 3;
 	for (var i = 0; i < numCharsRemaining; ++i)
 		this.bottomBorder += HORIZONTAL_SINGLE;
 	this.bottomBorder += LOWER_RIGHT_SINGLE;
@@ -1775,7 +1790,7 @@ function displayHelpHeader()
       var headerText = EDITOR_PROGRAM_NAME + " Help \x01w(\x01y"
                       + (EDITOR_STYLE == "DCT" ? "DCT" : "Ice")
                       + " mode\x01w)";
-      var headerTextLen = strip_ctrl(headerText).length;
+      var headerTextLen = console.strlen(headerText);
 
       // Top border
       var headerTextStr = "\x01n\x01h\x01c" + UPPER_LEFT_SINGLE;
@@ -4300,6 +4315,11 @@ function consolePauseWithoutText()
 function getKeyWithESCChars(pGetKeyMode, pCfgObj)
 {
 	var getKeyMode = (typeof(pGetKeyMode) == "number" ? pGetKeyMode : K_NONE);
+	// If the user's terminal supports UTF-8, then allow UTF-8 input and convert
+	// it to cp437 (this is necessary because Synchronet's internal strings aren't
+	// always UTF-8)
+	if (gUserConsoleSupportsUTF8 && g_K_CP437Exists)
+		getKeyMode |= K_CP437;
 	var userInput = getUserKey(getKeyMode, pCfgObj);
 	if (userInput == KEY_ESC)
 	{
@@ -4782,15 +4802,15 @@ function shortenStrWithAttrCodes(pStr, pNewLength, pFromLeft)
 function centeredText(pWidth, pText)
 {
 	var givenText = pText;
-	var textLen = strip_ctrl(givenText).length;
+	var textLen = console.strlen(givenText);
 	if (textLen > pWidth)
 	{
 		givenText = shortenStrWithAttrCodes(givenText, pWidth);
-		textLen = strip_ctrl(givenText).length;
+		textLen = console.strlen(givenText);
 	}
 	var textX = Math.floor(pWidth / 2) - Math.floor(textLen/2);
 	var textStr = format("%" + textX + "s", "") + givenText;
-	var numSpacesRemaining = pWidth - strip_ctrl(textStr).length;
+	var numSpacesRemaining = pWidth - console.strlen(textStr);
 	textStr += format("%" + numSpacesRemaining + "s", "");
 	return textStr;
 }