From 3926ca13b0b32d3f5936241005e51d60041d4967 Mon Sep 17 00:00:00 2001
From: Marisa Heit <rheit@users.noreply.github.com>
Date: Fri, 11 Nov 2016 22:31:49 -0600
Subject: [PATCH] Fix console input not scrolling when you hit the side of the
 screen

- Also changed the console input buffer into a struct instead of a
  frankenstein buffer.
---
 src/c_console.cpp | 529 +++++++++++++++++++++++-----------------------
 src/v_video.cpp   |   2 -
 src/zstring.cpp   |  97 ++++++---
 src/zstring.h     |   1 +
 4 files changed, 340 insertions(+), 289 deletions(-)

diff --git a/src/c_console.cpp b/src/c_console.cpp
index 2d164c478..2be93a239 100644
--- a/src/c_console.cpp
+++ b/src/c_console.cpp
@@ -100,7 +100,7 @@ extern bool		advancedemo;
 extern FBaseCVar *CVars;
 extern FConsoleCommand *Commands[FConsoleCommand::HASH_SIZE];
 
-int			ConCols, PhysRows;
+unsigned	ConCols, PhysRows;
 int			ConWidth;
 bool		vidactive = false;
 bool		cursoron = false;
@@ -112,7 +112,6 @@ constate_e	ConsoleState = c_up;
 static int TopLine, InsertLine;
 
 static void ClearConsole ();
