Editor.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using Terminal.Gui;
  11. using static UICatalog.Scenarios.DynamicMenuBar;
  12. namespace UICatalog.Scenarios;
  13. [ScenarioMetadata ("Editor", "A Text Editor using the TextView control.")]
  14. [ScenarioCategory ("Controls")]
  15. [ScenarioCategory ("Dialogs")]
  16. [ScenarioCategory ("Text and Formatting")]
  17. [ScenarioCategory ("Top Level Windows")]
  18. [ScenarioCategory ("Files and IO")]
  19. [ScenarioCategory ("TextView")]
  20. [ScenarioCategory ("Menus")]
  21. public class Editor : Scenario
  22. {
  23. private Window _appWindow;
  24. private List<CultureInfo> _cultureInfos;
  25. private string _fileName = "demo.txt";
  26. private bool _forceMinimumPosToZero = true;
  27. private bool _matchCase;
  28. private bool _matchWholeWord;
  29. private MenuItem _miForceMinimumPosToZero;
  30. private byte [] _originalText;
  31. private bool _saved = true;
  32. private ScrollBarView _scrollBar;
  33. private TabView _tabView;
  34. private string _textToFind;
  35. private string _textToReplace;
  36. private TextView _textView;
  37. private FindReplaceWindow _findReplaceWindow;
  38. public override void Main ()
  39. {
  40. // Init
  41. Application.Init ();
  42. // Setup - Create a top-level application window and configure it.
  43. _appWindow = new ()
  44. {
  45. //Title = GetQuitKeyAndName (),
  46. Title = _fileName ?? "Untitled",
  47. BorderStyle = LineStyle.None
  48. };
  49. _cultureInfos = Application.SupportedCultures;
  50. _textView = new ()
  51. {
  52. X = 0,
  53. Y = 1,
  54. Width = Dim.Fill (),
  55. Height = Dim.Fill (1),
  56. };
  57. CreateDemoFile (_fileName);
  58. LoadFile ();
  59. _appWindow.Add (_textView);
  60. var menu = new MenuBar
  61. {
  62. Menus =
  63. [
  64. new (
  65. "_File",
  66. new MenuItem []
  67. {
  68. new ("_New", "", () => New ()),
  69. new ("_Open", "", () => Open ()),
  70. new ("_Save", "", () => Save ()),
  71. new ("_Save As", "", () => SaveAs ()),
  72. new ("_Close", "", () => CloseFile ()),
  73. null,
  74. new ("_Quit", "", () => Quit ())
  75. }
  76. ),
  77. new (
  78. "_Edit",
  79. new MenuItem []
  80. {
  81. new (
  82. "_Copy",
  83. "",
  84. () => Copy (),
  85. null,
  86. null,
  87. KeyCode.CtrlMask | KeyCode.C
  88. ),
  89. new (
  90. "C_ut",
  91. "",
  92. () => Cut (),
  93. null,
  94. null,
  95. KeyCode.CtrlMask | KeyCode.W
  96. ),
  97. new (
  98. "_Paste",
  99. "",
  100. () => Paste (),
  101. null,
  102. null,
  103. KeyCode.CtrlMask | KeyCode.Y
  104. ),
  105. null,
  106. new (
  107. "_Find",
  108. "",
  109. () => Find (),
  110. null,
  111. null,
  112. KeyCode.CtrlMask | KeyCode.S
  113. ),
  114. new (
  115. "Find _Next",
  116. "",
  117. () => FindNext (),
  118. null,
  119. null,
  120. KeyCode.CtrlMask
  121. | KeyCode.ShiftMask
  122. | KeyCode.S
  123. ),
  124. new (
  125. "Find P_revious",
  126. "",
  127. () => FindPrevious (),
  128. null,
  129. null,
  130. KeyCode.CtrlMask
  131. | KeyCode.ShiftMask
  132. | KeyCode.AltMask
  133. | KeyCode.S
  134. ),
  135. new (
  136. "_Replace",
  137. "",
  138. () => Replace (),
  139. null,
  140. null,
  141. KeyCode.CtrlMask | KeyCode.R
  142. ),
  143. new (
  144. "Replace Ne_xt",
  145. "",
  146. () => ReplaceNext (),
  147. null,
  148. null,
  149. KeyCode.CtrlMask
  150. | KeyCode.ShiftMask
  151. | KeyCode.R
  152. ),
  153. new (
  154. "Replace Pre_vious",
  155. "",
  156. () => ReplacePrevious (),
  157. null,
  158. null,
  159. KeyCode.CtrlMask
  160. | KeyCode.ShiftMask
  161. | KeyCode.AltMask
  162. | KeyCode.R
  163. ),
  164. new (
  165. "Replace _All",
  166. "",
  167. () => ReplaceAll (),
  168. null,
  169. null,
  170. KeyCode.CtrlMask
  171. | KeyCode.ShiftMask
  172. | KeyCode.AltMask
  173. | KeyCode.A
  174. ),
  175. null,
  176. new (
  177. "_Select All",
  178. "",
  179. () => SelectAll (),
  180. null,
  181. null,
  182. KeyCode.CtrlMask | KeyCode.T
  183. )
  184. }
  185. ),
  186. new ("_ScrollBarView", CreateKeepChecked ()),
  187. new ("_Cursor", CreateCursorRadio ()),
  188. new (
  189. "Forma_t",
  190. new []
  191. {
  192. CreateWrapChecked (),
  193. CreateAutocomplete (),
  194. CreateAllowsTabChecked (),
  195. CreateReadOnlyChecked ()
  196. }
  197. ),
  198. new (
  199. "_Responder",
  200. new [] { CreateCanFocusChecked (), CreateEnabledChecked (), CreateVisibleChecked () }
  201. ),
  202. new (
  203. "Conte_xtMenu",
  204. new []
  205. {
  206. _miForceMinimumPosToZero = new (
  207. "ForceMinimumPosTo_Zero",
  208. "",
  209. () =>
  210. {
  211. _miForceMinimumPosToZero.Checked =
  212. _forceMinimumPosToZero =
  213. !_forceMinimumPosToZero;
  214. _textView.ContextMenu.ForceMinimumPosToZero =
  215. _forceMinimumPosToZero;
  216. }
  217. )
  218. {
  219. CheckType = MenuItemCheckStyle.Checked,
  220. Checked = _forceMinimumPosToZero
  221. },
  222. new MenuBarItem ("_Languages", GetSupportedCultures ())
  223. }
  224. )
  225. ]
  226. };
  227. _appWindow.Add (menu);
  228. var siCursorPosition = new Shortcut (KeyCode.Null, "", null);
  229. var statusBar = new StatusBar (
  230. new []
  231. {
  232. new (Application.QuitKey, $"Quit", Quit),
  233. new (Key.F2, "Open", Open),
  234. new (Key.F3, "Save", () => Save ()),
  235. new (Key.F4, "Save As", () => SaveAs ()),
  236. new (Key.Empty, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null),
  237. siCursorPosition,
  238. }
  239. )
  240. {
  241. AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast
  242. };
  243. _textView.UnwrappedCursorPosition += (s, e) =>
  244. {
  245. siCursorPosition.Title = $"Ln {e.Point.Y + 1}, Col {e.Point.X + 1}";
  246. };
  247. _appWindow.Add (statusBar);
  248. _scrollBar = new (_textView, true);
  249. _scrollBar.ChangedPosition += (s, e) =>
  250. {
  251. _textView.TopRow = _scrollBar.Position;
  252. if (_textView.TopRow != _scrollBar.Position)
  253. {
  254. _scrollBar.Position = _textView.TopRow;
  255. }
  256. _textView.SetNeedsDisplay ();
  257. };
  258. _scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
  259. {
  260. _textView.LeftColumn = _scrollBar.OtherScrollBarView.Position;
  261. if (_textView.LeftColumn != _scrollBar.OtherScrollBarView.Position)
  262. {
  263. _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn;
  264. }
  265. _textView.SetNeedsDisplay ();
  266. };
  267. _textView.DrawContent += (s, e) =>
  268. {
  269. _scrollBar.Size = _textView.Lines;
  270. _scrollBar.Position = _textView.TopRow;
  271. if (_scrollBar.OtherScrollBarView != null)
  272. {
  273. _scrollBar.OtherScrollBarView.Size = _textView.Maxlength;
  274. _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn;
  275. }
  276. _scrollBar.LayoutSubviews ();
  277. _scrollBar.Refresh ();
  278. };
  279. _appWindow.Closed += (s, e) => Thread.CurrentThread.CurrentUICulture = new ("en-US");
  280. CreateFindReplace ();
  281. // Run - Start the application.
  282. Application.Run (_appWindow);
  283. _appWindow.Dispose ();
  284. // Shutdown - Calling Application.Shutdown is required.
  285. Application.Shutdown ();
  286. }
  287. private bool CanCloseFile ()
  288. {
  289. if (_textView.Text == Encoding.Unicode.GetString (_originalText))
  290. {
  291. //System.Diagnostics.Debug.Assert (!_textView.IsDirty);
  292. return true;
  293. }
  294. Debug.Assert (_textView.IsDirty);
  295. int r = MessageBox.ErrorQuery (
  296. "Save File",
  297. $"Do you want save changes in {_appWindow.Title}?",
  298. "Yes",
  299. "No",
  300. "Cancel"
  301. );
  302. if (r == 0)
  303. {
  304. return Save ();
  305. }
  306. if (r == 1)
  307. {
  308. return true;
  309. }
  310. return false;
  311. }
  312. private void CloseFile ()
  313. {
  314. if (!CanCloseFile ())
  315. {
  316. return;
  317. }
  318. try
  319. {
  320. _textView.CloseFile ();
  321. New (false);
  322. }
  323. catch (Exception ex)
  324. {
  325. MessageBox.ErrorQuery ("Error", ex.Message, "Ok");
  326. }
  327. }
  328. private void ContinueFind (bool next = true, bool replace = false)
  329. {
  330. if (!replace && string.IsNullOrEmpty (_textToFind))
  331. {
  332. Find ();
  333. return;
  334. }
  335. if (replace
  336. && (string.IsNullOrEmpty (_textToFind)
  337. || (_findReplaceWindow == null && string.IsNullOrEmpty (_textToReplace))))
  338. {
  339. Replace ();
  340. return;
  341. }
  342. bool found;
  343. bool gaveFullTurn;
  344. if (next)
  345. {
  346. if (!replace)
  347. {
  348. found = _textView.FindNextText (
  349. _textToFind,
  350. out gaveFullTurn,
  351. _matchCase,
  352. _matchWholeWord
  353. );
  354. }
  355. else
  356. {
  357. found = _textView.FindNextText (
  358. _textToFind,
  359. out gaveFullTurn,
  360. _matchCase,
  361. _matchWholeWord,
  362. _textToReplace,
  363. true
  364. );
  365. }
  366. }
  367. else
  368. {
  369. if (!replace)
  370. {
  371. found = _textView.FindPreviousText (
  372. _textToFind,
  373. out gaveFullTurn,
  374. _matchCase,
  375. _matchWholeWord
  376. );
  377. }
  378. else
  379. {
  380. found = _textView.FindPreviousText (
  381. _textToFind,
  382. out gaveFullTurn,
  383. _matchCase,
  384. _matchWholeWord,
  385. _textToReplace,
  386. true
  387. );
  388. }
  389. }
  390. if (!found)
  391. {
  392. MessageBox.Query ("Find", $"The following specified text was not found: '{_textToFind}'", "Ok");
  393. }
  394. else if (gaveFullTurn)
  395. {
  396. MessageBox.Query (
  397. "Find",
  398. $"No more occurrences were found for the following specified text: '{_textToFind}'",
  399. "Ok"
  400. );
  401. }
  402. }
  403. private void Copy ()
  404. {
  405. if (_textView != null)
  406. {
  407. _textView.Copy ();
  408. }
  409. }
  410. private MenuItem CreateAllowsTabChecked ()
  411. {
  412. var item = new MenuItem { Title = "Allows Tab" };
  413. item.CheckType |= MenuItemCheckStyle.Checked;
  414. item.Checked = _textView.AllowsTab;
  415. item.Action += () => { _textView.AllowsTab = (bool)(item.Checked = !item.Checked); };
  416. return item;
  417. }
  418. private MenuItem CreateAutocomplete ()
  419. {
  420. var singleWordGenerator = new SingleWordSuggestionGenerator ();
  421. _textView.Autocomplete.SuggestionGenerator = singleWordGenerator;
  422. var auto = new MenuItem ();
  423. auto.Title = "Autocomplete";
  424. auto.CheckType |= MenuItemCheckStyle.Checked;
  425. auto.Checked = false;
  426. auto.Action += () =>
  427. {
  428. if ((bool)(auto.Checked = !auto.Checked))
  429. {
  430. // setup autocomplete with all words currently in the editor
  431. singleWordGenerator.AllSuggestions =
  432. Regex.Matches (_textView.Text, "\\w+")
  433. .Select (s => s.Value)
  434. .Distinct ()
  435. .ToList ();
  436. }
  437. else
  438. {
  439. singleWordGenerator.AllSuggestions.Clear ();
  440. }
  441. };
  442. return auto;
  443. }
  444. private MenuItem CreateCanFocusChecked ()
  445. {
  446. var item = new MenuItem { Title = "CanFocus" };
  447. item.CheckType |= MenuItemCheckStyle.Checked;
  448. item.Checked = _textView.CanFocus;
  449. item.Action += () =>
  450. {
  451. _textView.CanFocus = (bool)(item.Checked = !item.Checked);
  452. if (_textView.CanFocus)
  453. {
  454. _textView.SetFocus ();
  455. }
  456. };
  457. return item;
  458. }
  459. private MenuItem [] CreateCursorRadio ()
  460. {
  461. List<MenuItem> menuItems = new ();
  462. menuItems.Add (
  463. new ("_Invisible", "", () => SetCursor (CursorVisibility.Invisible))
  464. {
  465. CheckType = MenuItemCheckStyle.Radio,
  466. Checked = _textView.CursorVisibility
  467. == CursorVisibility.Invisible
  468. }
  469. );
  470. menuItems.Add (
  471. new ("_Box", "", () => SetCursor (CursorVisibility.Box))
  472. {
  473. CheckType = MenuItemCheckStyle.Radio,
  474. Checked = _textView.CursorVisibility == CursorVisibility.Box
  475. }
  476. );
  477. menuItems.Add (
  478. new ("_Underline", "", () => SetCursor (CursorVisibility.Underline))
  479. {
  480. CheckType = MenuItemCheckStyle.Radio,
  481. Checked = _textView.CursorVisibility
  482. == CursorVisibility.Underline
  483. }
  484. );
  485. menuItems.Add (new ("", "", () => { }, () => false));
  486. menuItems.Add (new ("xTerm :", "", () => { }, () => false));
  487. menuItems.Add (new ("", "", () => { }, () => false));
  488. menuItems.Add (
  489. new (" _Default", "", () => SetCursor (CursorVisibility.Default))
  490. {
  491. CheckType = MenuItemCheckStyle.Radio,
  492. Checked = _textView.CursorVisibility
  493. == CursorVisibility.Default
  494. }
  495. );
  496. menuItems.Add (
  497. new (" _Vertical", "", () => SetCursor (CursorVisibility.Vertical))
  498. {
  499. CheckType = MenuItemCheckStyle.Radio,
  500. Checked = _textView.CursorVisibility
  501. == CursorVisibility.Vertical
  502. }
  503. );
  504. menuItems.Add (
  505. new (" V_ertical Fix", "", () => SetCursor (CursorVisibility.VerticalFix))
  506. {
  507. CheckType = MenuItemCheckStyle.Radio,
  508. Checked = _textView.CursorVisibility == CursorVisibility.VerticalFix
  509. }
  510. );
  511. menuItems.Add (
  512. new (" B_ox Fix", "", () => SetCursor (CursorVisibility.BoxFix))
  513. {
  514. CheckType = MenuItemCheckStyle.Radio,
  515. Checked = _textView.CursorVisibility
  516. == CursorVisibility.BoxFix
  517. }
  518. );
  519. menuItems.Add (
  520. new (" U_nderline Fix", "", () => SetCursor (CursorVisibility.UnderlineFix))
  521. {
  522. CheckType = MenuItemCheckStyle.Radio,
  523. Checked = _textView.CursorVisibility == CursorVisibility.UnderlineFix
  524. }
  525. );
  526. void SetCursor (CursorVisibility visibility)
  527. {
  528. _textView.CursorVisibility = visibility;
  529. var title = "";
  530. switch (visibility)
  531. {
  532. case CursorVisibility.Default:
  533. title = " _Default";
  534. break;
  535. case CursorVisibility.Invisible:
  536. title = "_Invisible";
  537. break;
  538. case CursorVisibility.Underline:
  539. title = "_Underline";
  540. break;
  541. case CursorVisibility.UnderlineFix:
  542. title = " U_nderline Fix";
  543. break;
  544. case CursorVisibility.Vertical:
  545. title = " _Vertical";
  546. break;
  547. case CursorVisibility.VerticalFix:
  548. title = " V_ertical Fix";
  549. break;
  550. case CursorVisibility.Box:
  551. title = "_Box";
  552. break;
  553. case CursorVisibility.BoxFix:
  554. title = " B_ox Fix";
  555. break;
  556. }
  557. foreach (MenuItem menuItem in menuItems)
  558. {
  559. menuItem.Checked = menuItem.Title.Equals (title) && visibility == _textView.CursorVisibility;
  560. }
  561. }
  562. return menuItems.ToArray ();
  563. }
  564. private void CreateDemoFile (string fileName)
  565. {
  566. var sb = new StringBuilder ();
  567. // FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
  568. sb.Append ("Hello world.\n");
  569. sb.Append ("This is a test of the Emergency Broadcast System.\n");
  570. for (var i = 0; i < 30; i++)
  571. {
  572. sb.Append (
  573. $"{i} - This is a test with a very long line and many lines to test the ScrollViewBar against the TextView. - {i}\n"
  574. );
  575. }
  576. StreamWriter sw = File.CreateText (fileName);
  577. sw.Write (sb.ToString ());
  578. sw.Close ();
  579. }
  580. private MenuItem CreateEnabledChecked ()
  581. {
  582. var item = new MenuItem { Title = "Enabled" };
  583. item.CheckType |= MenuItemCheckStyle.Checked;
  584. item.Checked = _textView.Enabled;
  585. item.Action += () =>
  586. {
  587. _textView.Enabled = (bool)(item.Checked = !item.Checked);
  588. if (_textView.Enabled)
  589. {
  590. _textView.SetFocus ();
  591. }
  592. };
  593. return item;
  594. }
  595. private class FindReplaceWindow : Window
  596. {
  597. private TextView _textView;
  598. public FindReplaceWindow (TextView textView)
  599. {
  600. Title = "Find and Replace";
  601. _textView = textView;
  602. X = Pos.AnchorEnd () - 1;
  603. Y = 2;
  604. Width = 57;
  605. Height = 11;
  606. Arrangement = ViewArrangement.Movable;
  607. KeyBindings.Add (Key.Esc, KeyBindingScope.Focused, Command.Cancel);
  608. AddCommand (Command.Cancel, () =>
  609. {
  610. Visible = false;
  611. return true;
  612. });
  613. VisibleChanged += FindReplaceWindow_VisibleChanged;
  614. Initialized += FindReplaceWindow_Initialized;
  615. //var btnCancel = new Button
  616. //{
  617. // X = Pos.AnchorEnd (),
  618. // Y = Pos.AnchorEnd (),
  619. // Text = "Cancel"
  620. //};
  621. //btnCancel.Accept += (s, e) => { Visible = false; };
  622. //Add (btnCancel);
  623. }
  624. private void FindReplaceWindow_VisibleChanged (object sender, EventArgs e)
  625. {
  626. if (Visible == false)
  627. {
  628. _textView.SetFocus ();
  629. }
  630. else
  631. {
  632. FocusFirst (null);
  633. }
  634. }
  635. private void FindReplaceWindow_Initialized (object sender, EventArgs e)
  636. {
  637. Border.LineStyle = LineStyle.Dashed;
  638. Border.Thickness = new (0, 1, 0, 0);
  639. }
  640. }
  641. private void ShowFindReplace (bool isFind = true)
  642. {
  643. _findReplaceWindow.Visible = true;
  644. _findReplaceWindow.SuperView.BringSubviewToFront (_findReplaceWindow);
  645. _tabView.SetFocus ();
  646. _tabView.SelectedTab = isFind ? _tabView.Tabs.ToArray () [0] : _tabView.Tabs.ToArray () [1];
  647. _tabView.SelectedTab.View.FocusFirst (null);
  648. }
  649. private void CreateFindReplace ()
  650. {
  651. _findReplaceWindow = new (_textView);
  652. _tabView = new ()
  653. {
  654. X = 0, Y = 0,
  655. Width = Dim.Fill (), Height = Dim.Fill (0)
  656. };
  657. _tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true);
  658. _tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false);
  659. _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst (null);
  660. _findReplaceWindow.Add (_tabView);
  661. _tabView.SelectedTab.View.FocusLast (null); // Hack to get the first tab to be focused
  662. _findReplaceWindow.Visible = false;
  663. _appWindow.Add (_findReplaceWindow);
  664. }
  665. private MenuItem [] CreateKeepChecked ()
  666. {
  667. var item = new MenuItem ();
  668. item.Title = "Keep Content Always In Viewport";
  669. item.CheckType |= MenuItemCheckStyle.Checked;
  670. item.Checked = true;
  671. item.Action += () => _scrollBar.KeepContentAlwaysInViewport = (bool)(item.Checked = !item.Checked);
  672. return new [] { item };
  673. }
  674. private MenuItem CreateReadOnlyChecked ()
  675. {
  676. var item = new MenuItem { Title = "Read Only" };
  677. item.CheckType |= MenuItemCheckStyle.Checked;
  678. item.Checked = _textView.ReadOnly;
  679. item.Action += () => _textView.ReadOnly = (bool)(item.Checked = !item.Checked);
  680. return item;
  681. }
  682. private MenuItem CreateVisibleChecked ()
  683. {
  684. var item = new MenuItem { Title = "Visible" };
  685. item.CheckType |= MenuItemCheckStyle.Checked;
  686. item.Checked = _textView.Visible;
  687. item.Action += () =>
  688. {
  689. _textView.Visible = (bool)(item.Checked = !item.Checked);
  690. if (_textView.Visible)
  691. {
  692. _textView.SetFocus ();
  693. }
  694. };
  695. return item;
  696. }
  697. private MenuItem CreateWrapChecked ()
  698. {
  699. var item = new MenuItem { Title = "Word Wrap" };
  700. item.CheckType |= MenuItemCheckStyle.Checked;
  701. item.Checked = _textView.WordWrap;
  702. item.Action += () =>
  703. {
  704. _textView.WordWrap = (bool)(item.Checked = !item.Checked);
  705. if (_textView.WordWrap)
  706. {
  707. _scrollBar.OtherScrollBarView.ShowScrollIndicator = false;
  708. }
  709. };
  710. return item;
  711. }
  712. private void Cut ()
  713. {
  714. if (_textView != null)
  715. {
  716. _textView.Cut ();
  717. }
  718. }
  719. private void Find () { ShowFindReplace (true); }
  720. private void FindNext () { ContinueFind (); }
  721. private void FindPrevious () { ContinueFind (false); }
  722. private View CreateFindTab ()
  723. {
  724. var d = new View ()
  725. {
  726. Width = Dim.Fill (),
  727. Height = Dim.Fill ()
  728. };
  729. int lblWidth = "Replace:".Length;
  730. var label = new Label
  731. {
  732. Width = lblWidth,
  733. TextAlignment = Alignment.End,
  734. Text = "Find:"
  735. };
  736. d.Add (label);
  737. SetFindText ();
  738. var txtToFind = new TextField
  739. {
  740. X = Pos.Right (label) + 1,
  741. Y = Pos.Top (label),
  742. Width = Dim.Fill (1),
  743. Text = _textToFind
  744. };
  745. txtToFind.Enter += (s, e) => txtToFind.Text = _textToFind;
  746. d.Add (txtToFind);
  747. var btnFindNext = new Button
  748. {
  749. X = Pos.Align (Alignment.Center),
  750. Y = Pos.AnchorEnd (),
  751. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  752. IsDefault = true,
  753. Text = "Find _Next"
  754. };
  755. btnFindNext.Accept += (s, e) => FindNext ();
  756. d.Add (btnFindNext);
  757. var btnFindPrevious = new Button
  758. {
  759. X = Pos.Align (Alignment.Center),
  760. Y = Pos.AnchorEnd (),
  761. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  762. Text = "Find _Previous"
  763. };
  764. btnFindPrevious.Accept += (s, e) => FindPrevious ();
  765. d.Add (btnFindPrevious);
  766. txtToFind.TextChanged += (s, e) =>
  767. {
  768. _textToFind = txtToFind.Text;
  769. _textView.FindTextChanged ();
  770. btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  771. btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  772. };
  773. var ckbMatchCase = new CheckBox
  774. {
  775. X = 0, Y = Pos.Top (txtToFind) + 2, State = _matchCase ? CheckState.Checked : CheckState.UnChecked, Text = "Match c_ase"
  776. };
  777. ckbMatchCase.Toggle += (s, e) => _matchCase = e.NewValue == CheckState.Checked;
  778. d.Add (ckbMatchCase);
  779. var ckbMatchWholeWord = new CheckBox
  780. {
  781. X = 0, Y = Pos.Top (ckbMatchCase) + 1, State = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked, Text = "Match _whole word"
  782. };
  783. ckbMatchWholeWord.Toggle += (s, e) => _matchWholeWord = e.NewValue == CheckState.Checked;
  784. d.Add (ckbMatchWholeWord);
  785. return d;
  786. }
  787. private MenuItem [] GetSupportedCultures ()
  788. {
  789. List<MenuItem> supportedCultures = new ();
  790. int index = -1;
  791. foreach (CultureInfo c in _cultureInfos)
  792. {
  793. var culture = new MenuItem { CheckType = MenuItemCheckStyle.Checked };
  794. if (index == -1)
  795. {
  796. culture.Title = "_English";
  797. culture.Help = "en-US";
  798. culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == "en-US";
  799. CreateAction (supportedCultures, culture);
  800. supportedCultures.Add (culture);
  801. index++;
  802. culture = new () { CheckType = MenuItemCheckStyle.Checked };
  803. }
  804. culture.Title = $"_{c.Parent.EnglishName}";
  805. culture.Help = c.Name;
  806. culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == c.Name;
  807. CreateAction (supportedCultures, culture);
  808. supportedCultures.Add (culture);
  809. }
  810. return supportedCultures.ToArray ();
  811. void CreateAction (List<MenuItem> supportedCultures, MenuItem culture)
  812. {
  813. culture.Action += () =>
  814. {
  815. Thread.CurrentThread.CurrentUICulture = new (culture.Help);
  816. culture.Checked = true;
  817. foreach (MenuItem item in supportedCultures)
  818. {
  819. item.Checked = item.Help == Thread.CurrentThread.CurrentUICulture.Name;
  820. }
  821. };
  822. }
  823. }
  824. private void LoadFile ()
  825. {
  826. if (_fileName != null)
  827. {
  828. // FIXED: BUGBUG: #452 TextView.LoadFile keeps file open and provides no way of closing it
  829. _textView.Load (_fileName);
  830. //_textView.Text = System.IO.File.ReadAllText (_fileName);
  831. _originalText = Encoding.Unicode.GetBytes (_textView.Text);
  832. _appWindow.Title = _fileName;
  833. _saved = true;
  834. }
  835. }
  836. private void New (bool checkChanges = true)
  837. {
  838. if (checkChanges && !CanCloseFile ())
  839. {
  840. return;
  841. }
  842. _appWindow.Title = "Untitled.txt";
  843. _fileName = null;
  844. _originalText = new MemoryStream ().ToArray ();
  845. _textView.Text = Encoding.Unicode.GetString (_originalText);
  846. }
  847. private void Open ()
  848. {
  849. if (!CanCloseFile ())
  850. {
  851. return;
  852. }
  853. List<IAllowedType> aTypes = new ()
  854. {
  855. new AllowedType (
  856. "Text",
  857. ".txt;.bin;.xml;.json",
  858. ".txt",
  859. ".bin",
  860. ".xml",
  861. ".json"
  862. ),
  863. new AllowedTypeAny ()
  864. };
  865. var d = new OpenDialog { Title = "Open", AllowedTypes = aTypes, AllowsMultipleSelection = false };
  866. Application.Run (d);
  867. if (!d.Canceled && d.FilePaths.Count > 0)
  868. {
  869. _fileName = d.FilePaths [0];
  870. LoadFile ();
  871. }
  872. d.Dispose ();
  873. }
  874. private void Paste ()
  875. {
  876. if (_textView != null)
  877. {
  878. _textView.Paste ();
  879. }
  880. }
  881. private void Quit ()
  882. {
  883. if (!CanCloseFile ())
  884. {
  885. return;
  886. }
  887. Application.RequestStop ();
  888. }
  889. private void Replace () { ShowFindReplace (false); }
  890. private void ReplaceAll ()
  891. {
  892. if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _findReplaceWindow == null))
  893. {
  894. Replace ();
  895. return;
  896. }
  897. if (_textView.ReplaceAllText (_textToFind, _matchCase, _matchWholeWord, _textToReplace))
  898. {
  899. MessageBox.Query (
  900. "Replace All",
  901. $"All occurrences were replaced for the following specified text: '{_textToReplace}'",
  902. "Ok"
  903. );
  904. }
  905. else
  906. {
  907. MessageBox.Query (
  908. "Replace All",
  909. $"None of the following specified text was found: '{_textToFind}'",
  910. "Ok"
  911. );
  912. }
  913. }
  914. private void ReplaceNext () { ContinueFind (true, true); }
  915. private void ReplacePrevious () { ContinueFind (false, true); }
  916. private View CreateReplaceTab ()
  917. {
  918. var d = new View ()
  919. {
  920. Width = Dim.Fill (),
  921. Height = Dim.Fill ()
  922. };
  923. int lblWidth = "Replace:".Length;
  924. var label = new Label
  925. {
  926. Width = lblWidth,
  927. TextAlignment = Alignment.End,
  928. Text = "Find:"
  929. };
  930. d.Add (label);
  931. SetFindText ();
  932. var txtToFind = new TextField
  933. {
  934. X = Pos.Right (label) + 1,
  935. Y = Pos.Top (label),
  936. Width = Dim.Fill (1),
  937. Text = _textToFind
  938. };
  939. txtToFind.Enter += (s, e) => txtToFind.Text = _textToFind;
  940. d.Add (txtToFind);
  941. var btnFindNext = new Button
  942. {
  943. X = Pos.Align (Alignment.Center),
  944. Y = Pos.AnchorEnd (),
  945. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  946. IsDefault = true,
  947. Text = "Replace _Next"
  948. };
  949. btnFindNext.Accept += (s, e) => ReplaceNext ();
  950. d.Add (btnFindNext);
  951. label = new ()
  952. {
  953. X = Pos.Left (label),
  954. Y = Pos.Top (label) + 1,
  955. Text = "Replace:"
  956. };
  957. d.Add (label);
  958. SetFindText ();
  959. var txtToReplace = new TextField
  960. {
  961. X = Pos.Right (label) + 1,
  962. Y = Pos.Top (label),
  963. Width = Dim.Fill (1),
  964. Text = _textToReplace
  965. };
  966. txtToReplace.TextChanged += (s, e) => _textToReplace = txtToReplace.Text;
  967. d.Add (txtToReplace);
  968. var btnFindPrevious = new Button
  969. {
  970. X = Pos.Align (Alignment.Center),
  971. Y = Pos.AnchorEnd (),
  972. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  973. Text = "Replace _Previous"
  974. };
  975. btnFindPrevious.Accept += (s, e) => ReplacePrevious ();
  976. d.Add (btnFindPrevious);
  977. var btnReplaceAll = new Button
  978. {
  979. X = Pos.Align (Alignment.Center),
  980. Y = Pos.AnchorEnd (),
  981. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  982. Text = "Replace _All"
  983. };
  984. btnReplaceAll.Accept += (s, e) => ReplaceAll ();
  985. d.Add (btnReplaceAll);
  986. txtToFind.TextChanged += (s, e) =>
  987. {
  988. _textToFind = txtToFind.Text;
  989. _textView.FindTextChanged ();
  990. btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  991. btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  992. btnReplaceAll.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  993. };
  994. var ckbMatchCase = new CheckBox
  995. {
  996. X = 0, Y = Pos.Top (txtToFind) + 2, State = _matchCase ? CheckState.Checked : CheckState.UnChecked, Text = "Match c_ase"
  997. };
  998. ckbMatchCase.Toggle += (s, e) => _matchCase = e.NewValue == CheckState.Checked;
  999. d.Add (ckbMatchCase);
  1000. var ckbMatchWholeWord = new CheckBox
  1001. {
  1002. X = 0, Y = Pos.Top (ckbMatchCase) + 1, State = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked, Text = "Match _whole word"
  1003. };
  1004. ckbMatchWholeWord.Toggle += (s, e) => _matchWholeWord = e.NewValue == CheckState.Checked;
  1005. d.Add (ckbMatchWholeWord);
  1006. return d;
  1007. }
  1008. private bool Save ()
  1009. {
  1010. if (_fileName != null)
  1011. {
  1012. // FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
  1013. // As a result files saved on Windows and then read back will show invalid chars.
  1014. return SaveFile (_appWindow.Title, _fileName);
  1015. }
  1016. return SaveAs ();
  1017. }
  1018. private bool SaveAs ()
  1019. {
  1020. List<IAllowedType> aTypes = new ()
  1021. {
  1022. new AllowedType ("Text Files", ".txt", ".bin", ".xml"), new AllowedTypeAny ()
  1023. };
  1024. var sd = new SaveDialog { Title = "Save file", AllowedTypes = aTypes };
  1025. sd.Path = _appWindow.Title;
  1026. Application.Run (sd);
  1027. bool canceled = sd.Canceled;
  1028. string path = sd.Path;
  1029. string fileName = sd.FileName;
  1030. sd.Dispose ();
  1031. if (!canceled)
  1032. {
  1033. if (File.Exists (path))
  1034. {
  1035. if (MessageBox.Query (
  1036. "Save File",
  1037. "File already exists. Overwrite any way?",
  1038. "No",
  1039. "Ok"
  1040. )
  1041. == 1)
  1042. {
  1043. return SaveFile (fileName, path);
  1044. }
  1045. _saved = false;
  1046. return _saved;
  1047. }
  1048. return SaveFile (fileName, path);
  1049. }
  1050. _saved = false;
  1051. return _saved;
  1052. }
  1053. private bool SaveFile (string title, string file)
  1054. {
  1055. try
  1056. {
  1057. _appWindow.Title = title;
  1058. _fileName = file;
  1059. File.WriteAllText (_fileName, _textView.Text);
  1060. _originalText = Encoding.Unicode.GetBytes (_textView.Text);
  1061. _saved = true;
  1062. _textView.ClearHistoryChanges ();
  1063. MessageBox.Query ("Save File", "File was successfully saved.", "Ok");
  1064. }
  1065. catch (Exception ex)
  1066. {
  1067. MessageBox.ErrorQuery ("Error", ex.Message, "Ok");
  1068. return false;
  1069. }
  1070. return true;
  1071. }
  1072. private void SelectAll () { _textView.SelectAll (); }
  1073. private void SetFindText ()
  1074. {
  1075. _textToFind = !string.IsNullOrEmpty (_textView.SelectedText) ? _textView.SelectedText :
  1076. string.IsNullOrEmpty (_textToFind) ? "" : _textToFind;
  1077. _textToReplace = string.IsNullOrEmpty (_textToReplace) ? "" : _textToReplace;
  1078. }
  1079. }