Quellcode durchsuchen

Merge remote-tracking branch 'upstream/main' into main

tznind vor 4 Jahren
Ursprung
Commit
0c80a4f1f2

+ 24 - 0
.devcontainer/devcontainer.json

@@ -0,0 +1,24 @@
+{
+	"name": "Terminal.Gui Codespace",
+	"image": "mcr.microsoft.com/vscode/devcontainers/dotnet:0.201.7-5.0",
+	"settings": {
+		"terminal.integrated.defaultProfile.linux": "pwsh"
+	},
+	"extensions": [
+		"eamodio.gitlens",
+		"ms-dotnettools.csharp",
+		"VisualStudioExptTeam.vscodeintellicode",
+		"ms-vscode.powershell",
+		"cschleiden.vscode-github-actions",
+		"redhat.vscode-yaml",
+		"bierner.markdown-preview-github-styles",
+		"ban.spellright",
+		"jmrog.vscode-nuget-package-manager",
+		"coenraads.bracket-pair-colorizer",
+		"vscode-icons-team.vscode-icons",
+		"editorconfig.editorconfig"
+	],
+	"postCreateCommand": "dotnet restore && dotnet build --configuration Release --no-restore && dotnet test --configuration Debug --no-restore --verbosity normal --collect:'XPlat Code Coverage' --settings UnitTests/coverlet.runsettings",
+}
+
+// Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)

+ 172 - 48
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -77,10 +77,10 @@ namespace Terminal.Gui {
 		public override void Refresh ()
 		public override void Refresh ()
 		{
 		{
 			Curses.refresh ();
 			Curses.refresh ();
-			//if (Curses.CheckWinChange ()) {
-			//	Clip = new Rect (0, 0, Cols, Rows);
-			//	TerminalResized?.Invoke ();
-			//}
+			if (Curses.CheckWinChange ()) {
+				Clip = new Rect (0, 0, Cols, Rows);
+				TerminalResized?.Invoke ();
+			}
 		}
 		}
 
 
 		public override void UpdateCursor () => Refresh ();
 		public override void UpdateCursor () => Refresh ();
@@ -751,7 +751,11 @@ namespace Terminal.Gui {
 			if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 			if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 				clipboard = new MacOSXClipboard ();
 				clipboard = new MacOSXClipboard ();
 			} else {
 			} else {
-				clipboard = new CursesClipboard ();
+				if (Is_WSL_Platform ()) {
+					clipboard = new WSLClipboard ();
+				} else {
+					clipboard = new CursesClipboard ();
+				}
 			}
 			}
 
 
 			Curses.raw ();
 			Curses.raw ();
@@ -844,6 +848,15 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		public static bool Is_WSL_Platform ()
+		{
+			var result = BashRunner.Run ("uname -a");
+			if (result.Contains ("microsoft") && result.Contains ("WSL")) {
+				return true;
+			}
+			return false;
+		}
+
 		static int MapColor (Color color)
 		static int MapColor (Color color)
 		{
 		{
 			switch (color) {
 			switch (color) {
@@ -1056,67 +1069,102 @@ namespace Terminal.Gui {
 		}
 		}
 	}
 	}
 
 
-	class CursesClipboard : IClipboard {
-		public string GetClipboardData ()
+	class CursesClipboard : ClipboardBase {
+		public override bool IsSupported => CheckSupport ();
+
+		bool CheckSupport ()
 		{
 		{
-			var tempFileName = System.IO.Path.GetTempFileName ();
 			try {
 			try {
-				// BashRunner.Run ($"xsel -o --clipboard > {tempFileName}");
-				BashRunner.Run ($"xclip -o > {tempFileName}");
-				return System.IO.File.ReadAllText (tempFileName);
-			} finally {
-				System.IO.File.Delete (tempFileName);
+				var result = BashRunner.Run ("which xclip");
+				return BashRunner.FileExists (result);
+			} catch (Exception) {
+				// Permissions issue.
+				return false;
 			}
 			}
 		}
 		}
 
 
-		public void SetClipboardData (string text)
+		protected override string GetClipboardDataImpl ()
 		{
 		{
 			var tempFileName = System.IO.Path.GetTempFileName ();
 			var tempFileName = System.IO.Path.GetTempFileName ();
-			System.IO.File.WriteAllText (tempFileName, text);
 			try {
 			try {
-				// BashRunner.Run ($"cat {tempFileName} | xsel -i --clipboard");
-				BashRunner.Run ($"cat {tempFileName} | xclip -selection clipboard");
+				// BashRunner.Run ($"xsel -o --clipboard > {tempFileName}");
+				BashRunner.Run ($"xclip -selection clipboard -o > {tempFileName}");
+				return System.IO.File.ReadAllText (tempFileName);
 			} finally {
 			} finally {
 				System.IO.File.Delete (tempFileName);
 				System.IO.File.Delete (tempFileName);
 			}
 			}
 		}
 		}
+
+		protected override void SetClipboardDataImpl (string text)
+		{
+			// var tempFileName = System.IO.Path.GetTempFileName ();
+			// System.IO.File.WriteAllText (tempFileName, text);
+			// try {
+			// 	// BashRunner.Run ($"cat {tempFileName} | xsel -i --clipboard");
+			// 	BashRunner.Run ($"cat {tempFileName} | xclip -selection clipboard");
+			// } finally {
+			// 	System.IO.File.Delete (tempFileName);
+			// }
+
+			BashRunner.Run ("xclip -selection clipboard -i", false, text);
+		}
 	}
 	}
 
 
 	static class BashRunner {
 	static class BashRunner {
-		public static string Run (string commandLine)
+		public static string Run (string commandLine, bool output = true, string inputText = "")
 		{
 		{
-			var errorBuilder = new System.Text.StringBuilder ();
-			var outputBuilder = new System.Text.StringBuilder ();
 			var arguments = $"-c \"{commandLine}\"";
 			var arguments = $"-c \"{commandLine}\"";
-			using (var process = new System.Diagnostics.Process {
-				StartInfo = new System.Diagnostics.ProcessStartInfo {
-					FileName = "bash",
-					Arguments = arguments,
-					RedirectStandardOutput = true,
-					RedirectStandardError = true,
-					UseShellExecute = false,
-					CreateNoWindow = false,
-				}
-			}) {
-				process.Start ();
-				process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine (args.Data); };
-				process.BeginOutputReadLine ();
-				process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine (args.Data); };
-				process.BeginErrorReadLine ();
-				if (!process.DoubleWaitForExit ()) {
-					var timeoutError = $@"Process timed out. Command line: bash {arguments}.
+
+			if (output) {
+				var errorBuilder = new System.Text.StringBuilder ();
+				var outputBuilder = new System.Text.StringBuilder ();
+
+				using (var process = new System.Diagnostics.Process {
+					StartInfo = new System.Diagnostics.ProcessStartInfo {
+						FileName = "bash",
+						Arguments = arguments,
+						RedirectStandardOutput = true,
+						RedirectStandardError = true,
+						UseShellExecute = false,
+						CreateNoWindow = false,
+					}
+				}) {
+					process.Start ();
+					process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine (args.Data); };
+					process.BeginOutputReadLine ();
+					process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine (args.Data); };
+					process.BeginErrorReadLine ();
+					if (!process.DoubleWaitForExit ()) {
+						var timeoutError = $@"Process timed out. Command line: bash {arguments}.
+							Output: {outputBuilder}
+							Error: {errorBuilder}";
+						throw new Exception (timeoutError);
+					}
+					if (process.ExitCode == 0) {
+						return outputBuilder.ToString ();
+					}
+
+					var error = $@"Could not execute process. Command line: bash {arguments}.
 						Output: {outputBuilder}
 						Output: {outputBuilder}
 						Error: {errorBuilder}";
 						Error: {errorBuilder}";
-					throw new Exception (timeoutError);
+					throw new Exception (error);
 				}
 				}
-				if (process.ExitCode == 0) {
-					return outputBuilder.ToString ();
+			} else {
+				using (var process = new System.Diagnostics.Process {
+					StartInfo = new System.Diagnostics.ProcessStartInfo {
+						FileName = "bash",
+						Arguments = arguments,
+						RedirectStandardInput = true,
+						UseShellExecute = false,
+						CreateNoWindow = false
+					}
+				}) {
+					process.Start ();
+					process.StandardInput.Write (inputText);
+					process.StandardInput.Close ();
+					process.WaitForExit ();
+					return inputText;
 				}
 				}
-
-				var error = $@"Could not execute process. Command line: bash {arguments}.
-					Output: {outputBuilder}
-					Error: {errorBuilder}";
-				throw new Exception (error);
 			}
 			}
 		}
 		}
 
 
@@ -1128,9 +1176,14 @@ namespace Terminal.Gui {
 			}
 			}
 			return result;
 			return result;
 		}
 		}
+
+		public static bool FileExists (string value)
+		{
+			return !string.IsNullOrEmpty (value) && !value.Contains ("not found");
+		}
 	}
 	}
 
 