-static void C_PasteText(FString clip, BYTE *buffer, int len);
 
 struct GameAtExit
 {
@@ -139,27 +138,9 @@ static bool ConsoleDrawing;
 static char *work = NULL;
 static int worklen = 0;
 
-
-struct History
-{
-	struct History *Older;
-	struct History *Newer;
-	char String[1];
-};
-
-// CmdLine[0]  = # of chars on command line
-// CmdLine[1]  = cursor position
-// CmdLine[2+] = command line (max 255 chars + NULL)
-// CmdLine[259]= offset from beginning of cmdline to display
-static BYTE CmdLine[260];
-
-#define MAXHISTSIZE 50
-static struct History *HistHead = NULL, *HistTail = NULL, *HistPos = NULL;
-static int HistSize;
-
-CVAR (Float, con_notifytime, 3.f, CVAR_ARCHIVE)
-CVAR (Bool, con_centernotify, false, CVAR_ARCHIVE)
-CUSTOM_CVAR (Int, con_scaletext, 1, CVAR_ARCHIVE)		// Scale notify text at high resolutions?
+CVAR(Float, con_notifytime, 3.f, CVAR_ARCHIVE)
+CVAR(Bool, con_centernotify, false, CVAR_ARCHIVE)
+CUSTOM_CVAR(Int, con_scaletext, 1, CVAR_ARCHIVE)		// Scale notify text at high resolutions?
 {
 	if (self < 0) self = 0;
 	if (self > 3) self = 3;
@@ -172,10 +153,16 @@ CUSTOM_CVAR(Int, con_scale, 0, CVAR_ARCHIVE)
 
 int active_con_scale()
 {
-	if (con_scale == 0)
-		return uiscale;
-	else
-		return con_scale;
+	int scale = con_scale;
+	if (scale == 0)
+	{
+		scale = uiscale;
+		if (scale == 0)
+		{
+			scale = CleanXfac;
+		}
+	}
+	return scale;
 }
 
 int active_con_scaletext()
@@ -197,7 +184,195 @@ CUSTOM_CVAR(Float, con_alpha, 0.75f, CVAR_ARCHIVE)
 }
 
 // Command to run when Ctrl-D is pressed at start of line
-CVAR (String, con_ctrl_d, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
+CVAR(String, con_ctrl_d, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
+
+
+struct History
+{
+	struct History *Older;
+	struct History *Newer;
+	FString String;
+};
+
+struct FCommandBuffer
+{
+	FString Text;	// The actual command line text
+	unsigned CursorPos;
+	unsigned StartPos;	// First character to display
+
+	FCommandBuffer()
+	{
+		CursorPos = StartPos = 0;
+	}
+
+	FCommandBuffer(const FCommandBuffer &o)
+	{
+		Text = o.Text;
+		CursorPos = o.CursorPos;
+		StartPos = o.StartPos;
+	}
+
+	void Draw(int x, int y, int scale, bool cursor)
+	{
+		if (scale == 1)
+		{
+			screen->DrawChar(ConFont, CR_ORANGE, x, y, '\x1c', TAG_DONE);
+			screen->DrawText(ConFont, CR_ORANGE, x + ConFont->GetCharWidth(0x1c), y,
+				&Text[StartPos], TAG_DONE);
+
+			if (cursor)
+			{
+				screen->DrawChar(ConFont, CR_YELLOW,
+					x + ConFont->GetCharWidth(0x1c) + (CursorPos - StartPos) * ConFont->GetCharWidth(0xb),
+					y, '\xb', TAG_DONE);
+			}
+		}
+		else
+		{
+			screen->DrawChar(ConFont, CR_ORANGE, x, y, '\x1c',
+				DTA_VirtualWidth, screen->GetWidth() / scale,
+				DTA_VirtualHeight, screen->GetHeight() / scale,
+				DTA_KeepRatio, true, TAG_DONE);
+
+			screen->DrawText(ConFont, CR_ORANGE, x + ConFont->GetCharWidth(0x1c), y,
+				&Text[StartPos],
+				DTA_VirtualWidth, screen->GetWidth() / scale,
+				DTA_VirtualHeight, screen->GetHeight() / scale,
+				DTA_KeepRatio, true, TAG_DONE);
+
+			if (cursor)
+			{
+				screen->DrawChar(ConFont, CR_YELLOW,
+					x + ConFont->GetCharWidth(0x1c) + (CursorPos - StartPos) * ConFont->GetCharWidth(0xb),
+					y, '\xb',
+					DTA_VirtualWidth, screen->GetWidth() / scale,
+					DTA_VirtualHeight, screen->GetHeight() / scale,
+					DTA_KeepRatio, true, TAG_DONE);
+			}
+		}
+	}
+
+	void MakeStartPosGood()
+	{
+		int n = StartPos;
+		unsigned cols = ConCols / active_con_scale();
+
+		if (StartPos >= Text.Len())
+		{ // Start of visible line is beyond end of line
+			n = CursorPos - cols + 2;
+		}
+		if ((CursorPos - StartPos) >= cols - 2)
+		{ // The cursor is beyond the visible part of the line
+			n = CursorPos - cols + 2;
+		}
+		if (StartPos > CursorPos)
+		{ // The cursor is in front of the visible part of the line
+			n = CursorPos;
+		}
+		StartPos = MAX(0, n);
+	}
+
+	void CursorStart()
+	{
+		CursorPos = 0;
+		StartPos = 0;
+	}
+
+	void CursorEnd()
+	{
+		CursorPos = Text.Len();
+		StartPos = 0;
+		MakeStartPosGood();
+	}
+
+	void CursorLeft()
+	{
+		if (CursorPos > 0)
+		{
+			CursorPos--;
+			MakeStartPosGood();
+		}
+	}
+
+	void CursorRight()
+	{
+		if (CursorPos < Text.Len())
+		{
+			CursorPos++;
+			MakeStartPosGood();
+		}
+	}
+
+	void DeleteLeft()
+	{
+		if (CursorPos > 0)
+		{
+			Text.Remove(CursorPos - 1, 1);
+			CursorPos--;
+			MakeStartPosGood();
+		}
+	}
+
+	void DeleteRight()
+	{
+		if (CursorPos < Text.Len())
+		{
+			Text.Remove(CursorPos, 1);
+			MakeStartPosGood();
+		}
+	}
+
+	void AddChar(int character)
+	{
+		///FIXME: Not Unicode-aware
+		if (CursorPos == Text.Len())
+		{
+			Text += char(character);
+		}
+		else
+		{
+			char foo = char(character);
+			Text.Insert(CursorPos, &foo, 1);
+		}
+		CursorPos++;
+		MakeStartPosGood();
+	}
+
+	void AddString(FString clip)
+	{
+		if (clip.IsNotEmpty())
+		{
+			// Only paste the first line.
+			long brk = clip.IndexOfAny("\r\n\b");
+			if (brk >= 0)
+			{
+				clip.Truncate(brk);
+			}
+			if (Text.IsEmpty())
+			{
+				Text = clip;
+			}
+			else
+			{
+				Text.Insert(CursorPos, clip);
+			}
+			CursorPos += clip.Len();
+			MakeStartPosGood();
+		}
+	}
+
+	void SetString(FString str)
+	{
+		Text = str;
+		CursorPos = Text.Len();
+		MakeStartPosGood();
+	}
+};
+static FCommandBuffer CmdLine;
+
+#define MAXHISTSIZE 50
+static struct History *HistHead = NULL, *HistTail = NULL, *HistPos = NULL;
+static int HistSize;
 
 #define NUMNOTIFIES 4
 #define NOTIFYFADETIME 6
@@ -860,8 +1035,6 @@ void C_DrawConsole (bool hw2d)
 	int lines, left, offset;
 
 	int textScale = active_con_scale();
-	if (textScale == 0)
-		textScale = CleanXfac;
 
 	left = LEFTMARGIN;
 	lines = (ConBottom/textScale-ConFont->GetHeight()*2)/ConFont->GetHeight();
@@ -1037,44 +1210,8 @@ void C_DrawConsole (bool hw2d)
 			{
 				// Make a copy of the command line, in case an input event is handled
 				// while we draw the console and it changes.
-				CmdLine[2+CmdLine[0]] = 0;
-				FString command((char *)&CmdLine[2+CmdLine[259]]);
-				int cursorpos = CmdLine[1] - CmdLine[259];
-
-				if (textScale == 1)
-				{
-					screen->DrawChar(ConFont, CR_ORANGE, left, bottomline, '\x1c', TAG_DONE);
-					screen->DrawText(ConFont, CR_ORANGE, left + ConFont->GetCharWidth(0x1c), bottomline,
-						command, TAG_DONE);
-
-					if (cursoron)
-					{
-						screen->DrawChar(ConFont, CR_YELLOW, left + ConFont->GetCharWidth(0x1c) + cursorpos * ConFont->GetCharWidth(0xb),
-							bottomline, '\xb', TAG_DONE);
-					}
-				}
-				else
-				{
-					screen->DrawChar(ConFont, CR_ORANGE, left, bottomline, '\x1c',
-						DTA_VirtualWidth, screen->GetWidth() / textScale,
-						DTA_VirtualHeight, screen->GetHeight() / textScale,
-						DTA_KeepRatio, true, TAG_DONE);
-
-					screen->DrawText(ConFont, CR_ORANGE, left + ConFont->GetCharWidth(0x1c), bottomline,
-						command,
-						DTA_VirtualWidth, screen->GetWidth() / textScale,
-						DTA_VirtualHeight, screen->GetHeight() / textScale,
-						DTA_KeepRatio, true, TAG_DONE);
-
-					if (cursoron)
-					{
-						screen->DrawChar(ConFont, CR_YELLOW, left + ConFont->GetCharWidth(0x1c) + cursorpos * ConFont->GetCharWidth(0xb),
-							bottomline, '\xb',
-							DTA_VirtualWidth, screen->GetWidth() / textScale,
-							DTA_VirtualHeight, screen->GetHeight() / textScale,
-							DTA_KeepRatio, true, TAG_DONE);
-					}
-				}
+				FCommandBuffer command(CmdLine);
+				command.Draw(left, bottomline, textScale, cursoron);
 			}
 			if (RowAdjust && ConBottom >= ConFont->GetHeight()*7/2)
 			{
@@ -1146,35 +1283,8 @@ void C_HideConsole ()
 	}
 }
 
-static void makestartposgood ()
+static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
 {
-	int n;
-	int pos = CmdLine[259];
-	int curs = CmdLine[1];
-	int len = CmdLine[0];
-
-	n = pos;
-
-	if (pos >= len)
-	{ // Start of visible line is beyond end of line
-		n = curs - ConCols + 2;
-	}
-	if ((curs - pos) >= ConCols - 2)
-	{ // The cursor is beyond the visible part of the line
-		n = curs - ConCols + 2;
-	}
-	if (pos > curs)
-	{ // The cursor is in front of the visible part of the line
-		n = curs;
-	}
-	if (n < 0)
-		n = 0;
-	CmdLine[259] = n;
-}
-
-static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
-{
-	int i;
 	int data1 = ev->data1;
 
 	switch (ev->subtype)
@@ -1184,29 +1294,8 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 
 	case EV_GUI_Char:
 		// Add keypress to command line
-		if (buffer[0] < len)
-		{
-			if (buffer[1] == buffer[0])
-			{
-				buffer[buffer[0] + 2] = BYTE(ev->data1);
-			}
-			else
-			{
-				char *c, *e;
-
-				e = (char *)&buffer[buffer[0] + 1];
-				c = (char *)&buffer[buffer[1] + 2];
-
-				for (; e >= c; e--)
-					*(e + 1) = *e;
-
-				*c = char(ev->data1);
-			}
-			buffer[0]++;
-			buffer[1]++;
-			makestartposgood ();
-			HistPos = NULL;
-		}
+		buffer.AddChar(ev->data1);
+		HistPos = NULL;
 		TabbedLast = false;
 		TabbedList = false;
 		break;
@@ -1289,7 +1378,7 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 			}
 			else
 			{ // Move cursor to start of line
-				buffer[1] = buffer[len+4] = 0;
+				buffer.CursorStart();
 			}
 			break;
 
@@ -1300,66 +1389,30 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 			}
 			else
 			{ // Move cursor to end of line
-				buffer[1] = buffer[0];
-				makestartposgood ();
+				buffer.CursorEnd();
 			}
 			break;
 
 		case GK_LEFT:
 			// Move cursor left one character
-			if (buffer[1])
-			{
-				buffer[1]--;
-				makestartposgood ();
-			}
+			buffer.CursorLeft();
 			break;
 
 		case GK_RIGHT:
 			// Move cursor right one character
-			if (buffer[1] < buffer[0])
-			{
-				buffer[1]++;
-				makestartposgood ();
-			}
+			buffer.CursorRight();
 			break;
 
 		case '\b':
 			// Erase character to left of cursor
-			if (buffer[0] && buffer[1])
-			{
-				char *c, *e;
-
-				e = (char *)&buffer[buffer[0] + 2];
-				c = (char *)&buffer[buffer[1] + 2];
-
-				for (; c < e; c++)
-					*(c - 1) = *c;
-				
-				buffer[0]--;
-				buffer[1]--;
-				if (buffer[len+4])
-					buffer[len+4]--;
-				makestartposgood ();
-			}
+			buffer.DeleteLeft();
 			TabbedLast = false;
 			TabbedList = false;
 			break;
 
 		case GK_DEL:
 			// Erase character under cursor
-			if (buffer[1] < buffer[0])
-			{
-				char *c, *e;
-
-				e = (char *)&buffer[buffer[0] + 2];
-				c = (char *)&buffer[buffer[1] + 3];
-
-				for (; c < e; c++)
-					*(c - 1) = *c;
-
-				buffer[0]--;
-				makestartposgood ();
-			}
+			buffer.DeleteRight();
 			TabbedLast = false;
 			TabbedList = false;
 			break;
@@ -1377,10 +1430,7 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 
 			if (HistPos)
 			{
-				strcpy ((char *)&buffer[2], HistPos->String);
-				buffer[0] = buffer[1] = (BYTE)strlen ((char *)&buffer[2]);
-				buffer[len+4] = 0;
-				makestartposgood();
+				buffer.SetString(HistPos->String);
 			}
 
 			TabbedLast = false;
@@ -1392,17 +1442,13 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 			if (HistPos && HistPos->Newer)
 			{
 				HistPos = HistPos->Newer;
-			
-				strcpy ((char *)&buffer[2], HistPos->String);
-				buffer[0] = buffer[1] = (BYTE)strlen ((char *)&buffer[2]);
+				buffer.SetString(HistPos->String);
 			}
 			else
 			{
 				HistPos = NULL;
-				buffer[0] = buffer[1] = 0;
+				buffer.SetString("");
 			}
-			buffer[len+4] = 0;
-			makestartposgood();
 			TabbedLast = false;
 			TabbedList = false;
 			break;
@@ -1410,24 +1456,19 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 		case 'X':
 			if (ev->data3 & GKM_CTRL)
 			{
-				buffer[1] = buffer[0] = 0;
+				buffer.SetString("");
 				TabbedLast = TabbedList = false;
 			}
 			break;
 
 		case 'D':
-			if (ev->data3 & GKM_CTRL && buffer[0] == 0)
+			if (ev->data3 & GKM_CTRL && buffer.Text.Len() == 0)
 			{ // Control-D pressed on an empty line
-				int replen = (int)strlen (con_ctrl_d);
-
-				if (replen == 0)
+				if (strlen(con_ctrl_d) == 0)
+				{
 					break;	// Replacement is empty, so do nothing
-
-				if (replen > len)
-					replen = len;
-
-				memcpy (&buffer[2], con_ctrl_d, replen);
-				buffer[0] = buffer[1] = replen;
+				}
+				buffer.SetString(*con_ctrl_d);
 			}
 			else
 			{
@@ -1438,16 +1479,16 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 		case '\r':
 			// Execute command line (ENTER)
 
-			buffer[2 + buffer[0]] = 0;
+			buffer.Text.StripLeftRight();
+			Printf(127, TEXTCOLOR_WHITE "]%s\n", buffer.Text.GetChars());
+			AddCommandString(buffer.Text.LockBuffer());
+			buffer.Text.UnlockBuffer();
 
-			for (i = 0; i < buffer[0] && isspace(buffer[2+i]); ++i)
-			{
-			}
-			if (i == buffer[0])
+			if (buffer.Text.Len() == 0)
 			{
 				 // Command line is empty, so do nothing to the history
 			}
-			else if (HistHead && stricmp (HistHead->String, (char *)&buffer[2]) == 0)
+			else if (HistHead && HistHead->String.CompareNoCase(buffer.Text) == 0)
 			{
 				// Command line was the same as the previous one,
 				// so leave the history list alone
@@ -1458,9 +1499,8 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 				// or there is nothing in the history list,
 				// so add it to the history list.
 
-				History *temp = (History *)M_Malloc (sizeof(struct History) + buffer[0]);
-
-				strcpy (temp->String, (char *)&buffer[2]);
+				History *temp = new History;
+				temp->String = buffer.Text;
 				temp->Older = HistHead;
 				if (HistHead)
 				{
@@ -1477,7 +1517,7 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 				if (HistSize == MAXHISTSIZE)
 				{
 					HistTail = HistTail->Newer;
-					M_Free (HistTail->Older);
+					delete HistTail->Older;
 					HistTail->Older = NULL;
 				}
 				else
@@ -1486,9 +1526,7 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 				}
 			}
 			HistPos = NULL;
-			Printf (127, TEXTCOLOR_WHITE "]%s\n", &buffer[2]);
-			buffer[0] = buffer[1] = buffer[len+4] = 0;
-			AddCommandString ((char *)&buffer[2]);
+			buffer.SetString("");
 			TabbedLast = false;
 			TabbedList = false;
 			break;
@@ -1514,7 +1552,7 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 			}
 			else
 			{
-				buffer[0] = buffer[1] = buffer[len+4] = 0;
+				buffer.SetString("");
 				HistPos = NULL;
 				C_ToggleConsole ();
 			}
@@ -1532,15 +1570,15 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 			{
 				if (data1 == 'C')
 				{ // copy to clipboard
-					if (buffer[0] > 0)
+					if (buffer.Text.IsNotEmpty())
 					{
-						buffer[2 + buffer[0]] = 0;
-						I_PutInClipboard ((char *)&buffer[2]);
+						I_PutInClipboard(buffer.Text);
 					}
 				}
 				else
 				{ // paste from clipboard
-					C_PasteText(I_GetFromClipboard(false), buffer, len);
+					buffer.AddString(I_GetFromClipboard(false));
+					HistPos = NULL;
 				}
 				break;
 			}
@@ -1550,7 +1588,8 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 
 #ifdef __unix__
 	case EV_GUI_MButtonDown:
-		C_PasteText(I_GetFromClipboard(true), buffer, len);
+		buffer.AddString(I_GetFromClipboard(true));
+		HistPos = NULL;
 		break;
 #endif
 	}
