Editor.cs 48 KB

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