ソースを参照

better error handling

Charlie Kindel 2 年 前
コミット
495c4c6499

+ 73 - 96
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -956,9 +956,10 @@ namespace Terminal.Gui {
 		public static bool Is_WSL_Platform ()
 		{
 			if (new CursesClipboard ().IsSupported) {
+				// If xclip is installed on Linux under WSL, this will return true.
 				return false;
 			}
-			var (exitCode, result) = BashRunner.Run ("uname -a", runCurses: false);
+			var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", runCurses: false);
 			if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
 				return true;
 			}
@@ -1273,28 +1274,33 @@ namespace Terminal.Gui {
 			IsSupported = CheckSupport ();
 		}
 
+		string xclipPath = string.Empty;
 		public override bool IsSupported { get; }
 
 		bool CheckSupport ()
 		{
 			try {
-				var (exitCode, result) = BashRunner.Run ("which xclip", runCurses: false);
-				return (exitCode == 0 && result.FileExists ());
+				var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", runCurses: false);
+				if (exitCode == 0 && result.FileExists ()) {
+					xclipPath = result;
+					return true;
+				}
 			} catch (Exception) {
 				// Permissions issue.
-				return false;
 			}
+			return false;
 		}
 
 		protected override string GetClipboardDataImpl ()
 		{
 			var tempFileName = System.IO.Path.GetTempFileName ();
 			try {
-				// BashRunner.Run ($"xsel -o --clipboard > {tempFileName}");
-				var (exitCode, result) = BashRunner.Run ($"xclip -selection clipboard -o > {tempFileName}");
+				var (exitCode, result) = ClipboardProcessRunner.Bash ($"{xclipPath} -selection clipboard -o > {tempFileName}");
 				if (exitCode == 0) {
 					return System.IO.File.ReadAllText (tempFileName);
 				}
+			} catch (Exception e) {
+				throw new NotSupportedException ($"{xclipPath} -selection clipboard -o failed.", e);
 			} finally {
 				System.IO.File.Delete (tempFileName);
 			}
@@ -1303,65 +1309,73 @@ namespace Terminal.Gui {
 
 		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);
+			try {
+				ClipboardProcessRunner.Bash ($"{xclipPath} - selection clipboard -i", false, text);
+			} catch (Exception e) {
+				throw new NotSupportedException ($"{xclipPath} -selection clipboard -o failed", e);
+			}
 		}
 	}
 
