private async Task <Message> RunLoop(CancellationToken ct) { TaskUtilities.AssertIsOnBackgroundThread(); try { _log.EnterRLoop(_rLoopDepth++); while (!ct.IsCancellationRequested) { var message = await ReceiveMessageAsync(ct); if (message.IsResponse) { Request request; if (!_requests.TryRemove(message.RequestId, out request)) { throw ProtocolError($"Mismatched response - no request with such ID:", message); } else if (message.Name != ":" + request.MessageName.Substring(1)) { throw ProtocolError($"Mismatched response - message name does not match request '{request.MessageName}':", message); } request.Handle(this, message); continue; } try { switch (message.Name) { case "!End": return(null); case "!CanceledAll": CancelAll(); break; case "?YesNoCancel": ShowDialog(message, MessageButtons.YesNoCancel, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token) .SilenceException <MessageTransportException>() .DoNotWait(); break; case "?YesNo": ShowDialog(message, MessageButtons.YesNo, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token) .SilenceException <MessageTransportException>() .DoNotWait(); break; case "?OkCancel": ShowDialog(message, MessageButtons.OKCancel, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token) .SilenceException <MessageTransportException>() .DoNotWait(); break; case "?>": ReadConsole(message, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token) .SilenceException <MessageTransportException>() .DoNotWait(); break; case "!": case "!!": message.ExpectArguments(1); await _callbacks.WriteConsoleEx( message.GetString(0, "buf", allowNull: true), message.Name.Length == 1?OutputType.Output : OutputType.Error, ct); break; case "!ShowMessage": message.ExpectArguments(1); await _callbacks.ShowMessage(message.GetString(0, "s", allowNull: true), ct); break; case "!+": await _callbacks.Busy(true, ct); break; case "!-": await _callbacks.Busy(false, ct); break; case "!SetWD": _callbacks.DirectoryChanged(); break; case "!Library": await _callbacks.ViewLibrary(); break; case "!ShowFile": message.ExpectArguments(3); await _callbacks.ShowFile( message.GetString(0, "file"), message.GetString(1, "tabName"), message.GetBoolean(2, "delete.file")); break; case "!View": message.ExpectArguments(2); _callbacks.ViewObject(message.GetString(0, "x"), message.GetString(1, "title")); break; case "!Plot": await _callbacks.Plot( new PlotMessage( message.GetString(0, "xaml_file_path"), message.GetInt32(1, "active_plot_index"), message.GetInt32(2, "plot_count"), message.Blob), ct); break; case "?Locator": var locatorResult = await _callbacks.Locator(ct); ct.ThrowIfCancellationRequested(); await RespondAsync(message, ct, locatorResult.Clicked, locatorResult.X, locatorResult.Y); break; case "!WebBrowser": await _callbacks.WebBrowser(message.GetString(0, "url")); break; case "!PackagesInstalled": _callbacks.PackagesInstalled(); break; case "!PackagesRemoved": _callbacks.PackagesRemoved(); break; case "!FetchFile": var destPath = await _callbacks.SaveFileAsync(message.GetString(0, "file_path"), message.Blob); await _callbacks.WriteConsoleEx(destPath, OutputType.Error, ct); break; default: throw ProtocolError($"Unrecognized host message name:", message); } } catch (OperationCanceledException) when(!ct.IsCancellationRequested) { // Canceled via _cancelAllCts - just move onto the next message. } } } finally { _log.ExitRLoop(--_rLoopDepth); } return(null); }