@@ -1560,36 +1599,6 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
 	return true;
 }
 
-static void C_PasteText(FString clip, BYTE *buffer, int len)
-{
-	if (clip.IsNotEmpty())
-	{
-		// Only paste the first line.
-		long brk = clip.IndexOfAny("\r\n\b");
-		int cliplen = brk >= 0 ? brk : (int)clip.Len();
-
-		// Make sure there's room for the whole thing.
-		if (buffer[0] + cliplen > len)
-		{
-			cliplen = len - buffer[0];
-		}
-
-		if (cliplen > 0)
-		{
-			if (buffer[1] < buffer[0])
-			{
-				memmove (&buffer[2 + buffer[1] + cliplen],
-						 &buffer[2 + buffer[1]], buffer[0] - buffer[1]);
-			}
-			memcpy (&buffer[2 + buffer[1]], clip, cliplen);
-			buffer[0] += cliplen;
-			buffer[1] += cliplen;
-			makestartposgood ();
-			HistPos = NULL;
-		}
-	}
-}
-
 bool C_Responder (event_t *ev)
 {
 	if (ev->type != EV_GUI_Event ||
@@ -1600,7 +1609,7 @@ bool C_Responder (event_t *ev)
 		return false;
 	}
 
-	return C_HandleKey (ev, CmdLine, 255);
+	return C_HandleKey(ev, CmdLine);
 }
 
 CCMD (history)
