Editor.cs 42 KB

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