-	class MacOSXClipboard : IClipboard {
+	class MacOSXClipboard : ClipboardBase {
 		IntPtr nsString = objc_getClass ("NSString");
 		IntPtr nsString = objc_getClass ("NSString");
 		IntPtr nsPasteboard = objc_getClass ("NSPasteboard");
 		IntPtr nsPasteboard = objc_getClass ("NSPasteboard");
 		IntPtr utfTextType;
 		IntPtr utfTextType;
@@ -1144,6 +1197,18 @@ namespace Terminal.Gui {
 		IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard");
 		IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard");
 		IntPtr clearContentsRegister = sel_registerName ("clearContents");
 		IntPtr clearContentsRegister = sel_registerName ("clearContents");
 
 
+		public override bool IsSupported => CheckSupport ();
+
+		bool CheckSupport ()
+		{
+			var result = BashRunner.Run ("which pbcopy");
+			if (!BashRunner.FileExists (result)) {
+				return false;
+			}
+			result = BashRunner.Run ("which pbpaste");
+			return BashRunner.FileExists (result);
+		}
+
 		public MacOSXClipboard ()
 		public MacOSXClipboard ()
 		{
 		{
 			utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text");
 			utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text");
@@ -1151,14 +1216,14 @@ namespace Terminal.Gui {
 			generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister);
 			generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister);
 		}
 		}
 
 
-		public string GetClipboardData ()
+		protected override string GetClipboardDataImpl ()
 		{
 		{
 			var ptr = objc_msgSend (generalPasteboard, stringForTypeRegister, nsStringPboardType);
 			var ptr = objc_msgSend (generalPasteboard, stringForTypeRegister, nsStringPboardType);
 			var charArray = objc_msgSend (ptr, utf8Register);
 			var charArray = objc_msgSend (ptr, utf8Register);
 			return Marshal.PtrToStringAnsi (charArray);
 			return Marshal.PtrToStringAnsi (charArray);
 		}
 		}
 
 
-		public void SetClipboardData (string text)
+		protected override void SetClipboardDataImpl (string text)
 		{
 		{
 			IntPtr str = default;
 			IntPtr str = default;
 			try {
 			try {
@@ -1190,4 +1255,63 @@ namespace Terminal.Gui {
 		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
 		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
 		static extern IntPtr sel_registerName (string selectorName);
 		static extern IntPtr sel_registerName (string selectorName);
 	}
 	}
+
+	class WSLClipboard : ClipboardBase {
+		public override bool IsSupported => CheckSupport ();
+
+		bool CheckSupport ()
+		{
+			var result = BashRunner.Run ("which powershell.exe");
+			return BashRunner.FileExists (result);
+
+			//var result = BashRunner.Run ("which powershell.exe");
+			//if (!BashRunner.FileExists (result)) {
+			//	return false;
+			//}
+			//result = BashRunner.Run ("which clip.exe");
+			//return BashRunner.FileExists (result);
+		}
+
+		protected override string GetClipboardDataImpl ()
+		{
+			using (var powershell = new System.Diagnostics.Process {
+				StartInfo = new System.Diagnostics.ProcessStartInfo {
+					RedirectStandardOutput = true,
+					FileName = "powershell.exe",
+					Arguments = "-noprofile -command \"Get-Clipboard\""
+				}
+			}) {
+				powershell.Start ();
+				var result = powershell.StandardOutput.ReadToEnd ();
+				powershell.StandardOutput.Close ();
+				powershell.WaitForExit ();
+				return result.TrimEnd ();
+			}
+		}
+
+		protected override void SetClipboardDataImpl (string text)
+		{
+			using (var powershell = new System.Diagnostics.Process {
+				StartInfo = new System.Diagnostics.ProcessStartInfo {
+					FileName = "powershell.exe",
+					Arguments = $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""
+				}
+			}) {
+				powershell.Start ();
+				powershell.WaitForExit ();
+			}
+
+			//using (var clipExe = new System.Diagnostics.Process {
+			//	StartInfo = new System.Diagnostics.ProcessStartInfo {
+			//		FileName = "clip.exe",
+			//		RedirectStandardInput = true
+			//	}
+			//}) {
+			//	clipExe.Start ();
+			//	clipExe.StandardInput.Write (text);
+			//	clipExe.StandardInput.Close ();
+			//	clipExe.WaitForExit ();
+			//}
+		}
+	}
 }
 }

+ 5 - 1
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -64,7 +64,11 @@ namespace Terminal.Gui {
 			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 				Clipboard = new MacOSXClipboard ();
 				Clipboard = new MacOSXClipboard ();
 			} else {
 			} else {
-				Clipboard = new CursesClipboard ();
+				if (CursesDriver.Is_WSL_Platform ()) {
+					Clipboard = new WSLClipboard ();
+				} else {
+					Clipboard = new CursesClipboard ();
+				}
 			}
 			}
 		}
 		}
 
 

+ 11 - 4
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -183,7 +183,8 @@ namespace Terminal.Gui {
 						return;
 						return;
 					}
 					}
 				} else {
 				} else {
-					largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+					//largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+					largestWindowHeight = Console.BufferHeight;
 					if (Console.BufferWidth != consoleDriver.Cols || largestWindowHeight != consoleDriver.Rows
 					if (Console.BufferWidth != consoleDriver.Cols || largestWindowHeight != consoleDriver.Rows
 						|| Console.WindowHeight != lastWindowHeight) {
 						|| Console.WindowHeight != lastWindowHeight) {
 						lastWindowHeight = Console.WindowHeight;
 						lastWindowHeight = Console.WindowHeight;
@@ -1094,13 +1095,18 @@ namespace Terminal.Gui {
 				IsWinPlatform = true;
 				IsWinPlatform = true;
 				NetWinConsole = new NetWinVTConsole ();
 				NetWinConsole = new NetWinVTConsole ();
 			}
 			}
-			largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+			//largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+			largestWindowHeight = Console.BufferHeight;
 			if (IsWinPlatform) {
 			if (IsWinPlatform) {
 				Clipboard = new WindowsClipboard ();
 				Clipboard = new WindowsClipboard ();
 			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 				Clipboard = new MacOSXClipboard ();
 				Clipboard = new MacOSXClipboard ();
 			} else {
 			} else {
-				Clipboard = new CursesClipboard ();
+				if (CursesDriver.Is_WSL_Platform ()) {
+					Clipboard = new WSLClipboard ();
+				}else{
+					Clipboard = new CursesClipboard ();
+				}
 			}
 			}
 		}
 		}
 
 
@@ -1604,7 +1610,8 @@ namespace Terminal.Gui {
 					Console.WindowHeight);
 					Console.WindowHeight);
 				top = 0;
 				top = 0;
 			} else {
 			} else {
-				largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+				//largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+				largestWindowHeight = Console.BufferHeight;
 				size = new Size (Console.BufferWidth, largestWindowHeight);
 				size = new Size (Console.BufferWidth, largestWindowHeight);
 			}
 			}
 			cols = size.Width;
 			cols = size.Width;

+ 7 - 5
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1640,11 +1640,13 @@ namespace Terminal.Gui {
 		}
 		}
 	}
 	}
 
 