@@ -1793,7 +1802,7 @@ static int FindDiffPoint (FName name1, const char *str2)
 
 static void C_TabComplete (bool goForward)
 {
-	int i;
+	unsigned i;
 	int diffpoint;
 
 	if (!TabbedLast)
@@ -1801,25 +1810,20 @@ static void C_TabComplete (bool goForward)
 		bool cancomplete;
 
 		// Skip any spaces at beginning of command line
-		if (CmdLine[2] == ' ')
+		for (i = 0; i < CmdLine.Text.Len(); ++i)
 		{
-			for (i = 0; i < CmdLine[0]; i++)
-				if (CmdLine[2+i] != ' ')
-					break;
-
-			TabStart = i + 2;
+			if (CmdLine.Text[i] != ' ')
+				break;
 		}
-		else
-		{
-			TabStart = 2;
+		if (i == CmdLine.Text.Len())
+		{ // Line was nothing but spaces
+			return;
 		}
+		TabStart = i;
 
-		if (TabStart == CmdLine[0] + 2)
-			return;		// Line was nothing but spaces
+		TabSize = CmdLine.Text.Len() - TabStart;
 
-		TabSize = CmdLine[0] - TabStart + 2;
-
-		if (!FindTabCommand ((char *)(CmdLine + TabStart), &TabPos, TabSize))
+		if (!FindTabCommand(&CmdLine.Text[TabStart], &TabPos, TabSize))
 			return;		// No initial matches
 
 		// Show a list of possible completions, if more than one.
@@ -1842,7 +1846,7 @@ static void C_TabComplete (bool goForward)
 		{ // Find the last matching tab, then go one past it.
 			while (++TabPos < (int)TabCommands.Size())
 			{
-				if (FindDiffPoint (TabCommands[TabPos].TabName, (char *)(CmdLine + TabStart)) < TabSize)
+				if (FindDiffPoint(TabCommands[TabPos].TabName, &CmdLine.Text[TabStart]) < TabSize)
 				{
 					break;
 				}
@@ -1859,27 +1863,26 @@ static void C_TabComplete (bool goForward)
 		(!goForward && --TabPos < 0))
 	{
 		TabbedLast = false;
-		CmdLine[0] = CmdLine[1] = TabSize;
+		CmdLine.Text.Truncate(TabSize);
 	}
 	else
 	{
-		diffpoint = FindDiffPoint (TabCommands[TabPos].TabName, (char *)(CmdLine + TabStart));
+		diffpoint = FindDiffPoint(TabCommands[TabPos].TabName, &CmdLine.Text[TabStart]);
 
 		if (diffpoint < TabSize)
 		{
 			// No more matches
 			TabbedLast = false;
-			CmdLine[0] = CmdLine[1] = TabSize + TabStart - 2;
+			CmdLine.Text.Truncate(TabSize - TabStart);
 		}
 		else
-		{		
-			strcpy ((char *)(CmdLine + TabStart), TabCommands[TabPos].TabName.GetChars());
-			CmdLine[0] = CmdLine[1] = (BYTE)strlen ((char *)(CmdLine + 2)) + 1;
-			CmdLine[CmdLine[0] + 1] = ' ';
+		{
+			CmdLine.Text.Truncate(TabStart);
+			CmdLine.Text << TabCommands[TabPos].TabName << ' ';
 		}
 	}
-
-	makestartposgood ();
+	CmdLine.CursorPos = CmdLine.Text.Len();
+	CmdLine.MakeStartPosGood();
 }
 
 static bool C_TabCompleteList ()
@@ -1893,7 +1896,7 @@ static bool C_TabCompleteList ()
 
 	for (i = TabPos; i < (int)TabCommands.Size(); ++i)
 	{
-		if (FindDiffPoint (TabCommands[i].TabName, (char *)(CmdLine + TabStart)) < TabSize)
+		if (FindDiffPoint (TabCommands[i].TabName, &CmdLine.Text[TabStart]) < TabSize)
 		{
 			break;
 		}
@@ -1918,7 +1921,7 @@ static bool C_TabCompleteList ()
 	{
 		size_t x = 0;
 		maxwidth += 3;
-		Printf (TEXTCOLOR_BLUE "Completions for %s:\n", CmdLine+2);
+		Printf (TEXTCOLOR_BLUE "Completions for %s:\n", CmdLine.Text.GetChars());
 		for (i = TabPos; nummatches > 0; ++i, --nummatches)
 		{
 			// [Dusk] Print console commands blue, CVars green, aliases red.
@@ -1936,7 +1939,7 @@ static bool C_TabCompleteList ()
 
 			Printf ("%s%-*s", colorcode, int(maxwidth), TabCommands[i].TabName.GetChars());
 			x += maxwidth;
-			if (x > ConCols - maxwidth)
+			if (x > ConCols / active_con_scale() - maxwidth)
 			{
 				x = 0;
 				Printf ("\n");
@@ -1950,9 +1953,9 @@ static bool C_TabCompleteList ()
 		if (TabSize != commonsize)
 		{
 			TabSize = commonsize;
-			strncpy ((char *)CmdLine + TabStart, TabCommands[TabPos].TabName.GetChars(), commonsize);
-			CmdLine[0] = TabStart + commonsize - 2;
-			CmdLine[1] = CmdLine[0];
+			CmdLine.Text.Truncate(TabStart);
+			CmdLine.Text.AppendCStrPart(TabCommands[TabPos].TabName.GetChars(), commonsize);
+			CmdLine.CursorPos = CmdLine.Text.Len();
 		}
 		return false;
 	}
diff --git a/src/v_video.cpp b/src/v_video.cpp
index b1f1ced9c..37cfdd480 100644
--- a/src/v_video.cpp
+++ b/src/v_video.cpp
@@ -877,8 +877,6 @@ void DFrameBuffer::DrawRateStuff ()
 			int rate_x;
 
 			int textScale = active_con_scale();
-			if (textScale == 0)
-				textScale = CleanXfac;
 
 			chars = mysnprintf (fpsbuff, countof(fpsbuff), "%2u ms (%3u fps)", howlong, LastCount);
 			rate_x = Width / textScale - ConFont->StringWidth(&fpsbuff[0]);
diff --git a/src/zstring.cpp b/src/zstring.cpp
index f4a3a1fbb..63fdf6428 100644
--- a/src/zstring.cpp
+++ b/src/zstring.cpp
@@ -343,28 +343,74 @@ FString &FString::operator += (char tail)
 
 FString &FString::AppendCStrPart (const char *tail, size_t tailLen)
 {
-	size_t len1 = Len();
-	ReallocBuffer (len1 + tailLen);
-	StrCopy (Chars + len1, tail, tailLen);
+	if (tailLen > 0)
+	{
+		size_t len1 = Len();
+		ReallocBuffer(len1 + tailLen);
+		StrCopy(Chars + len1, tail, tailLen);
+	}
 	return *this;
 }
 
 FString &FString::CopyCStrPart(const char *tail, size_t tailLen)
 {
-	ReallocBuffer(tailLen);
-	StrCopy(Chars, tail, tailLen);
+	if (tailLen > 0)
+	{
+		ReallocBuffer(tailLen);
+		StrCopy(Chars, tail, tailLen);
+	}
+	else
+	{
+		Data()->Release();
+		NullString.RefCount++;
+		Chars = &NullString.Nothing[0];
+	}
 	return *this;
 }
 
 void FString::Truncate(long newlen)
 {
-	if (newlen >= 0 && newlen < (long)Len())
+	if (newlen <= 0)
+	{
+		Data()->Release();
+		NullString.RefCount++;
+		Chars = &NullString.Nothing[0];
+	}
+	else if (newlen < (long)Len())
 	{
 		ReallocBuffer (newlen);
 		Chars[newlen] = '\0';
 	}
 }
 
+void FString::Remove(size_t index, size_t remlen)
+{
+	if (index < Len())
+	{
+		if (index + remlen >= Len())
+		{
+			Truncate((long)index);
+		}
+		else
+		{
+			remlen = Len() - remlen < remlen ? Len() - remlen : remlen;
+			if (Data()->RefCount == 1)
+			{ // Can do this in place
+				memmove(Chars + index, Chars + index + remlen, Len() - index - remlen);
+				Data()->Len -= remlen;
+			}
+			else
+			{ // Must do it in a copy
+				FStringData *old = Data();
+				AllocBuffer(old->Len - remlen);
+				StrCopy(Chars, old->Chars(), index);
+				StrCopy(Chars + index, old->Chars() + index + remlen, old->Len - index - remlen);
+				old->Release();
+			}
+		}
+	}
+}
+
 FString FString::Left (size_t numChars) const
 {
 	size_t len = Len();
@@ -768,25 +814,28 @@ void FString::Insert (size_t index, const char *instr)
 
 void FString::Insert (size_t index, const char *instr, size_t instrlen)
 {
-	size_t mylen = Len();
-	if (index > mylen)
+	if (instrlen > 0)
 	{
-		index = mylen;
-	}
-	if (Data()->RefCount <= 1)
-	{
-		ReallocBuffer (mylen + instrlen);
-		memmove (Chars + index + instrlen, Chars + index, (mylen - index + 1)*sizeof(char));
-		memcpy (Chars + index, instr, instrlen*sizeof(char));
-	}
-	else
-	{
-		FStringData *old = Data();
-		AllocBuffer (mylen + instrlen);
-		StrCopy (Chars, old->Chars(), index);
-		StrCopy (Chars + index, instr, instrlen);
-		StrCopy (Chars + index + instrlen, old->Chars() + index, mylen - index);
-		old->Release();
+		size_t mylen = Len();
+		if (index >= mylen)
+		{
+			AppendCStrPart(instr, instrlen);
+		}
+		else if (Data()->RefCount <= 1)
+		{
+			ReallocBuffer(mylen + instrlen);
+			memmove(Chars + index + instrlen, Chars + index, (mylen - index + 1) * sizeof(char));
+			memcpy(Chars + index, instr, instrlen * sizeof(char));
+		}
+		else
+		{
+			FStringData *old = Data();
+			AllocBuffer(mylen + instrlen);
+			StrCopy(Chars, old->Chars(), index);
+			StrCopy(Chars + index, instr, instrlen);
+			StrCopy(Chars + index + instrlen, old->Chars() + index, mylen - index);
+			old->Release();
+		}
 	}
 }
 
diff --git a/src/zstring.h b/src/zstring.h
index 00f373b59..ba9208719 100644
--- a/src/zstring.h
+++ b/src/zstring.h
@@ -268,6 +268,7 @@ public:
 	bool IsNotEmpty() const { return Len() != 0; }
 
 	void Truncate (long newlen);
+	void Remove(size_t index, size_t remlen);
 
 	int Compare (const FString &other) const { return strcmp (Chars, other.Chars); }
 	int Compare (const char *other) const { return strcmp (Chars, other); }