UICatalog.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. using NStack;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using Terminal.Gui;
  11. using Rune = System.Rune;
  12. /// <summary>
  13. /// UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the catalog of scenarios.
  14. /// </summary>
  15. /// <remarks>
  16. /// <para>
  17. /// UI Catalog attempts to satisfy the following goals:
  18. /// </para>
  19. /// <para>
  20. /// <list type="number">
  21. /// <item>
  22. /// <description>
  23. /// Be an easy to use showcase for Terminal.Gui concepts and features.
  24. /// </description>
  25. /// </item>
  26. /// <item>
  27. /// <description>
  28. /// Provide sample code that illustrates how to properly implement said concepts & features.
  29. /// </description>
  30. /// </item>
  31. /// <item>
  32. /// <description>
  33. /// Make it easy for contributors to add additional samples in a structured way.
  34. /// </description>
  35. /// </item>
  36. /// </list>
  37. /// </para>
  38. /// <para>
  39. /// See the project README for more details (https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog/README.md).
  40. /// </para>
  41. /// </remarks>
  42. namespace UICatalog {
  43. /// <summary>
  44. /// UI Catalog is a comprehensive sample app and scenario library for <see cref="Terminal.Gui"/>
  45. /// </summary>
  46. public class UICatalogApp {
  47. private static int _nameColumnWidth;
  48. private static FrameView _leftPane;
  49. private static List<string> _categories;
  50. private static ListView _categoryListView;
  51. private static FrameView _rightPane;
  52. private static List<Scenario> _scenarios;
  53. private static ListView _scenarioListView;
  54. private static StatusBar _statusBar;
  55. private static StatusItem _capslock;
  56. private static StatusItem _numlock;
  57. private static StatusItem _scrolllock;
  58. // If set, holds the scenario the user selected
  59. private static Scenario _selectedScenario = null;
  60. private static bool _useSystemConsole = false;
  61. private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
  62. private static bool _heightAsBuffer = false;
  63. private static bool _isFirstRunning = true;
  64. // When a scenario is run, the main app is killed. These items
  65. // are therefore cached so that when the scenario exits the
  66. // main app UI can be restored to previous state
  67. private static int _cachedScenarioIndex = 0;
  68. private static int _cachedCategoryIndex = 0;
  69. private static StringBuilder _aboutMessage;
  70. static void Main (string [] args)
  71. {
  72. Console.OutputEncoding = Encoding.Default;
  73. if (Debugger.IsAttached) {
  74. CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
  75. }
  76. _scenarios = Scenario.GetScenarios ();
  77. if (args.Length > 0 && args.Contains ("-usc")) {
  78. _useSystemConsole = true;
  79. args = args.Where (val => val != "-usc").ToArray ();
  80. }
  81. if (args.Length > 0) {
  82. var item = _scenarios.FindIndex (s => s.GetName ().Equals (args [0], StringComparison.OrdinalIgnoreCase));
  83. _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ());
  84. Application.UseSystemConsole = _useSystemConsole;
  85. Application.Init ();
  86. _selectedScenario.Init (Application.Top, _colorScheme);
  87. _selectedScenario.Setup ();
  88. _selectedScenario.Run ();
  89. _selectedScenario = null;
  90. Application.Shutdown ();
  91. return;
  92. }
  93. _aboutMessage = new StringBuilder ();
  94. _aboutMessage.AppendLine (@"A comprehensive sample library for");
  95. _aboutMessage.AppendLine (@"");
  96. _aboutMessage.AppendLine (@" _______ _ _ _____ _ ");
  97. _aboutMessage.AppendLine (@" |__ __| (_) | | / ____| (_) ");
  98. _aboutMessage.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ ");
  99. _aboutMessage.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
  100. _aboutMessage.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | ");
  101. _aboutMessage.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ");
  102. _aboutMessage.AppendLine (@"");
  103. _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
  104. Scenario scenario;
  105. while ((scenario = SelectScenario ()) != null) {
  106. #if DEBUG_IDISPOSABLE
  107. // Validate there are no outstanding Responder-based instances
  108. // after a scenario was selected to run. This proves the main UI Catalog
  109. // 'app' closed cleanly.
  110. foreach (var inst in Responder.Instances) {
  111. Debug.Assert (inst.WasDisposed);
  112. }
  113. Responder.Instances.Clear ();
  114. #endif
  115. scenario.Init (Application.Top, _colorScheme);
  116. scenario.Setup ();
  117. scenario.Run ();
  118. // This call to Application.Shutdown brackets the Application.Init call
  119. // made by Scenario.Init()
  120. Application.Shutdown ();
  121. #if DEBUG_IDISPOSABLE
  122. // After the scenario runs, validate all Responder-based instances
  123. // were disposed. This proves the scenario 'app' closed cleanly.
  124. foreach (var inst in Responder.Instances) {
  125. Debug.Assert (inst.WasDisposed);
  126. }
  127. Responder.Instances.Clear ();
  128. #endif
  129. }
  130. #if DEBUG_IDISPOSABLE
  131. // This proves that when the user exited the UI Catalog app
  132. // it cleaned up properly.
  133. foreach (var inst in Responder.Instances) {
  134. Debug.Assert (inst.WasDisposed);
  135. }
  136. Responder.Instances.Clear ();
  137. #endif
  138. }
  139. /// <summary>
  140. /// Shows the UI Catalog selection UI. When the user selects a Scenario to run, the
  141. /// UI Catalog main app UI is killed and the Scenario is run as though it were Application.Top.
  142. /// When the Scenario exits, this function exits.
  143. /// </summary>
  144. /// <returns></returns>
  145. private static Scenario SelectScenario ()
  146. {
  147. Application.UseSystemConsole = _useSystemConsole;
  148. Application.Init ();
  149. if (_colorScheme == null) {
  150. // `Colors` is not initilized until the ConsoleDriver is loaded by
  151. // Application.Init. Set it only the first time though so it is
  152. // preserved between running multiple Scenarios
  153. _colorScheme = Colors.Base;
  154. }
  155. Application.HeightAsBuffer = _heightAsBuffer;
  156. var menu = new MenuBar (new MenuBarItem [] {
  157. new MenuBarItem ("_File", new MenuItem [] {
  158. new MenuItem ("_Quit", "Quit UI Catalog", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask)
  159. }),
  160. new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
  161. new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()),
  162. new MenuBarItem ("_Help", new MenuItem [] {
  163. new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1),
  164. new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2),
  165. new MenuItem ("_About...",
  166. "About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
  167. }),
  168. });
  169. _leftPane = new FrameView ("Categories") {
  170. X = 0,
  171. Y = 1, // for menu
  172. Width = 25,
  173. Height = Dim.Fill (1),
  174. CanFocus = true,
  175. Shortcut = Key.CtrlMask | Key.C
  176. };
  177. _leftPane.Title = $"{_leftPane.Title} ({_leftPane.ShortcutTag})";
  178. _leftPane.ShortcutAction = () => _leftPane.SetFocus ();
  179. _categories = Scenario.GetAllCategories ();
  180. _categoryListView = new ListView (_categories) {
  181. X = 0,
  182. Y = 0,
  183. Width = Dim.Fill (0),
  184. Height = Dim.Fill (0),
  185. AllowsMarking = false,
  186. CanFocus = true,
  187. };
  188. _categoryListView.OpenSelectedItem += (a) => {
  189. _rightPane.SetFocus ();
  190. };
  191. _categoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
  192. _leftPane.Add (_categoryListView);
  193. _rightPane = new FrameView ("Scenarios") {
  194. X = 25,
  195. Y = 1, // for menu
  196. Width = Dim.Fill (),
  197. Height = Dim.Fill (1),
  198. CanFocus = true,
  199. Shortcut = Key.CtrlMask | Key.S
  200. };
  201. _rightPane.Title = $"{_rightPane.Title} ({_rightPane.ShortcutTag})";
  202. _rightPane.ShortcutAction = () => _rightPane.SetFocus ();
  203. _nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length;
  204. _scenarioListView = new ListView () {
  205. X = 0,
  206. Y = 0,
  207. Width = Dim.Fill (0),
  208. Height = Dim.Fill (0),
  209. AllowsMarking = false,
  210. CanFocus = true,
  211. };
  212. _scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
  213. _rightPane.Add (_scenarioListView);
  214. _capslock = new StatusItem (Key.CharMask, "Caps", null);
  215. _numlock = new StatusItem (Key.CharMask, "Num", null);
  216. _scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
  217. _statusBar = new StatusBar () {
  218. Visible = true,
  219. };
  220. _statusBar.Items = new StatusItem [] {
  221. _capslock,
  222. _numlock,
  223. _scrolllock,
  224. new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
  225. if (_selectedScenario is null){
  226. // This causes GetScenarioToRun to return null
  227. _selectedScenario = null;
  228. Application.RequestStop();
  229. } else {
  230. _selectedScenario.RequestStop();
  231. }
  232. }),
  233. new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
  234. _statusBar.Visible = !_statusBar.Visible;
  235. _leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
  236. _rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
  237. Application.Top.LayoutSubviews();
  238. Application.Top.SetChildNeedsDisplay();
  239. }),
  240. new StatusItem (Key.CharMask, Application.Driver.GetType ().Name, null),
  241. };
  242. Application.Top.ColorScheme = _colorScheme;
  243. Application.Top.KeyDown += KeyDownHandler;
  244. Application.Top.Add (menu);
  245. Application.Top.Add (_leftPane);
  246. Application.Top.Add (_rightPane);
  247. Application.Top.Add (_statusBar);
  248. void TopHandler ()
  249. {
  250. if (_selectedScenario != null) {
  251. _selectedScenario = null;
  252. _isFirstRunning = false;
  253. }
  254. if (!_isFirstRunning) {
  255. _rightPane.SetFocus ();
  256. }
  257. Application.Top.Loaded -= TopHandler;
  258. }
  259. Application.Top.Loaded += TopHandler;
  260. // Restore previous selections
  261. _categoryListView.SelectedItem = _cachedCategoryIndex;
  262. _scenarioListView.SelectedItem = _cachedScenarioIndex;
  263. // Run UI Catalog UI. When it exits, if _selectedScenario is != null then
  264. // a Scenario was selected. Otherwise, the user wants to exit UI Catalog.
  265. Application.Run (Application.Top);
  266. Application.Shutdown ();
  267. return _selectedScenario;
  268. }
  269. /// <summary>
  270. /// Launches the selected scenario, setting the global _selectedScenario
  271. /// </summary>
  272. /// <param name="e"></param>
  273. private static void _scenarioListView_OpenSelectedItem (EventArgs e)
  274. {
  275. if (_selectedScenario is null) {
  276. // Save selected item state
  277. _cachedCategoryIndex = _categoryListView.SelectedItem;
  278. _cachedScenarioIndex = _scenarioListView.SelectedItem;
  279. // Create new instance of scenario (even though Scenarios contains instances)
  280. _selectedScenario = (Scenario)Activator.CreateInstance (_scenarioListView.Source.ToList () [_scenarioListView.SelectedItem].GetType ());
  281. // Tell the main app to stop
  282. Application.RequestStop ();
  283. }
  284. }
  285. static List<MenuItem []> CreateDiagnosticMenuItems ()
  286. {
  287. List<MenuItem []> menuItems = new List<MenuItem []> ();
  288. menuItems.Add (CreateDiagnosticFlagsMenuItems ());
  289. menuItems.Add (new MenuItem [] { null });
  290. menuItems.Add (CreateSizeStyle ());
  291. menuItems.Add (CreateDisabledEnabledMouse ());
  292. menuItems.Add (CreateKeybindings ());
  293. return menuItems;
  294. }
  295. private static MenuItem [] CreateDisabledEnabledMouse ()
  296. {
  297. List<MenuItem> menuItems = new List<MenuItem> ();
  298. var item = new MenuItem ();
  299. item.Title = "_Disable Mouse";
  300. item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
  301. item.CheckType |= MenuItemCheckStyle.Checked;
  302. item.Checked = Application.IsMouseDisabled;
  303. item.Action += () => {
  304. item.Checked = Application.IsMouseDisabled = !item.Checked;
  305. };
  306. menuItems.Add (item);
  307. return menuItems.ToArray ();
  308. }
  309. private static MenuItem [] CreateKeybindings ()
  310. {
  311. List<MenuItem> menuItems = new List<MenuItem> ();
  312. var item = new MenuItem ();
  313. item.Title = "_Key Bindings";
  314. item.Help = "Change which keys do what";
  315. item.Action += () => {
  316. var dlg = new KeyBindingsDialog ();
  317. Application.Run (dlg);
  318. };
  319. menuItems.Add (null);
  320. menuItems.Add (item);
  321. return menuItems.ToArray ();
  322. }
  323. static MenuItem [] CreateSizeStyle ()
  324. {
  325. List<MenuItem> menuItems = new List<MenuItem> ();
  326. var item = new MenuItem ();
  327. item.Title = "_Height As Buffer";
  328. item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
  329. item.CheckType |= MenuItemCheckStyle.Checked;
  330. item.Checked = Application.HeightAsBuffer;
  331. item.Action += () => {
  332. item.Checked = !item.Checked;
  333. _heightAsBuffer = item.Checked;
  334. Application.HeightAsBuffer = _heightAsBuffer;
  335. };
  336. menuItems.Add (item);
  337. return menuItems.ToArray ();
  338. }
  339. static MenuItem [] CreateDiagnosticFlagsMenuItems ()
  340. {
  341. const string OFF = "Diagnostics: _Off";
  342. const string FRAME_RULER = "Diagnostics: Frame _Ruler";
  343. const string FRAME_PADDING = "Diagnostics: _Frame Padding";
  344. var index = 0;
  345. List<MenuItem> menuItems = new List<MenuItem> ();
  346. foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
  347. var item = new MenuItem ();
  348. item.Title = GetDiagnosticsTitle (diag);
  349. item.Shortcut = Key.AltMask + index.ToString () [0];
  350. index++;
  351. item.CheckType |= MenuItemCheckStyle.Checked;
  352. if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) {
  353. item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding
  354. | ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0;
  355. } else {
  356. item.Checked = _diagnosticFlags.HasFlag (diag);
  357. }
  358. item.Action += () => {
  359. var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
  360. if (item.Title == t && !item.Checked) {
  361. _diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
  362. item.Checked = true;
  363. } else if (item.Title == t && item.Checked) {
  364. _diagnosticFlags |= (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
  365. item.Checked = false;
  366. } else {
  367. var f = GetDiagnosticsEnumValue (item.Title);
  368. if (_diagnosticFlags.HasFlag (f)) {
  369. SetDiagnosticsFlag (f, false);
  370. } else {
  371. SetDiagnosticsFlag (f, true);
  372. }
  373. }
  374. foreach (var menuItem in menuItems) {
  375. if (menuItem.Title == t) {
  376. menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler)
  377. && !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding);
  378. } else if (menuItem.Title != t) {
  379. menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
  380. }
  381. }
  382. ConsoleDriver.Diagnostics = _diagnosticFlags;
  383. Application.Top.SetNeedsDisplay ();
  384. };
  385. menuItems.Add (item);
  386. }
  387. return menuItems.ToArray ();
  388. string GetDiagnosticsTitle (Enum diag)
  389. {
  390. switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) {
  391. case "Off":
  392. return OFF;
  393. case "FrameRuler":
  394. return FRAME_RULER;
  395. case "FramePadding":
  396. return FRAME_PADDING;
  397. }
  398. return "";
  399. }
  400. Enum GetDiagnosticsEnumValue (ustring title)
  401. {
  402. switch (title.ToString ()) {
  403. case FRAME_RULER:
  404. return ConsoleDriver.DiagnosticFlags.FrameRuler;
  405. case FRAME_PADDING:
  406. return ConsoleDriver.DiagnosticFlags.FramePadding;
  407. }
  408. return null;
  409. }
  410. void SetDiagnosticsFlag (Enum diag, bool add)
  411. {
  412. switch (diag) {
  413. case ConsoleDriver.DiagnosticFlags.FrameRuler:
  414. if (add) {
  415. _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler;
  416. } else {
  417. _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler;
  418. }
  419. break;
  420. case ConsoleDriver.DiagnosticFlags.FramePadding:
  421. if (add) {
  422. _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding;
  423. } else {
  424. _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding;
  425. }
  426. break;
  427. default:
  428. _diagnosticFlags = default;
  429. break;
  430. }
  431. }
  432. }
  433. static ColorScheme _colorScheme;
  434. static MenuItem [] CreateColorSchemeMenuItems ()
  435. {
  436. List<MenuItem> menuItems = new List<MenuItem> ();
  437. foreach (var sc in Colors.ColorSchemes) {
  438. var item = new MenuItem ();
  439. item.Title = $"_{sc.Key}";
  440. item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
  441. item.CheckType |= MenuItemCheckStyle.Radio;
  442. item.Checked = sc.Value == _colorScheme;
  443. item.Action += () => {
  444. Application.Top.ColorScheme = _colorScheme = sc.Value;
  445. Application.Top?.SetNeedsDisplay ();
  446. foreach (var menuItem in menuItems) {
  447. menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme;
  448. }
  449. };
  450. menuItems.Add (item);
  451. }
  452. return menuItems.ToArray ();
  453. }
  454. private static void KeyDownHandler (View.KeyEventEventArgs a)
  455. {
  456. if (a.KeyEvent.IsCapslock) {
  457. _capslock.Title = "Caps: On";
  458. _statusBar.SetNeedsDisplay ();
  459. } else {
  460. _capslock.Title = "Caps: Off";
  461. _statusBar.SetNeedsDisplay ();
  462. }
  463. if (a.KeyEvent.IsNumlock) {
  464. _numlock.Title = "Num: On";
  465. _statusBar.SetNeedsDisplay ();
  466. } else {
  467. _numlock.Title = "Num: Off";
  468. _statusBar.SetNeedsDisplay ();
  469. }
  470. if (a.KeyEvent.IsScrolllock) {
  471. _scrolllock.Title = "Scroll: On";
  472. _statusBar.SetNeedsDisplay ();
  473. } else {
  474. _scrolllock.Title = "Scroll: Off";
  475. _statusBar.SetNeedsDisplay ();
  476. }
  477. }
  478. private static void CategoryListView_SelectedChanged (ListViewItemEventArgs e)
  479. {
  480. var item = _categories [e.Item];
  481. List<Scenario> newlist;
  482. if (e.Item == 0) {
  483. // First category is "All"
  484. newlist = _scenarios;
  485. } else {
  486. newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList ();
  487. }
  488. _scenarioListView.SetSource (newlist.ToList ());
  489. }
  490. private static void OpenUrl (string url)
  491. {
  492. try {
  493. if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
  494. url = url.Replace ("&", "^&");
  495. Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
  496. } else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
  497. using (var process = new Process {
  498. StartInfo = new ProcessStartInfo {
  499. FileName = "xdg-open",
  500. Arguments = url,
  501. RedirectStandardError = true,
  502. RedirectStandardOutput = true,
  503. CreateNoWindow = true,
  504. UseShellExecute = false
  505. }
  506. }) {
  507. process.Start ();
  508. }
  509. } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
  510. Process.Start ("open", url);
  511. }
  512. } catch {
  513. throw;
  514. }
  515. }
  516. }
  517. }