-	class WindowsClipboard : IClipboard {
-		public string GetClipboardData ()
+	class WindowsClipboard : ClipboardBase {
+		public override bool IsSupported => IsClipboardFormatAvailable (cfUnicodeText) ? true : false;
+
+		protected override string GetClipboardDataImpl ()
 		{
 		{
-			if (!IsClipboardFormatAvailable (cfUnicodeText))
-				return null;
+			//if (!IsClipboardFormatAvailable (cfUnicodeText))
+			//	return null;
 
 
 			try {
 			try {
 				if (!OpenClipboard (IntPtr.Zero))
 				if (!OpenClipboard (IntPtr.Zero))
@@ -1678,7 +1680,7 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		public void SetClipboardData (string text)
+		protected override void SetClipboardDataImpl (string text)
 		{
 		{
 			OpenClipboard ();
 			OpenClipboard ();
 
 

+ 0 - 32
Terminal.Gui/Core/Clipboard.cs

@@ -1,32 +0,0 @@
-using NStack;
-using System;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Provides cut, copy, and paste support for the clipboard with OS interaction.
-	/// </summary>
-	public static class Clipboard {
-		static ustring contents;
-
-		/// <summary>
-		/// Get or sets the operation system clipboard, otherwise the contents field.
-		/// </summary>
-		public static ustring Contents {
-			get {
-				try {
-					return Application.Driver.Clipboard.GetClipboardData ();
-				} catch (Exception) {
-					return contents;
-				}
-			}
-			set {
-				try {
-					Application.Driver.Clipboard.SetClipboardData (value.ToString ());
-					contents = value;
-				} catch (Exception) {
-					contents = value;
-				}
-			}
-		}
-	}
-}

+ 67 - 0
Terminal.Gui/Core/Clipboard/Clipboard.cs

@@ -0,0 +1,67 @@
+using NStack;
+using System;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Provides cut, copy, and paste support for the clipboard with OS interaction.
+	/// </summary>
+	public static class Clipboard {
+		static ustring contents;
+
+		/// <summary>
+		/// Get or sets the operation system clipboard, otherwise the contents field.
+		/// </summary>
+		public static ustring Contents {
+			get {
+				try {
+					return Application.Driver.Clipboard.GetClipboardData ();
+				} catch (Exception) {
+					return contents;
+				}
+			}
+			set {
+				try {
+					Application.Driver.Clipboard.SetClipboardData (value.ToString ());
+					contents = value;
+				} catch (Exception) {
+					contents = value;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
+		/// </summary>
+		public static bool IsSupported { get; } = Application.Driver.Clipboard.IsSupported;
+
+		/// <summary>
+		/// Gets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="result">Clipboard contents read</param>
+		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		public static bool TryGetClipboardData (out string result)
+		{
+			if (Application.Driver.Clipboard.TryGetClipboardData (out result)) {
+				if (contents != result) {
+					contents = result;
+				}
+				return true;
+			}
+			return false;
+		}
+
+		/// <summary>
+		/// Sets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <returns>True if the clipboard content was set successfully.</returns>
+		public static bool TrySetClipboardData (string text)
+		{
+			if (Application.Driver.Clipboard.TrySetClipboardData (text)) {
+				contents = text;
+				return true;
+			}
+			return false;
+		}
+	}
+}

+ 100 - 0
Terminal.Gui/Core/Clipboard/ClipboardBase.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Shared abstract class to enforce rules from the implementation of the <see cref="IClipboard"/> interface.
+	/// </summary>
+	public abstract class ClipboardBase : IClipboard {
+		/// <summary>
+		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard
+		/// </summary>
+		public abstract bool IsSupported { get; }
+
+		/// <summary>
+		/// Get the operation system clipboard.
+		/// </summary>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents</exception>
+		public string GetClipboardData ()
+		{
+			try {
+				return GetClipboardDataImpl ();
+			} catch (Exception ex) {
+				throw new NotSupportedException ("Failed to read clipboard.", ex);
+			}
+		}
+
+		/// <summary>
+		/// Get the operation system clipboard.
+		/// </summary>
+		protected abstract string GetClipboardDataImpl ();
+
+		/// <summary>
+		/// Sets the operation system clipboard.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents</exception>
+		public void SetClipboardData (string text)
+		{
+			try {
+				SetClipboardDataImpl (text);
+			} catch (Exception ex) {
+				throw new NotSupportedException ("Failed to write to clipboard.", ex);
+			}
+		}
+
+		/// <summary>
+		/// Sets the operation system clipboard.
+		/// </summary>
+		/// <param name="text"></param>
+		protected abstract void SetClipboardDataImpl (string text);
+
+		/// <summary>
+		/// Gets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="result">Clipboard contents read</param>
+		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		public bool TryGetClipboardData (out string result)
+		{
+			// Don't even try to read because environment is not set up.
+			if (!IsSupported) {
+				result = null;
+				return false;
+			}
+
+			try {
+				result = GetClipboardDataImpl ();
+				while (result == null) {
+					result = GetClipboardDataImpl ();
+				}
+				return true;
+			} catch (Exception) {
+				result = null;
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// Sets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <returns>True if the clipboard content was set successfully</returns>
+		public bool TrySetClipboardData (string text)
+		{
+			// Don't even try to set because environment is not set up
+			if (!IsSupported) {
+				return false;
+			}
+
+			try {
+				SetClipboardDataImpl (text);
+				return true;
+			} catch (Exception) {
+				return false;
+			}
+		}
+	}
+}

+ 44 - 0
Terminal.Gui/Core/Clipboard/IClipboard.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Definition to interact with the OS clipboard.
+	/// </summary>
+	public interface IClipboard {
+		/// <summary>
+		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
+		/// </summary>
+		bool IsSupported { get; }
+
+		/// <summary>
+		/// Get the operation system clipboard.
+		/// </summary>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents.</exception>
+		string GetClipboardData ();
+
+		/// <summary>
+		/// Gets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="result">Clipboard contents read</param>
+		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		bool TryGetClipboardData (out string result);
+
+		/// <summary>
+		/// Sets the operation system clipboard.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents.</exception>
+		void SetClipboardData (string text);
+
+		/// <summary>
+		/// Sets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <returns>True if the clipboard content was set successfully.</returns>
+		bool TrySetClipboardData (string text);
+	}
+}

+ 0 - 16
Terminal.Gui/Core/ConsoleDriver.cs

@@ -1178,20 +1178,4 @@ namespace Terminal.Gui {
 		/// <returns>The current attribute.</returns>
 		/// <returns>The current attribute.</returns>
 		public abstract Attribute GetAttribute ();
 		public abstract Attribute GetAttribute ();
 	}
 	}
-
-	/// <summary>
-	/// Definition to interact with the OS clipboard.
-	/// </summary>
-	public interface IClipboard {
-		/// <summary>
-		/// Sets the operation system clipboard.
-		/// </summary>
-		/// <param name="text"></param>
-		void SetClipboardData (string text);
-		/// <summary>
-		/// Get the operation system clipboard.
-		/// </summary>
-		/// <returns></returns>
-		string GetClipboardData ();
-	}
 }
 }

+ 27 - 20
Terminal.Gui/Core/View.cs

@@ -1988,8 +1988,10 @@ namespace Terminal.Gui {
 			get => textFormatter.Text;
 			get => textFormatter.Text;
 			set {
 			set {
 				textFormatter.Text = value;
 				textFormatter.Text = value;
-				ResizeView (autoSize);
-				if (textFormatter.Size != Bounds.Size) {
+				var canResize = ResizeView (autoSize);
+				if (canResize && textFormatter.Size != Bounds.Size) {
+					Bounds = new Rect (new Point (Bounds.X, Bounds.Y), textFormatter.Size);
+				} else if (!canResize && textFormatter.Size != Bounds.Size) {
 					textFormatter.Size = Bounds.Size;
 					textFormatter.Size = Bounds.Size;
 				}
 				}
 				SetNeedsLayout ();
 				SetNeedsLayout ();
@@ -2085,7 +2087,9 @@ namespace Terminal.Gui {
 
 
 			var aSize = autoSize;
 			var aSize = autoSize;
 			Rect nBounds = TextFormatter.CalcRect (Bounds.X, Bounds.Y, Text, textFormatter.Direction);
 			Rect nBounds = TextFormatter.CalcRect (Bounds.X, Bounds.Y, Text, textFormatter.Direction);
-
+			if (textFormatter.Size != nBounds.Size) {
+				textFormatter.Size = nBounds.Size;
+			}
 			if ((textFormatter.Size != Bounds.Size || textFormatter.Size != nBounds.Size)
 			if ((textFormatter.Size != Bounds.Size || textFormatter.Size != nBounds.Size)
 				&& (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0
 				&& (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0
 				|| autoSize && Bounds.Width != nBounds.Width))
 				|| autoSize && Bounds.Width != nBounds.Width))
@@ -2098,17 +2102,20 @@ namespace Terminal.Gui {
 
 
 		bool SetWidthHeight (Rect nBounds)
 		bool SetWidthHeight (Rect nBounds)
 		{
 		{
-			bool aSize;
+			bool aSize = false;
 			var canSizeW = SetWidth (nBounds.Width, out int rW);
 			var canSizeW = SetWidth (nBounds.Width, out int rW);
 			var canSizeH = SetHeight (nBounds.Height, out int rH);
 			var canSizeH = SetHeight (nBounds.Height, out int rH);
-			if (canSizeW && canSizeH) {
+			if (canSizeW) {
 				aSize = true;
 				aSize = true;
-				Bounds = nBounds;
 				width = rW;
 				width = rW;
+			}
+			if (canSizeH) {
+				aSize = true;
 				height = rH;
 				height = rH;
+			}
+			if (aSize) {
+				Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
 				textFormatter.Size = Bounds.Size;
 				textFormatter.Size = Bounds.Size;
-			} else {
-				aSize = false;
 			}
 			}
 
 
 			return aSize;
 			return aSize;
@@ -2266,15 +2273,13 @@ namespace Terminal.Gui {
 			return true;
 			return true;
 		}
 		}
 
 
-		bool CanSetWidth (int desiredWidth, out int resultWidth, out int currentWidth)
+		bool CanSetWidth (int desiredWidth, out int resultWidth)
 		{
 		{
 			int w = desiredWidth;
 			int w = desiredWidth;
-			currentWidth = Width != null ? Width.Anchor (0) : 0;
 			bool canSetWidth;
 			bool canSetWidth;
 			if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) {
 			if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) {
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored.
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored.
 				w = Width.Anchor (w);
 				w = Width.Anchor (w);
-				currentWidth = Width.Anchor (w);
 				canSetWidth = false;
 				canSetWidth = false;
 			} else if (Width is Dim.DimFactor factor) {
 			} else if (Width is Dim.DimFactor factor) {
 				// Tries to get the SuperView width otherwise the view width.
 				// Tries to get the SuperView width otherwise the view width.
@@ -2283,7 +2288,6 @@ namespace Terminal.Gui {
 					sw -= Frame.X;
 					sw -= Frame.X;
 				}
 				}
 				w = Width.Anchor (sw);
 				w = Width.Anchor (sw);
-				currentWidth = Width.Anchor (sw);
 				canSetWidth = false;
 				canSetWidth = false;
 			} else {
 			} else {
 				canSetWidth = true;
 				canSetWidth = true;
@@ -2293,15 +2297,13 @@ namespace Terminal.Gui {
 			return canSetWidth;
 			return canSetWidth;
 		}
 		}
 
 
-		bool CanSetHeight (int desiredHeight, out int resultHeight, out int currentHeight)
+		bool CanSetHeight (int desiredHeight, out int resultHeight)
 		{
 		{
 			int h = desiredHeight;
 			int h = desiredHeight;
-			currentHeight = Height != null ? Height.Anchor (0) : 0;
 			bool canSetHeight;
 			bool canSetHeight;
 			if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) {
 			if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) {
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
 				h = Height.Anchor (h);
 				h = Height.Anchor (h);
-				currentHeight = Height.Anchor (h);
 				canSetHeight = false;
 				canSetHeight = false;
 			} else if (Height is Dim.DimFactor factor) {
 			} else if (Height is Dim.DimFactor factor) {
 				// Tries to get the SuperView height otherwise the view height.
 				// Tries to get the SuperView height otherwise the view height.
@@ -2310,7 +2312,6 @@ namespace Terminal.Gui {
 					sh -= Frame.Y;
 					sh -= Frame.Y;
 				}
 				}
 				h = Height.Anchor (sh);
 				h = Height.Anchor (sh);
-				currentHeight = Height.Anchor (sh);
 				canSetHeight = false;
 				canSetHeight = false;
 			} else {
 			} else {
 				canSetHeight = true;
 				canSetHeight = true;
@@ -2328,7 +2329,7 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c> if the width can be directly assigned, <c>false</c> otherwise.</returns>
 		/// <returns><c>true</c> if the width can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool SetWidth (int desiredWidth, out int resultWidth)
 		public bool SetWidth (int desiredWidth, out int resultWidth)
 		{
 		{
-			return CanSetWidth (desiredWidth, out resultWidth, out _);
+			return CanSetWidth (desiredWidth, out resultWidth);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -2339,7 +2340,7 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c> if the height can be directly assigned, <c>false</c> otherwise.</returns>
 		/// <returns><c>true</c> if the height can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool SetHeight (int desiredHeight, out int resultHeight)
 		public bool SetHeight (int desiredHeight, out int resultHeight)
 		{
 		{
-			return CanSetHeight (desiredHeight, out resultHeight, out _);
+			return CanSetHeight (desiredHeight, out resultHeight);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -2349,7 +2350,10 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c> if the width can be directly assigned, <c>false</c> otherwise.</returns>
 		/// <returns><c>true</c> if the width can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool GetCurrentWidth (out int currentWidth)
 		public bool GetCurrentWidth (out int currentWidth)
 		{
 		{
-			return CanSetWidth (0, out _, out currentWidth);
+			SetRelativeLayout (SuperView == null ? Frame : SuperView.Frame);
+			currentWidth = Frame.Width;
+
+			return CanSetWidth (0, out _);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -2359,7 +2363,10 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c> if the height can be directly assigned, <c>false</c> otherwise.</returns>
 		/// <returns><c>true</c> if the height can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool GetCurrentHeight (out int currentHeight)
 		public bool GetCurrentHeight (out int currentHeight)
 		{
 		{
-			return CanSetHeight (0, out _, out currentHeight);
+			SetRelativeLayout (SuperView == null ? Frame : SuperView.Frame);
+			currentHeight = Frame.Height;
+
+			return CanSetHeight (0, out _);
 		}
 		}
 	}
 	}
 }
 }

+ 31 - 22
Terminal.Gui/Core/Window.cs

@@ -23,6 +23,7 @@ namespace Terminal.Gui {
 	public class Window : Toplevel {
 	public class Window : Toplevel {
 		View contentView;
 		View contentView;
 		ustring title;
 		ustring title;
+		int padding;
 
 
 		/// <summary>
 		/// <summary>
 		/// The title to be displayed for this window.
 		/// The title to be displayed for this window.
@@ -52,7 +53,7 @@ namespace Terminal.Gui {
 		/// <param name="frame">Superview-relative rectangle specifying the location and size</param>
 		/// <param name="frame">Superview-relative rectangle specifying the location and size</param>
 		/// <param name="title">Title</param>
 		/// <param name="title">Title</param>
 		/// <remarks>
 		/// <remarks>
-		/// This constructor intitalizes a Window with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Absolute"/>. Use constructors
+		/// This constructor initializes a Window with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Absolute"/>. Use constructors
 		/// that do not take <c>Rect</c> parameters to initialize a Window with <see cref="LayoutStyle.Computed"/>. 
 		/// that do not take <c>Rect</c> parameters to initialize a Window with <see cref="LayoutStyle.Computed"/>. 
 		/// </remarks>
 		/// </remarks>
 		public Window (Rect frame, ustring title = null) : this (frame, title, padding: 0)
 		public Window (Rect frame, ustring title = null) : this (frame, title, padding: 0)
@@ -64,7 +65,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <param name="title">Title.</param>
 		/// <param name="title">Title.</param>
 		/// <remarks>
 		/// <remarks>
-		///   This constructor intitalize a View with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Computed"/>. 
+		///   This constructor initializes a View with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Computed"/>. 
 		///   Use <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/> properties to dynamically control the size and location of the view.
 		///   Use <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/> properties to dynamically control the size and location of the view.
 		/// </remarks>
 		/// </remarks>
 		public Window (ustring title = null) : this (title, padding: 0)
 		public Window (ustring title = null) : this (title, padding: 0)
@@ -76,7 +77,6 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public Window () : this (title: null) { }
 		public Window () : this (title: null) { }
 
 
-		int padding;
 		/// <summary>
 		/// <summary>
 		/// Initializes a new instance of the <see cref="Window"/> using <see cref="LayoutStyle.Absolute"/> positioning with the specified frame for its location, with the specified frame padding,
 		/// Initializes a new instance of the <see cref="Window"/> using <see cref="LayoutStyle.Absolute"/> positioning with the specified frame for its location, with the specified frame padding,
 		/// and an optional title.
 		/// and an optional title.
@@ -85,40 +85,48 @@ namespace Terminal.Gui {
 		/// <param name="padding">Number of characters to use for padding of the drawn frame.</param>
 		/// <param name="padding">Number of characters to use for padding of the drawn frame.</param>
 		/// <param name="title">Title</param>
 		/// <param name="title">Title</param>
 		/// <remarks>
 		/// <remarks>
-		/// This constructor intitalizes a Window with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Absolute"/>. Use constructors
+		/// This constructor initializes a Window with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Absolute"/>. Use constructors
 		/// that do not take <c>Rect</c> parameters to initialize a Window with  <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Computed"/> 
 		/// that do not take <c>Rect</c> parameters to initialize a Window with  <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Computed"/> 
 		/// </remarks>
 		/// </remarks>
 		public Window (Rect frame, ustring title = null, int padding = 0) : base (frame)
 		public Window (Rect frame, ustring title = null, int padding = 0) : base (frame)
 		{
 		{
-			this.Title = title;
-			int wb = 2 * (1 + padding);
-			this.padding = padding;
-			var cFrame = new Rect (1 + padding, 1 + padding, frame.Width - wb, frame.Height - wb);
-			contentView = new ContentView (cFrame);
-			base.Add (contentView);
+			Initialize (title, frame, padding);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Initializes a new instance of the <see cref="Window"/> using <see cref="LayoutStyle.Absolute"/> positioning with the specified frame for its location, with the specified frame padding,
+		/// Initializes a new instance of the <see cref="Window"/> using <see cref="LayoutStyle.Computed"/> positioning,
 		/// and an optional title.
 		/// and an optional title.
 		/// </summary>
 		/// </summary>
 		/// <param name="padding">Number of characters to use for padding of the drawn frame.</param>
 		/// <param name="padding">Number of characters to use for padding of the drawn frame.</param>
 		/// <param name="title">Title.</param>
 		/// <param name="title">Title.</param>
 		/// <remarks>
 		/// <remarks>
-		///   This constructor intitalize a View with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Computed"/>. 
+		///   This constructor initializes a View with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Computed"/>. 
 		///   Use <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/> properties to dynamically control the size and location of the view.
 		///   Use <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/> properties to dynamically control the size and location of the view.
 		/// </remarks>
 		/// </remarks>
 		public Window (ustring title = null, int padding = 0) : base ()
 		public Window (ustring title = null, int padding = 0) : base ()
 		{
 		{
-			this.Title = title;
-			int wb = 1 + padding;
+			Initialize (title, Rect.Empty, padding);
+		}
+
+		void Initialize (ustring title, Rect frame, int padding = 0)
+		{
+			ColorScheme = Colors.Base;
+			Title = title;
+			int wb;
+			if (frame == Rect.Empty) {
+				wb = 1 + padding;
+				contentView = new ContentView () {
+					X = wb,
+					Y = wb,
+					Width = Dim.Fill (wb),
+					Height = Dim.Fill (wb)
+				};
+			} else {
+				wb = 2 * (1 + padding);
+				var cFrame = new Rect (1 + padding, 1 + padding, frame.Width - wb, frame.Height - wb);
+				contentView = new ContentView (cFrame);
+			}
 			this.padding = padding;
 			this.padding = padding;
-			contentView = new ContentView () {
-				X = wb,
-				Y = wb,
-				Width = Dim.Fill (wb),
-				Height = Dim.Fill (wb)
-			};
 			base.Add (contentView);
 			base.Add (contentView);
 		}
 		}
 
 
@@ -179,7 +187,7 @@ namespace Terminal.Gui {
 
 
 			var savedClip = ClipToBounds ();
 			var savedClip = ClipToBounds ();
 
 
-			// Redraw our contenetView
+			// Redraw our contentView
 			// TODO: smartly constrict contentView.Bounds to just be what intersects with the 'bounds' we were passed
 			// TODO: smartly constrict contentView.Bounds to just be what intersects with the 'bounds' we were passed
 			contentView.Redraw (contentView.Bounds);
 			contentView.Redraw (contentView.Bounds);
 			Driver.Clip = savedClip;
 			Driver.Clip = savedClip;
@@ -216,7 +224,8 @@ namespace Terminal.Gui {
 			// a pending mouse event activated.
 			// a pending mouse event activated.
 
 
 			int nx, ny;
 			int nx, ny;
-			if (!dragPosition.HasValue && mouseEvent.Flags == (MouseFlags.Button1Pressed)) {
+			if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed
+				|| mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
 				// Only start grabbing if the user clicks on the title bar.
 				// Only start grabbing if the user clicks on the title bar.
 				if (mouseEvent.Y == 0) {
 				if (mouseEvent.Y == 0) {
 					start = new Point (mouseEvent.X, mouseEvent.Y);
 					start = new Point (mouseEvent.X, mouseEvent.Y);

+ 2 - 1
Terminal.Gui/Views/FrameView.cs

@@ -9,6 +9,7 @@
 //  - Does not support IEnumerable
 //  - Does not support IEnumerable
 // Any udpates done here should probably be done in Window as well; TODO: Merge these classes
 // Any udpates done here should probably be done in Window as well; TODO: Merge these classes
 
 
+using System;
 using System.Linq;
 using System.Linq;
 using NStack;
 using NStack;
 
 
@@ -50,7 +51,7 @@ namespace Terminal.Gui {
 		/// <param name="title">Title.</param>
 		/// <param name="title">Title.</param>
 		public FrameView (Rect frame, ustring title = null) : base (frame)
 		public FrameView (Rect frame, ustring title = null) : base (frame)
 		{
 		{
-			var cFrame = new Rect (1, 1, frame.Width - 2, frame.Height - 2);
+			var cFrame = new Rect (1, 1, Math.Max (frame.Width - 2, 0), Math.Max (frame.Height - 2, 0));
 			this.title = title;
 			this.title = title;
 			contentView = new ContentView (cFrame);
 			contentView = new ContentView (cFrame);
 			Initialize ();
 			Initialize ();

+ 9 - 0
Terminal.Gui/Views/Label.cs

@@ -23,6 +23,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		/// <inheritdoc/>
 		public Label ()
 		public Label ()
 		{
 		{
+			Initialize ();
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
@@ -33,6 +34,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		/// <inheritdoc/>
 		public Label (ustring text) : base (text)
 		public Label (ustring text) : base (text)
 		{
 		{
+			Initialize ();
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
@@ -43,12 +45,19 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		/// <inheritdoc/>
 		public Label (int x, int y, ustring text) : base (x, y, text)
 		public Label (int x, int y, ustring text) : base (x, y, text)
 		{
 		{
+			Initialize ();
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
 		public Label (ustring text, TextDirection direction)
 		public Label (ustring text, TextDirection direction)
 			: base (text, direction)
 			: base (text, direction)
 		{
 		{
+			Initialize ();
+		}
+
+		void Initialize ()
+		{
+			AutoSize = true;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>

+ 5 - 3
Terminal.Gui/Views/ListView.cs

@@ -468,7 +468,7 @@ namespace Terminal.Gui {
 		public virtual bool MovePageDown ()
 		public virtual bool MovePageDown ()
 		{
 		{
 			var n = (selected + Frame.Height);
 			var n = (selected + Frame.Height);
-			if (n > source.Count)
+			if (n >= source.Count)
 				n = source.Count - 1;
 				n = source.Count - 1;
 			if (n != selected) {
 			if (n != selected) {
 				selected = n;
 				selected = n;
@@ -557,9 +557,11 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		/// <returns></returns>
 		public virtual bool MoveEnd ()
 		public virtual bool MoveEnd ()
 		{
 		{
-			if (selected != source.Count - 1) {
+			if (source.Count > 0 && selected != source.Count - 1) {
 				selected = source.Count - 1;
 				selected = source.Count - 1;
-				top = selected;
+				if (top + selected > Frame.Height - 1) {
+					top = selected;
+				}
 				OnSelectedChanged ();
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 			}
 			}

+ 7 - 3
Terminal.Gui/Views/ScrollBarView.cs

@@ -298,11 +298,15 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			var pending = CheckBothScrollBars (this);
 			var pending = CheckBothScrollBars (this);
-			CheckBothScrollBars (otherScrollBarView, pending);
+			if (otherScrollBarView != null) {
+				CheckBothScrollBars (otherScrollBarView, pending);
+			}
 
 
 			SetWidthHeight ();
 			SetWidthHeight ();
 			SetRelativeLayout (Bounds);
 			SetRelativeLayout (Bounds);
-			OtherScrollBarView.SetRelativeLayout (OtherScrollBarView.Bounds);
+			if (otherScrollBarView != null) {
+				OtherScrollBarView.SetRelativeLayout (OtherScrollBarView.Bounds);
+			}
 
 
 			if (showBothScrollIndicator) {
 			if (showBothScrollIndicator) {
 				if (contentBottomRightCorner != null) {
 				if (contentBottomRightCorner != null) {
@@ -321,7 +325,7 @@ namespace Terminal.Gui {
 			if (showScrollIndicator) {
 			if (showScrollIndicator) {
 				Redraw (Bounds);
 				Redraw (Bounds);
 			}
 			}
-			if (otherScrollBarView.showScrollIndicator) {
+			if (otherScrollBarView != null && otherScrollBarView.showScrollIndicator) {
 				otherScrollBarView.Redraw (otherScrollBarView.Bounds);
 				otherScrollBarView.Redraw (otherScrollBarView.Bounds);
 			}
 			}
 		}
 		}

+ 8 - 1
Terminal.Gui/Views/TextField.cs

@@ -143,6 +143,9 @@ namespace Terminal.Gui {
 
 
 				var newText = OnTextChanging (value.Split ("\n") [0]);
 				var newText = OnTextChanging (value.Split ("\n") [0]);
 				if (newText.Cancel) {
 				if (newText.Cancel) {
+					if (point > text.Count) {
+						point = text.Count;
+					}
 					return;
 					return;
 				}
 				}
 				text = TextModel.ToRunes (newText.NewText);
 				text = TextModel.ToRunes (newText.NewText);
@@ -347,7 +350,11 @@ namespace Terminal.Gui {
 						return true;
 						return true;
 
 
 					point--;
 					point--;
-					SetText (text.GetRange (0, oldCursorPos - 1).Concat (text.GetRange (oldCursorPos, text.Count - oldCursorPos)));
+					if (oldCursorPos < text.Count) {
+						SetText (text.GetRange (0, oldCursorPos - 1).Concat (text.GetRange (oldCursorPos, text.Count - oldCursorPos)));
+					} else {
+						SetText (text.GetRange (0, oldCursorPos - 1));
+					}
 					Adjust ();
 					Adjust ();
 				} else {
 				} else {
 					DeleteSelectedText ();
 					DeleteSelectedText ();

+ 35 - 6
Terminal.Gui/Views/TextView.cs

@@ -1333,17 +1333,46 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		void ColorNormal ()
+		/// <summary>
+		/// Sets the driver to the default color for the control where no text is being rendered.  Defaults to <see cref="ColorScheme.Normal"/>.
+		/// </summary>
+		protected virtual void ColorNormal ()
+		{
+			Driver.SetAttribute (ColorScheme.Normal);
+		}
+
+		/// <summary>
+		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
+		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// Defaults to <see cref="ColorScheme.Normal"/>.
+		/// </summary>
+		/// <param name="line"></param>
+		/// <param name="idx"></param>
+		protected virtual void ColorNormal (List<Rune> line, int idx)
 		{
 		{
 			Driver.SetAttribute (ColorScheme.Normal);
 			Driver.SetAttribute (ColorScheme.Normal);
 		}
 		}
 
 
-		void ColorSelection ()
+		/// <summary>
+		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
+		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// Defaults to <see cref="ColorScheme.Focus"/>.
+		/// </summary>
+		/// <param name="line"></param>
+		/// <param name="idx"></param>
+		protected virtual void ColorSelection (List<Rune> line, int idx)
 		{
 		{
 			Driver.SetAttribute (ColorScheme.Focus);
 			Driver.SetAttribute (ColorScheme.Focus);
 		}
 		}
 
 
-		void ColorUsed ()
+		/// <summary>
+		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
+		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// Defaults to <see cref="ColorScheme.HotFocus"/>.
+		/// </summary>
+		/// <param name="line"></param>
+		/// <param name="idx"></param>
+		protected virtual void ColorUsed (List<Rune> line, int idx)
 		{
 		{
 			Driver.SetAttribute (ColorScheme.HotFocus);
 			Driver.SetAttribute (ColorScheme.HotFocus);
 		}
 		}
@@ -1667,12 +1696,12 @@ namespace Terminal.Gui {
 					var rune = idxCol >= lineRuneCount ? ' ' : line [idxCol];
 					var rune = idxCol >= lineRuneCount ? ' ' : line [idxCol];
 					var cols = Rune.ColumnWidth (rune);
 					var cols = Rune.ColumnWidth (rune);
 					if (idxCol < line.Count && selecting && PointInSelection (idxCol, idxRow)) {
 					if (idxCol < line.Count && selecting && PointInSelection (idxCol, idxRow)) {
-						ColorSelection ();
+						ColorSelection (line, idxCol);
 					} else if (idxCol == currentColumn && idxRow == currentRow && !selecting && !Used
 					} else if (idxCol == currentColumn && idxRow == currentRow && !selecting && !Used
 						&& HasFocus && idxCol < lineRuneCount) {
 						&& HasFocus && idxCol < lineRuneCount) {
-						ColorUsed ();
+						ColorUsed (line, idxCol);
 					} else {
 					} else {
-						ColorNormal ();
+						ColorNormal (line,idxCol);
 					}
 					}
 
 
 					if (rune == '\t' && TabWidth > 0) {
 					if (rune == '\t' && TabWidth > 0) {

+ 8 - 2
UICatalog/Scenarios/LabelsAsButtons.cs

@@ -117,8 +117,14 @@ namespace UICatalog {
 				CanFocus = true,
 				CanFocus = true,
 			};
 			};
 			Win.Add (removeLabel);
 			Win.Add (removeLabel);
-			// This in intresting test case because `moveBtn` and below are laid out relative to this one!
-			removeLabel.Clicked += () => Win.Remove (removeLabel);
+			// This in interesting test case because `moveBtn` and below are laid out relative to this one!
+			removeLabel.Clicked += () => {
+				// Now this throw a InvalidOperationException on the TopologicalSort method as is expected.
+				//Win.Remove (removeLabel);
+
+				removeLabel.Visible = false;
+				Win.SetNeedsDisplay ();
+			};
 
 
 			var computedFrame = new FrameView ("Computed Layout") {
 			var computedFrame = new FrameView ("Computed Layout") {
 				X = 0,
 				X = 0,

+ 180 - 0
UICatalog/Scenarios/SyntaxHighlighting.cs

@@ -0,0 +1,180 @@
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using static UICatalog.Scenario;
+using Attribute = Terminal.Gui.Attribute;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Syntax Highlighting", Description: "Text editor with keyword highlighting")]
+	[ScenarioCategory ("Controls")]
+	class SyntaxHighlighting : Scenario {
+
+			public override void Setup ()
+			{
+				Win.Title = this.GetName ();
+				Win.Y = 1; // menu
+				Win.Height = Dim.Fill (1); // status bar
+				Top.LayoutSubviews ();
+
+				var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_Quit", "", () => Quit()),
+				})
+				});
+				Top.Add (menu);
+
+				var textView = new SqlTextView () {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (),
+					Height = Dim.Fill (1),
+				};
+
+				textView.Init();
+
+				textView.Text = "SELECT TOP 100 * \nfrom\n MyDb.dbo.Biochemistry;";
+				
+				Win.Add (textView);
+
+				var statusBar = new StatusBar (new StatusItem [] {
+				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
+
+			});
+
+
+				Top.Add (statusBar);
+			}
+
+
+			private void Quit ()
+			{
+				Application.RequestStop ();
+			}
+
+		private class SqlTextView : TextView{
+
+			private HashSet<string> keywords = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
+			private Attribute blue;
+			private Attribute white;
+			private Attribute magenta;
+
+
+			public void Init()
+			{
+				keywords.Add("select");
+				keywords.Add("distinct");
+				keywords.Add("top");
+				keywords.Add("from");
+				keywords.Add ("create");
+				keywords.Add ("primary");
+				keywords.Add ("key");
+				keywords.Add ("insert");
+				keywords.Add ("alter");
+				keywords.Add ("add");
+				keywords.Add ("update");
+				keywords.Add ("set");
+				keywords.Add ("delete");
+				keywords.Add ("truncate");
+				keywords.Add ("as");
+				keywords.Add ("order");
+				keywords.Add ("by");
+				keywords.Add ("asc");
+				keywords.Add ("desc");
+				keywords.Add ("between");
+				keywords.Add ("where");
+				keywords.Add ("and");
+				keywords.Add ("or");
+				keywords.Add ("not");
+				keywords.Add ("limit");
+				keywords.Add ("null");
+				keywords.Add ("is");
+				keywords.Add ("drop");
+				keywords.Add ("database");
+				keywords.Add ("column");
+				keywords.Add ("table");
+				keywords.Add ("having");
+				keywords.Add ("in");
+				keywords.Add ("join");
+				keywords.Add ("on");
+				keywords.Add ("union");
+				keywords.Add ("exists");
+
+				magenta = Driver.MakeAttribute (Color.Magenta, Color.Black);
+				blue = Driver.MakeAttribute (Color.Cyan, Color.Black);
+				white = Driver.MakeAttribute (Color.White, Color.Black);
+			}
+
+			protected override void ColorNormal ()
+			{
+				Driver.SetAttribute (white);
+			}
+
+			protected override void ColorNormal (List<System.Rune> line, int idx)
+			{
+				if(IsInStringLiteral(line,idx)) {
+					Driver.SetAttribute (magenta);
+				}
+				else
+				if(IsKeyword(line,idx))
+				{
+					Driver.SetAttribute (blue);
+				}
+				else{
+					Driver.SetAttribute (white);
+				}
+			}
+
+			private bool IsInStringLiteral (List<System.Rune> line, int idx)
+			{
+				string strLine = new string (line.Select (r => (char)r).ToArray ());
+				
+				foreach(Match m in Regex.Matches(strLine, "'[^']*'")) {
+					if(idx >= m.Index && idx < m.Index+m.Length) {
+						return true;
+					}
+				}
+
+				return false;
+			}
+
+			private bool IsKeyword(List<System.Rune> line, int idx)
+			{
+				var word = IdxToWord(line,idx);
+				
+				if(string.IsNullOrWhiteSpace(word)){
+					return false;
+				}
+
+				return keywords.Contains(word,StringComparer.CurrentCultureIgnoreCase);
+			}
+
+			private string IdxToWord(List<System.Rune> line, int idx)
+			{
+				var words = Regex.Split(
+					new string(line.Select(r=>(char)r).ToArray()),
+					"\\b");
+
+
+				int count = 0;
+				string current = null;
+
+				foreach(var word in words)
+				{
+					current = word;
+					count+= word.Length;
+					if(count > idx){
+						break;
+					}
+				}
+
+				return current?.Trim();
+			}
+		}
+	}
+}

+ 14 - 6
UICatalog/UICatalog.cs

@@ -635,19 +635,27 @@ namespace UICatalog {
 		private static void OpenUrl (string url)
 		private static void OpenUrl (string url)
 		{
 		{
 			try {
 			try {
-				Process.Start (url);
-			} catch {
-				// hack because of this: https://github.com/dotnet/corefx/issues/10361
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
 					url = url.Replace ("&", "^&");
 					url = url.Replace ("&", "^&");
 					Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
 					Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
-					Process.Start ("xdg-open", url);
+					using (var process = new Process {
+						StartInfo = new ProcessStartInfo {
+							FileName = "xdg-open",
+							Arguments = url,
+							RedirectStandardError = true,
+							RedirectStandardOutput = true,
+							CreateNoWindow = true,
+							UseShellExecute = false
+						}
+					}) {
+						process.Start ();
+					}
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 					Process.Start ("open", url);
 					Process.Start ("open", url);
-				} else {
-					throw;
 				}
 				}
+			} catch {
+				throw;
 			}
 			}
 		}
 		}
 	}
 	}

+ 112 - 0
UnitTests/AllViewsTests.cs

@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+using System.IO;
+
+namespace Terminal.Gui.Views {
+	public class AllViewsTests {
+		[Fact]
+		public void AllViews_Tests_All_Constructors ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			foreach (var type in GetAllViewClassesCollection ()) {
+				Assert.True (Constructors_FullTest (type));
+			}
+
+			Application.Shutdown ();
+		}
+
+		public bool Constructors_FullTest (Type type)
+		{
+			foreach (var ctor in type.GetConstructors ()) {
+				if (type.IsGenericType && type.IsTypeDefinition) {
+					List<Type> gTypes = new List<Type> ();
+
+					foreach (var args in type.GetGenericArguments ()) {
+						gTypes.Add (typeof (object));
+					}
+					type = type.MakeGenericType (gTypes.ToArray ());
+
+					Assert.IsType (type, (View)Activator.CreateInstance (type));
+
+				} else {
+					ParameterInfo [] paramsInfo = ctor.GetParameters ();
+					Type paramType;
+					List<object> pTypes = new List<object> ();
+
+					if (type.IsGenericType) {
+						foreach (var args in type.GetGenericArguments ()) {
+							paramType = args.GetType ();
+							if (args.Name == "T") {
+								pTypes.Add (typeof (object));
+							} else {
+								AddArguments (paramType, pTypes);
+							}
+						}
+					}
+
+					foreach (var p in paramsInfo) {
+						paramType = p.ParameterType;
+						if (p.HasDefaultValue) {
+							pTypes.Add (p.DefaultValue);
+						} else {
+							AddArguments (paramType, pTypes);
+						}
+
+					}
+
+					if (type.IsGenericType && !type.IsTypeDefinition) {
+						Assert.IsType (type, (View)Activator.CreateInstance (type));
+					} else {
+						Assert.IsType (type, ctor.Invoke (pTypes.ToArray ()));
+					}
+				}
+			}
+
+			return true;
+		}
+
+		private static void AddArguments (Type paramType, List<object> pTypes)
+		{
+			if (paramType == typeof (Rect)) {
+				pTypes.Add (Rect.Empty);
+			} else if (paramType == typeof (NStack.ustring)) {
+				pTypes.Add (NStack.ustring.Empty);
+			} else if (paramType == typeof (int)) {
+				pTypes.Add (0);
+			} else if (paramType == typeof (bool)) {
+				pTypes.Add (true);
+			} else if (paramType.Name == "IList") {
+				pTypes.Add (new List<object> ());
+			} else if (paramType.Name == "View") {
+				var top = new Toplevel ();
+				var view = new View ();
+				top.Add (view);
+				pTypes.Add (view);
+			} else if (paramType.Name == "View[]") {
+				pTypes.Add (new View [] { });
+			} else if (paramType.Name == "Stream") {
+				pTypes.Add (new MemoryStream ());
+			} else if (paramType.Name == "String") {
+				pTypes.Add (string.Empty);
+			} else if (paramType.Name == "TreeView`1[T]") {
+				pTypes.Add (string.Empty);
+			} else {
+				pTypes.Add (null);
+			}
+		}
+
+		List<Type> GetAllViewClassesCollection ()
+		{
+			List<Type> types = new List<Type> ();
+			foreach (Type type in typeof (View).Assembly.GetTypes ()
+			 .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
+				types.Add (type);
+			}
+			return types;
+		}
+	}
+}

+ 315 - 1
UnitTests/ClipboardTests.cs

@@ -1,4 +1,6 @@
-using Xunit;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Xunit;
 
 
 namespace Terminal.Gui.Core {
 namespace Terminal.Gui.Core {
 	public class ClipboardTests {
 	public class ClipboardTests {
@@ -9,9 +11,321 @@ namespace Terminal.Gui.Core {
 
 
 			var clipText = "This is a clipboard unit test.";
 			var clipText = "This is a clipboard unit test.";
 			Clipboard.Contents = clipText;
 			Clipboard.Contents = clipText;
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+
 			Assert.Equal (clipText, Clipboard.Contents);
 			Assert.Equal (clipText, Clipboard.Contents);
 
 
 			Application.Shutdown ();
 			Application.Shutdown ();
 		}
 		}
+
+		[Fact]
+		public void IsSupported_Get ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			if (Clipboard.IsSupported) {
+				Assert.True (Clipboard.IsSupported);
+			} else {
+				Assert.False (Clipboard.IsSupported);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void TryGetClipboardData_Gets_From_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "Trying to get from the OS clipboard.";
+			Clipboard.Contents = clipText;
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+
+			if (Clipboard.IsSupported) {
+				Assert.True (Clipboard.TryGetClipboardData (out string result));
+				Assert.Equal (clipText, result);
+			} else {
+				Assert.False (Clipboard.TryGetClipboardData (out string result));
+				Assert.NotEqual (clipText, result);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void TrySetClipboardData_Sets_The_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "Trying to set the OS clipboard.";
+			if (Clipboard.IsSupported) {
+				Assert.True (Clipboard.TrySetClipboardData (clipText));
+			} else {
+				Assert.False (Clipboard.TrySetClipboardData (clipText));
+			}
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+
+			if (Clipboard.IsSupported) {
+				Assert.Equal (clipText, Clipboard.Contents);
+			} else {
+				Assert.NotEqual (clipText, Clipboard.Contents);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void Contents_Gets_From_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "This is a clipboard unit test to get clipboard from OS.";
+			var exit = false;
+
+			Application.Iteration += () => {
+				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+					// using (Process clipExe = new Process {
+					// 	StartInfo = new ProcessStartInfo {
+					// 		RedirectStandardInput = true,
+					// 		FileName = "clip"
+					// 	}
+					// }) {
+					// 	clipExe.Start ();
+					// 	clipExe.StandardInput.Write (clipText);
+					// 	clipExe.StandardInput.Close ();
+					// 	var result = clipExe.WaitForExit (500);
+					// 	if (result) {
+					// 		clipExe.WaitForExit ();
+					// 	}
+					// }
+
+					using (Process pwsh = new Process {
+						StartInfo = new ProcessStartInfo {
+							FileName = "powershell",
+							Arguments = $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\""
+						}
+					}) {
+						pwsh.Start ();
+						pwsh.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+					using (Process copy = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardInput = true,
+							FileName = "pbcopy"
+						}
+					}) {
+						copy.Start ();
+						copy.StandardInput.Write (clipText);
+						copy.StandardInput.Close ();
+						copy.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
+					if (Is_WSL_Platform ()) {
+						try {
+							using (Process bash = new Process {
+								StartInfo = new ProcessStartInfo {
+									FileName = "powershell.exe",
+									Arguments = $"-noprofile -command \"Set-Clipboard -Value \\\"{clipText}\\\"\""
+								}
+							}) {
+								bash.Start ();
+								bash.WaitForExit ();
+							}
+
+							//using (Process clipExe = new Process {
+							//	StartInfo = new ProcessStartInfo {
+							//		RedirectStandardInput = true,
+							//		FileName = "clip.exe"
+							//	}
+							//}) {
+							//	clipExe.Start ();
+							//	clipExe.StandardInput.Write (clipText);
+							//	clipExe.StandardInput.Close ();
+							//	clipExe.WaitForExit ();
+							//	//var result = clipExe.WaitForExit (500);
+							//	//if (result) {
+							//	//	clipExe.WaitForExit ();
+							//	//}
+							//}
+						} catch {
+							exit = true;
+						}
+						Application.RequestStop ();
+						return;
+					}
+					if (exit = xclipExists () == false) {
+						// xclip doesn't exist then exit.
+						Application.RequestStop ();
+						return;
+					}
+
+					using (Process bash = new Process {
+						StartInfo = new ProcessStartInfo {
+							FileName = "bash",
+							Arguments = $"-c \"xclip -sel clip -i\"",
+							RedirectStandardInput = true,
+						}
+					}) {
+						bash.Start ();
+						bash.StandardInput.Write (clipText);
+						bash.StandardInput.Close ();
+						bash.WaitForExit ();
+					}
+				}
+
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			if (!exit) {
+				Assert.Equal (clipText, Clipboard.Contents);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void Contents_Sets_The_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "This is a clipboard unit test to set the OS clipboard.";
+			var clipReadText = "";
+			var exit = false;
+
+			Application.Iteration += () => {
+				Clipboard.Contents = clipText;
+
+				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+					using (Process pwsh = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardOutput = true,
+							FileName = "powershell.exe",
+							Arguments = "-noprofile -command \"Get-Clipboard\""
+						}
+					}) {
+						pwsh.Start ();
+						clipReadText = pwsh.StandardOutput.ReadToEnd ().TrimEnd ();
+						pwsh.StandardOutput.Close ();
+						pwsh.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+					using (Process paste = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardOutput = true,
+							FileName = "pbpaste"
+						}
+					}) {
+						paste.Start ();
+						clipReadText = paste.StandardOutput.ReadToEnd ();
+						paste.StandardOutput.Close ();
+						paste.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
+					if (Is_WSL_Platform ()) {
+						try {
+							using (Process bash = new Process {
+								StartInfo = new ProcessStartInfo {
+									RedirectStandardOutput = true,
+									FileName = "powershell.exe",
+									Arguments = "-noprofile -command \"Get-Clipboard\""
+								}
+							}) {
+								bash.Start ();
+								clipReadText = bash.StandardOutput.ReadToEnd ();
+								bash.StandardOutput.Close ();
+								bash.WaitForExit ();
+							}
+						} catch {
+							exit = true;
+						}
+						Application.RequestStop ();
+					}
+					if (exit = xclipExists () == false) {
+						// xclip doesn't exist then exit.
+						Application.RequestStop ();
+					}
+
+					using (Process bash = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardOutput = true,
+							FileName = "bash",
+							Arguments = $"-c \"xclip -sel clip -o\""
+						}
+					}) {
+						bash.Start ();
+						clipReadText = bash.StandardOutput.ReadToEnd ();
+						bash.StandardOutput.Close ();
+						bash.WaitForExit ();
+					}
+				}
+
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			if (!exit) {
+				Assert.Equal (clipText, clipReadText);
+			}
+
+			Application.Shutdown ();
+		}
+
+		bool Is_WSL_Platform ()
+		{
+			using (Process bash = new Process {
+				StartInfo = new ProcessStartInfo {
+					FileName = "bash",
+					Arguments = $"-c \"uname -a\"",
+					RedirectStandardOutput = true,
+				}
+			}) {
+				bash.Start ();
+				var result = bash.StandardOutput.ReadToEnd ();
+				var isWSL = false;
+				if (result.Contains ("microsoft") && result.Contains ("WSL")) {
+					isWSL = true;
+				}
+				bash.StandardOutput.Close ();
+				bash.WaitForExit ();
+				return isWSL;
+			}
+		}
+
+		bool xclipExists ()
+		{
+			try {
+				using (Process bash = new Process {
+					StartInfo = new ProcessStartInfo {
+						FileName = "bash",
+						Arguments = $"-c \"which xclip\"",
+						RedirectStandardOutput = true,
+					}
+				}) {
+					bash.Start ();
+					bool exist = bash.StandardOutput.ReadToEnd ().TrimEnd () != "";
+					bash.StandardOutput.Close ();
+					bash.WaitForExit ();
+					if (exist) {
+						return true;
+					}
+				}
+				return false;
+			} catch (System.Exception) {
+				return false;
+			}
+		}
 	}
 	}
 }
 }

+ 50 - 0
UnitTests/GraphViewTests.cs

@@ -1382,6 +1382,56 @@ namespace Terminal.Gui.Views {
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
 		}
 		}
+
+		[Theory]
+		[InlineData (true)]
+		[InlineData (false)]
+		public void LabelChangeText_RendersCorrectly (bool useFill)
+		{
+			var driver = new FakeDriver ();
+			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			driver.Init (() => { });
+
+			// create a wide window
+			var mount = new View () {
+				Width = 100,
+				Height = 100
+			};
+
+			try {
+				// Create a label with a short text 
+				var lbl1 = new Label ("ff");
+
+				// Specify that the label should be very wide
+				if (useFill) {
+					lbl1.Width = Dim.Fill ();
+				} else {
+					lbl1.Width = 100;
+				}
+
+				//put label into view
+				mount.Add (lbl1);
+
+				// render view
+				lbl1.ColorScheme = new ColorScheme ();
+				Assert.Equal (1, lbl1.Height);
+				mount.Redraw (mount.Bounds);
+
+				// should have the initial text
+				GraphViewTests.AssertDriverContentsAre ("ff", null);
+
+				// change the text and redraw
+				lbl1.Text = "ff1234";
+				mount.Redraw (mount.Bounds);
+
+				// should have the new text rendered
+				GraphViewTests.AssertDriverContentsAre ("ff1234", null);
+
+
+			} finally {
+				Application.Shutdown ();
+			}
+		}
 	}
 	}
 
 
 		public class AxisIncrementToRenderTests {
 		public class AxisIncrementToRenderTests {

+ 73 - 2
UnitTests/ScrollBarViewTests.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Reflection;
 using System.Reflection;
 using Xunit;
 using Xunit;
@@ -6,7 +7,7 @@ using Xunit;
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 	public class ScrollBarViewTests {
 	public class ScrollBarViewTests {
 
 
-		// This class enables test functions annoated with the [InitShutdown] attribute
+		// This class enables test functions annotated with the [InitShutdown] attribute
 		// to have a function called before the test function is called and after.
 		// to have a function called before the test function is called and after.
 		// 
 		// 
 		// This is necessary because a) Application is a singleton and Init/Shutdown must be called
 		// This is necessary because a) Application is a singleton and Init/Shutdown must be called
@@ -55,7 +56,7 @@ namespace Terminal.Gui.Views {
 
 
 		private static HostView _hostView;
 		private static HostView _hostView;
 		private ScrollBarView _scrollBar;
 		private ScrollBarView _scrollBar;
-		private  bool _added;
+		private bool _added;
 
 
 		private void AddHandlers ()
 		private void AddHandlers ()
 		{
 		{
@@ -444,5 +445,75 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ());
 			Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ());
 			Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height);
 			Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height);
 		}
 		}
+
+		[Fact]
+		public void Constructor_ShowBothScrollIndicator_False_Refresh_Does_Not_Throws_An_Object_Null_Exception ()
+		{
+			var exception = Record.Exception (() => {
+				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+				var top = Application.Top;
+
+				var win = new Window () {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (),
+					Height = Dim.Fill ()
+				};
+
+				List<string> source = new List<string> ();
+
+				for (int i = 0; i < 50; i++) {
+					source.Add ($"item {i}");
+				}
+
+				var listView = new ListView (source) {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (),
+					Height = Dim.Fill ()
+				};
+				win.Add (listView);
+
+				var newScrollBarView = new ScrollBarView (listView, true, false) {
+					KeepContentAlwaysInViewport = true
+				};
+				win.Add (newScrollBarView);
+
+				newScrollBarView.ChangedPosition += () => {
+					listView.TopItem = newScrollBarView.Position;
+					if (listView.TopItem != newScrollBarView.Position) {
+						newScrollBarView.Position = listView.TopItem;
+					}
+					Assert.Equal (newScrollBarView.Position, listView.TopItem);
+					listView.SetNeedsDisplay ();
+				};
+
+				listView.DrawContent += (e) => {
+					newScrollBarView.Size = listView.Source.Count - 1;
+					Assert.Equal (newScrollBarView.Size, listView.Source.Count);
+					newScrollBarView.Position = listView.TopItem;
+					Assert.Equal (newScrollBarView.Position, listView.TopItem);
+					newScrollBarView.Refresh ();
+				};
+
+				top.Ready += () => {
+					newScrollBarView.Position = 45;
+					Assert.Equal (newScrollBarView.Position, newScrollBarView.Size - listView.TopItem + (listView.TopItem - listView.Bounds.Height));
+					Assert.Equal (newScrollBarView.Position, listView.TopItem);
+					Assert.Equal (27, newScrollBarView.Position);
+					Assert.Equal (27, listView.TopItem);
+					Application.RequestStop ();
+				};
+
+				top.Add (win);
+
+				Application.Run ();
+
+				Application.Shutdown ();
+
+			});
+			Assert.Null (exception);
+		}
 	}
 	}
 }
 }

+ 67 - 0
UnitTests/TextFieldTests.cs

@@ -706,5 +706,72 @@ namespace Terminal.Gui.Views {
 			_textField.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
 			_textField.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
 			Assert.Equal ("TAB to jumusedtween text fields.", _textField.Text);
 			Assert.Equal ("TAB to jumusedtween text fields.", _textField.Text);
 		}
 		}
+
+		[Fact]
+		public void ProcessKey_Backspace_From_End ()
+		{
+			var tf = new TextField ("ABC");
+			tf.EnsureFocus ();
+			Assert.Equal ("ABC", tf.Text);
+
+			// now delete the C
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+			Assert.Equal ("AB", tf.Text);
+
+			// then delete the B
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+			Assert.Equal ("A", tf.Text);
+
+			// then delete the A
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+			Assert.Equal ("", tf.Text);
+		}
+
+		[Fact]
+		public void ProcessKey_Backspace_From_Middle ()
+		{
+			var tf = new TextField ("ABC");
+			tf.EnsureFocus ();
+			tf.CursorPosition = 2;
+			Assert.Equal ("ABC", tf.Text);
+
+			// now delete the B
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+			Assert.Equal ("AC", tf.Text);
+
+			// then delete the A
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+			Assert.Equal ("C", tf.Text);
+
+			// then delete nothing
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+			Assert.Equal ("C", tf.Text);
+
+			// now delete the C
+			tf.CursorPosition = 1;
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+			Assert.Equal ("", tf.Text);
+		}
+
+		[Fact]
+		public void Cancel_TextChanging_ThenBackspace ()
+		{
+			var tf = new TextField ();
+			tf.EnsureFocus ();
+			tf.ProcessKey (new KeyEvent (Key.A, new KeyModifiers ()));
+			Assert.Equal ("A", tf.Text);
+
+			// cancel the next keystroke
+			tf.TextChanging += (e) => e.Cancel = e.NewText == "AB";
+			tf.ProcessKey (new KeyEvent (Key.B, new KeyModifiers ()));
+
+			// B was canceled so should just be A
+			Assert.Equal ("A", tf.Text);
+
+			// now delete the A
+			tf.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()));
+
+			Assert.Equal ("", tf.Text);
+		}
 	}
 	}
 }
 }

+ 2 - 2
UnitTests/UnitTests.csproj

@@ -15,8 +15,8 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
-    <PackageReference Include="ReportGenerator" Version="4.8.8" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
+    <PackageReference Include="ReportGenerator" Version="4.8.11" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">

+ 162 - 0
UnitTests/ViewTests.cs

@@ -1170,5 +1170,167 @@ namespace Terminal.Gui.Views {
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
 		}
 		}
+
+		[Fact]
+		public void SetWidth_CanSetWidth ()
+		{
+			var top = new View () {
+				X = 0,
+				Y = 0,
+				Width = 80,
+			};
+
+			var v = new View () {
+				Width = Dim.Fill ()
+			};
+			top.Add (v);
+
+			Assert.False (v.SetWidth (70, out int rWidth));
+			Assert.Equal (70, rWidth);
+
+			v.Width = Dim.Fill (1);
+			Assert.False (v.SetWidth (70, out rWidth));
+			Assert.Equal (69, rWidth);
+
+			v.Width = null;
+			Assert.True (v.SetWidth (70, out rWidth));
+			Assert.Equal (70, rWidth);
+
+			v.IsInitialized = true;
+			v.Width = Dim.Fill (1);
+			Assert.Throws<ArgumentException> (() => v.Width = 75);
+			v.LayoutStyle = LayoutStyle.Absolute;
+			v.Width = 75;
+			Assert.True (v.SetWidth (60, out rWidth));
+			Assert.Equal (60, rWidth);
+		}
+
+		[Fact]
+		public void SetHeight_CanSetHeight ()
+		{
+			var top = new View () {
+				X = 0,
+				Y = 0,
+				Height = 20
+			};
+
+			var v = new View () {
+				Height = Dim.Fill ()
+			};
+			top.Add (v);
+
+			Assert.False (v.SetHeight (10, out int rHeight));
+			Assert.Equal (10, rHeight);
+
+			v.Height = Dim.Fill (1);
+			Assert.False (v.SetHeight (10, out rHeight));
+			Assert.Equal (9, rHeight);
+
+			v.Height = null;
+			Assert.True (v.SetHeight (10, out rHeight));
+			Assert.Equal (10, rHeight);
+
+			v.IsInitialized = true;
+			v.Height = Dim.Fill (1);
+			Assert.Throws<ArgumentException> (() => v.Height = 15);
+			v.LayoutStyle = LayoutStyle.Absolute;
+			v.Height = 15;
+			Assert.True (v.SetHeight (5, out rHeight));
+			Assert.Equal (5, rHeight);
+		}
+
+		[Fact]
+		public void GetCurrentWidth_CanSetWidth ()
+		{
+			var top = new View () {
+				X = 0,
+				Y = 0,
+				Width = 80,
+			};
+
+			var v = new View () {
+				Width = Dim.Fill ()
+			};
+			top.Add (v);
+
+			Assert.False (v.GetCurrentWidth (out int cWidth));
+			Assert.Equal (80, cWidth);
+
+			v.Width = Dim.Fill (1);
+			Assert.False (v.GetCurrentWidth (out cWidth));
+			Assert.Equal (79, cWidth);
+		}
+
+		[Fact]
+		public void GetCurrentHeight_CanSetHeight ()
+		{
+			var top = new View () {
+				X = 0,
+				Y = 0,
+				Height = 20
+			};
+
+			var v = new View () {
+				Height = Dim.Fill ()
+			};
+			top.Add (v);
+
+			Assert.False (v.GetCurrentHeight (out int cHeight));
+			Assert.Equal (20, cHeight);
+
+			v.Height = Dim.Fill (1);
+			Assert.False (v.GetCurrentHeight (out cHeight));
+			Assert.Equal (19, cHeight);
+		}
+
+		[Fact]
+		public void AutoSize_False_ResizeView_Is_Always_False ()
+		{
+			var label = new Label () { AutoSize = false };
+
+			label.Text = "New text";
+
+			Assert.False (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=0,Height=0}", label.Bounds.ToString ());
+		}
+
+		[Fact]
+		public void AutoSize_True_ResizeView_With_Dim_Absolute ()
+		{
+			var label = new Label ();
+
+			label.Text = "New text";
+
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=8,Height=1}", label.Bounds.ToString ());
+		}
+
+		[Fact]
+		public void AutoSize_True_ResizeView_With_Dim_Fill ()
+		{
+			var win = new Window (new Rect (0, 0, 30, 80), "");
+			var label = new Label () { Width = Dim.Fill (), Height = Dim.Fill () };
+			win.Add (label);
+
+			label.Text = "New text\nNew line";
+			win.LayoutSubviews ();
+
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=78}", label.Bounds.ToString ());
+		}
+
+		[Fact]
+		public void AutoSize_True_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute ()
+		{
+			var win = new Window (new Rect (0, 0, 30, 80), "");
+			var label = new Label () { Width = Dim.Fill () };
+			win.Add (label);
+
+			label.Text = "New text\nNew line";
+			win.LayoutSubviews ();
+
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=2}", label.Bounds.ToString ());
+		}
 	}
 	}
 }
 }