-	static class BashRunner {
-		public static (int exitCode, string result) Run (string commandLine, bool output = true, string inputText = "", bool runCurses = true)
+	internal static class ClipboardProcessRunner {
+		public static (int exitCode, string result) Bash (string commandLine, bool output = true, string inputText = "", bool runCurses = true)
 		{
 			var arguments = $"-c \"{commandLine}\"";
+			var (exitCode, result) = Process ("bash", arguments, inputText);
+
+			if (exitCode == 0) {
+				if (runCurses && Application.Driver is CursesDriver) {
+					Curses.raw ();
+					Curses.noecho ();
+				}
+			}
+			return (exitCode, result.TrimEnd ());
+		}
 
+		public static (int exitCode, string result) Process (string cmd, string arguments, string input = null)
+		{
 			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",
+			var output = string.Empty;
+
+			using (Process process = new Process {
+				StartInfo = new ProcessStartInfo {
+					FileName = cmd,
 					Arguments = arguments,
-					RedirectStandardInput = true,
 					RedirectStandardOutput = true,
 					RedirectStandardError = true,
+					RedirectStandardInput = true,
 					UseShellExecute = false,
-					CreateNoWindow = false,
+					CreateNoWindow = true,
 				}
 			}) {
 				process.Start ();
-				if (output) {
-					process.StandardInput.Write (inputText);
+				if (!string.IsNullOrEmpty (input)) {
+					process.StandardInput.Write (input);
+					process.StandardInput.Close ();
 				}
-				process.StandardInput.Close ();
+
 				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: {process.StartInfo.FileName} {arguments}.
+
+				if (!process.WaitForExit (5000)) {
+					var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.
 							Output: {outputBuilder}
 							Error: {errorBuilder}";
 					throw new TimeoutException (timeoutError);
 				}
-				if (process.ExitCode == 0) {
-					if (runCurses && Application.Driver is CursesDriver) {
-						Curses.raw ();
-						Curses.noecho ();
-					}
-					return (process.ExitCode, outputBuilder.ToString ().TrimEnd ());
-				}
 
-				var error = $@"Could not execute process. ExitCode: {process.ExitCode}, Command line: {process.StartInfo.FileName} {arguments}.
-						Output: {outputBuilder}
-						Error: {errorBuilder}";
-				return (process.ExitCode, error);
+				if (process.ExitCode > 0) {
+					output = $@"Process failed to run. Command line: {cmd} {arguments}.
+										Output: {outputBuilder}
+										Error: {errorBuilder}";
+				} else {
+					output = outputBuilder.ToString ().TrimEnd ();
+				}
+				return (process.ExitCode, output);
 			}
 		}
 
@@ -1380,6 +1394,7 @@ namespace Terminal.Gui {
 		}
 	}
 
+
 	/// <summary>
 	///  A clipboard implementation for MacOSX. 
 	///  This implementation uses the Mac clipboard API (via P/Invoke) to copy/paste.
@@ -1412,12 +1427,12 @@ namespace Terminal.Gui {
 
 		bool CheckSupport ()
 		{
-			var (exitCode, result) = BashRunner.Run ("which pbcopy");
-			if (!result.FileExists ()) {
+			var (exitCode, result) = ClipboardProcessRunner.Bash ("which pbcopy");
+			if (exitCode != 0 || !result.FileExists ()) {
 				return false;
 			}
-			(exitCode, result) = BashRunner.Run ("which pbpaste");
-			return result.FileExists ();
+			(exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste");
+			return exitCode == 0 && result.FileExists ();
 		}
 
 		protected override string GetClipboardDataImpl ()
@@ -1474,83 +1489,45 @@ namespace Terminal.Gui {
 
 		public override bool IsSupported { get; }
 
-		private string powershellCommand = string.Empty;
+		private string powershellPath = string.Empty;
 
 		bool CheckSupport ()
 		{
-			if (string.IsNullOrEmpty (powershellCommand)) {
+			if (string.IsNullOrEmpty (powershellPath)) {
 				// Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL)
-				var (exitCode, result) = BashRunner.Run ("which pwsh.exe");
+				var (exitCode, result) = ClipboardProcessRunner.Bash ("which pwsh.exe");
 				if (exitCode > 0) {
-					(exitCode, result) = BashRunner.Run ("which powershell.exe");
+					(exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe");
 				}
 
 				if (exitCode == 0) {
-					powershellCommand = result;
+					powershellPath = result;
 				}
 			}
-			return !string.IsNullOrEmpty (powershellCommand);
+			return !string.IsNullOrEmpty (powershellPath);
 		}
 
 		protected override string GetClipboardDataImpl ()
 		{
-			if (!IsSupported) return string.Empty;
-			using (var powershell = new System.Diagnostics.Process {
-				StartInfo = new System.Diagnostics.ProcessStartInfo {
-					RedirectStandardOutput = true,
-					FileName = powershellCommand,
-					Arguments = "-noprofile -command \"Get-Clipboard\"",
-					UseShellExecute = false,
-					CreateNoWindow = true
-				}
-			}) {
-				powershell.Start ();
-				if (!powershell.DoubleWaitForExit ()) {
-					var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}.
-							Output: {powershell.StandardOutput.ReadToEnd ()}
-							Error: {powershell.StandardError.ReadToEnd ()}";
-					throw new Exception (timeoutError);
-				}
-				var result = powershell.StandardOutput.ReadToEnd ();
-				powershell.StandardOutput.Close ();
+			var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, "-noprofile -command \"Get-Clipboard\"");
+			if (exitCode == 0) { 
 				if (Application.Driver is CursesDriver) {
 					Curses.raw ();
 					Curses.noecho ();
 				}
-				if (result.EndsWith ("\r\n")) {
-					result = result.Substring (0, result.Length - 2);
+
+				if (output.EndsWith ("\r\n")) {
+					output = output.Substring (0, output.Length - 2);
 				}
-				return result;
+				return output;
 			}
+			return string.Empty;
 		}
 
 		protected override void SetClipboardDataImpl (string text)
 		{
-			if (!IsSupported) return;
-
-			using (var powershell = new System.Diagnostics.Process {
-				StartInfo = new System.Diagnostics.ProcessStartInfo {
-					RedirectStandardOutput = true,
-					RedirectStandardError = true,
-					RedirectStandardInput = true,
-					FileName = powershellCommand,
-					Arguments = $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""
-				}
-			}) {
-				powershell.Start ();
-				powershell.WaitForExit ();
-				if (!powershell.DoubleWaitForExit ()) {
-					var timeoutError = $@"Process timed out. Command line: {powershell.StartInfo.FileName} {powershell.StartInfo.Arguments}.
-							Output: {powershell.StandardOutput.ReadToEnd ()}
-							Error: {powershell.StandardError.ReadToEnd ()}";
-					throw new TimeoutException (timeoutError);
-				}
-				if (powershell.ExitCode > 0) {
-					var setClipboardError = $@"Set-Clipboard failed. Command line: {powershell.StartInfo.FileName} {powershell.StartInfo.Arguments}.
-							Output: {powershell.StandardOutput.ReadToEnd ()}
-							Error: {powershell.StandardError.ReadToEnd ()}";
-					throw new System.InvalidOperationException (setClipboardError);
-				}
+			var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\"");
+			if (exitCode == 0) {
 				if (Application.Driver is CursesDriver) {
 					Curses.raw ();
 					Curses.noecho ();

+ 17 - 23
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -27,28 +27,7 @@ namespace Terminal.Gui {
 		public override int Top => 0;
 		public override bool HeightAsBuffer { get; set; }
 		private IClipboard clipboard = null;
-		public override IClipboard Clipboard {
-			get {
-				if (clipboard == null) {
-					if (usingFakeClipboard) {
-						clipboard = new FakeClipboard ();
-					} else {
-						if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-							clipboard = new WindowsClipboard ();
-						} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-							clipboard = new MacOSXClipboard ();
-						} else {
-							if (CursesDriver.Is_WSL_Platform ()) {
-								clipboard = new WSLClipboard ();
-							} else {
-								clipboard = new CursesClipboard ();
-							}
-						}
-					}
-				}
-				return clipboard;
-			}
-		}
+		public override IClipboard Clipboard => clipboard;
 
 		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
 		int [,,] contents;
@@ -83,6 +62,21 @@ namespace Terminal.Gui {
 		public FakeDriver (bool useFakeClipboard = true)
 		{
 			usingFakeClipboard = useFakeClipboard;
+			if (usingFakeClipboard) {
+				clipboard = new FakeClipboard ();
+			} else {
+				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+					clipboard = new WindowsClipboard ();
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+					clipboard = new MacOSXClipboard ();
+				} else {
+					if (CursesDriver.Is_WSL_Platform ()) {
+						clipboard = new WSLClipboard ();
+					} else {
+						clipboard = new CursesClipboard ();
+					}
+				}
+			}
 		}
 
 		bool needMove;
@@ -654,7 +648,7 @@ namespace Terminal.Gui {
 		}
 
 		#endregion
-		
+
 		public class FakeClipboard : ClipboardBase {
 			public override bool IsSupported => true;
 

+ 19 - 15
Terminal.Gui/Core/Clipboard/ClipboardBase.cs

@@ -15,9 +15,10 @@ namespace Terminal.Gui {
 		public abstract bool IsSupported { get; }
 
 		/// <summary>
-		/// Get the operation system clipboard.
+		/// Returns the contents of the OS clipboard if possible.
 		/// </summary>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents</exception>
+		/// <returns>The contents of the OS clipboard if successful.</returns>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
 		public string GetClipboardData ()
 		{
 			try {
@@ -28,35 +29,38 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Get the operation system clipboard.
+		/// Returns the contents of the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific subclasses.
 		/// </summary>
+		/// <returns>The contents of the OS clipboard if successful.</returns>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
 		protected abstract string GetClipboardDataImpl ();
 
 		/// <summary>
-		/// Sets the operation system clipboard.
+		/// Pastes the <paramref name="text"/> to the OS clipboard if possible.
 		/// </summary>
-		/// <param name="text"></param>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents</exception>
+		/// <param name="text">The text to paste to the OS clipboard.</param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
 		public void SetClipboardData (string text)
 		{
 			try {
 				SetClipboardDataImpl (text);
 			} catch (Exception ex) {
-				throw new NotSupportedException ("Failed to write to clipboard.", ex);
+				throw new NotSupportedException ("Failed to paste to the OS clipboard.", ex);
 			}
 		}
 
 		/// <summary>
-		/// Sets the operation system clipboard.
+		/// Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific subclasses.
 		/// </summary>
-		/// <param name="text"></param>
+		/// <param name="text">The text to paste to the OS clipboard.</param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
 		protected abstract void SetClipboardDataImpl (string text);
 
 		/// <summary>
-		/// Gets the operation system clipboard if possible.
+		/// Copies the contents of the OS clipboard to <paramref name="result"/> if possible.
 		/// </summary>
-		/// <param name="result">Clipboard contents read</param>
-		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		/// <param name="result">The contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
+		/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
 		public bool TryGetClipboardData (out string result)
 		{
 			// Don't even try to read because environment is not set up.
@@ -78,10 +82,10 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Sets the operation system clipboard if possible.
+		/// Pastes the <paramref name="text"/> to the OS clipboard if possible.
 		/// </summary>
-		/// <param name="text"></param>
-		/// <returns>True if the clipboard content was set successfully</returns>
+		/// <param name="text">The text to paste to the OS clipboard.</param>
+		/// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
 		public bool TrySetClipboardData (string text)
 		{
 			// Don't even try to set because environment is not set up

+ 17 - 45
UnitTests/ClipboardTests.cs

@@ -69,37 +69,6 @@ namespace Terminal.Gui.ConsoleDrivers {
 			}
 		}
 
-		private static string RunClipboardProcess (string cmd, string args, string writeText = null)
-		{
-			string output = string.Empty;
-
-			using (Process process = new Process {
-				StartInfo = new ProcessStartInfo {
-					FileName = cmd,
-					Arguments = args,
-					RedirectStandardOutput = true,
-					RedirectStandardError = true,
-					RedirectStandardInput = true
-				}
-			}) {
-				process.Start ();
-				if (string.IsNullOrEmpty (writeText)) {
-					process.StandardInput.Write (writeText);
-					process.StandardInput.Close ();
-				}
-				process.WaitForExit ();
-
-				if (process.ExitCode > 0) {
-					var error = $@"RunClipboardProcess failed. Command line: {cmd} {args}.
-										Output: {process.StandardOutput.ReadToEnd ()}
-										Error: {process.StandardError.ReadToEnd ()}";
-					throw new InvalidOperationException (error);
-				}
-				output = process.StandardOutput.ReadToEnd ().TrimEnd ();
-				process.StandardOutput.Close ();
-			}
-			return output;
-		}
 
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
 		public void Contents_Gets_From_OS_Clipboard ()
@@ -110,18 +79,18 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 			Application.Iteration += () => {
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-					RunClipboardProcess ("pwsh", $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\"");
+					ClipboardProcessRunner.Process ("pwsh", $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\"");
 					getClipText = Clipboard.Contents.ToString ();
 
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-					RunClipboardProcess ("pbcopy", string.Empty, clipText);
+					ClipboardProcessRunner.Process ("pbcopy", string.Empty, clipText);
 					getClipText = Clipboard.Contents.ToString ();
 
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
 					if (Is_WSL_Platform ()) {
 						try {
 							// This runs the WINDOWS version of powershell.exe via WSL.
-							RunClipboardProcess ("powershell.exe", $"-noprofile -command \"Set-Clipboard -Value \\\"{clipText}\\\"\"");
+							ClipboardProcessRunner.Process ("powershell.exe", $"-noprofile -command \"Set-Clipboard -Value \\\"{clipText}\\\"\"");
 						} catch {
 							failed = true;
 						}
@@ -139,7 +108,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 					}
 
 					// If we get here, powershell didn't work and xclip exists...
-					RunClipboardProcess ("bash", $"-c \"xclip -sel clip -i\"", clipText);
+					ClipboardProcessRunner.Process ("bash", $"-c \"xclip -sel clip -i\"", clipText);
 
 					if (!failed) {
 						getClipText = Clipboard.Contents.ToString ();
@@ -168,27 +137,30 @@ namespace Terminal.Gui.ConsoleDrivers {
 				Clipboard.Contents = clipText;
 
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-					clipReadText = RunClipboardProcess ("pwsh", "-noprofile -command \"Get-Clipboard\"");
+					(_, clipReadText) = ClipboardProcessRunner.Process ("pwsh", "-noprofile -command \"Get-Clipboard\"");
 
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-					clipReadText = RunClipboardProcess ("pbpaste", "");
+					(_, clipReadText) = ClipboardProcessRunner.Process ("pbpaste", "");
 
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
+					var exitCode = 0;
 					if (Is_WSL_Platform ()) {
-						try {
-							clipReadText = RunClipboardProcess ("/opt/microsoft/powershell/7/pwsh", "-noprofile -command \"Get-Clipboard\"");
-						} catch {
-							failed = true;
+						(exitCode, clipReadText) = ClipboardProcessRunner.Process ("powershell.exe", "-noprofile -command \"Get-Clipboard\"");
+						if (exitCode == 0) {
+							Application.RequestStop ();
+							return;
 						}
-						Application.RequestStop ();
+						failed = true;
 					}
+
 					if (failed = xclipExists () == false) {
 						// xclip doesn't exist then exit.
 						Application.RequestStop ();
 						return;
 					}
 
-					clipReadText = RunClipboardProcess ("bash", $"-c \"xclip -sel clip -o\"");
+					(exitCode, clipReadText) = ClipboardProcessRunner.Process ("bash", $"-c \"xclip -sel clip -o\"");
+					Assert.Equal (0, exitCode);
 				}
 
 				Application.RequestStop ();
@@ -204,14 +176,14 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 		bool Is_WSL_Platform ()
 		{
-			var result = RunClipboardProcess ("bash", $"-c \"uname -a\"");
+			var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"uname -a\"");
 			return result.Contains ("microsoft") && result.Contains ("WSL");
 		}
 
 		bool xclipExists ()
 		{
 			try {
-				var result = RunClipboardProcess ("bash", $"-c \"which xclip\"");
+				var (_, result) = ClipboardProcessRunner.Process ("bash", $"-c \"which xclip\"");
 				return result.TrimEnd () != "";
 			} catch (System.Exception) {
 				return false;