Editor.cs 42 KB

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