Editor.cs 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331
  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. Top = new ();
  42. Win = new()
  43. {
  44. Title = _fileName ?? "Untitled",
  45. X = 0,
  46. Y = 1,
  47. Width = Dim.Fill (),
  48. Height = Dim.Fill (),
  49. ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]
  50. };
  51. Top.Add (Win);
  52. _textView = new()
  53. {
  54. X = 0,
  55. Y = 0,
  56. Width = Dim.Fill (),
  57. Height = Dim.Fill (),
  58. BottomOffset = 1,
  59. RightOffset = 1
  60. };
  61. CreateDemoFile (_fileName);
  62. LoadFile ();
  63. Win.Add (_textView);
  64. var menu = new MenuBar
  65. {
  66. Menus =
  67. [
  68. new (
  69. "_File",
  70. new MenuItem []
  71. {
  72. new ("_New", "", () => New ()),
  73. new ("_Open", "", () => Open ()),
  74. new ("_Save", "", () => Save ()),
  75. new ("_Save As", "", () => SaveAs ()),
  76. new ("_Close", "", () => CloseFile ()),
  77. null,
  78. new ("_Quit", "", () => Quit ())
  79. }
  80. ),
  81. new (
  82. "_Edit",
  83. new MenuItem []
  84. {
  85. new (
  86. "_Copy",
  87. "",
  88. () => Copy (),
  89. null,
  90. null,
  91. KeyCode.CtrlMask | KeyCode.C
  92. ),
  93. new (
  94. "C_ut",
  95. "",
  96. () => Cut (),
  97. null,
  98. null,
  99. KeyCode.CtrlMask | KeyCode.W
  100. ),
  101. new (
  102. "_Paste",
  103. "",
  104. () => Paste (),
  105. null,
  106. null,
  107. KeyCode.CtrlMask | KeyCode.Y
  108. ),
  109. null,
  110. new (
  111. "_Find",
  112. "",
  113. () => Find (),
  114. null,
  115. null,
  116. KeyCode.CtrlMask | KeyCode.S
  117. ),
  118. new (
  119. "Find _Next",
  120. "",
  121. () => FindNext (),
  122. null,
  123. null,
  124. KeyCode.CtrlMask
  125. | KeyCode.ShiftMask
  126. | KeyCode.S
  127. ),
  128. new (
  129. "Find P_revious",
  130. "",
  131. () => FindPrevious (),
  132. null,
  133. null,
  134. KeyCode.CtrlMask
  135. | KeyCode.ShiftMask
  136. | KeyCode.AltMask
  137. | KeyCode.S
  138. ),
  139. new (
  140. "_Replace",
  141. "",
  142. () => Replace (),
  143. null,
  144. null,
  145. KeyCode.CtrlMask | KeyCode.R
  146. ),
  147. new (
  148. "Replace Ne_xt",
  149. "",
  150. () => ReplaceNext (),
  151. null,
  152. null,
  153. KeyCode.CtrlMask
  154. | KeyCode.ShiftMask
  155. | KeyCode.R
  156. ),
  157. new (
  158. "Replace Pre_vious",
  159. "",
  160. () => ReplacePrevious (),
  161. null,
  162. null,
  163. KeyCode.CtrlMask
  164. | KeyCode.ShiftMask
  165. | KeyCode.AltMask
  166. | KeyCode.R
  167. ),
  168. new (
  169. "Replace _All",
  170. "",
  171. () => ReplaceAll (),
  172. null,
  173. null,
  174. KeyCode.CtrlMask
  175. | KeyCode.ShiftMask
  176. | KeyCode.AltMask
  177. | KeyCode.A
  178. ),
  179. null,
  180. new (
  181. "_Select All",
  182. "",
  183. () => SelectAll (),
  184. null,
  185. null,
  186. KeyCode.CtrlMask | KeyCode.T
  187. )
  188. }
  189. ),
  190. new ("_ScrollBarView", CreateKeepChecked ()),
  191. new ("_Cursor", CreateCursorRadio ()),
  192. new (
  193. "Forma_t",
  194. new []
  195. {
  196. CreateWrapChecked (),
  197. CreateAutocomplete (),
  198. CreateAllowsTabChecked (),
  199. CreateReadOnlyChecked ()
  200. }
  201. ),
  202. new (
  203. "_Responder",
  204. new [] { CreateCanFocusChecked (), CreateEnabledChecked (), CreateVisibleChecked () }
  205. ),
  206. new (
  207. "Conte_xtMenu",
  208. new []
  209. {
  210. _miForceMinimumPosToZero = new (
  211. "ForceMinimumPosTo_Zero",
  212. "",
  213. () =>
  214. {
  215. _miForceMinimumPosToZero.Checked =
  216. _forceMinimumPosToZero =
  217. !_forceMinimumPosToZero;
  218. _textView.ContextMenu.ForceMinimumPosToZero =
  219. _forceMinimumPosToZero;
  220. }
  221. )
  222. {
  223. CheckType = MenuItemCheckStyle.Checked,
  224. Checked = _forceMinimumPosToZero
  225. },
  226. new MenuBarItem ("_Languages", GetSupportedCultures ())
  227. }
  228. )
  229. ]
  230. };
  231. Top.Add (menu);
  232. var siCursorPosition = new StatusItem (KeyCode.Null, "", null);
  233. var statusBar = new StatusBar (
  234. new []
  235. {
  236. siCursorPosition,
  237. new (KeyCode.F2, "~F2~ Open", () => Open ()),
  238. new (KeyCode.F3, "~F3~ Save", () => Save ()),
  239. new (KeyCode.F4, "~F4~ Save As", () => SaveAs ()),
  240. new (
  241. Application.QuitKey,
  242. $"{Application.QuitKey} to Quit",
  243. () => Quit ()
  244. ),
  245. new (
  246. KeyCode.Null,
  247. $"OS Clipboard IsSupported : {Clipboard.IsSupported}",
  248. null
  249. )
  250. }
  251. );
  252. _textView.UnwrappedCursorPosition += (s, e) =>
  253. {
  254. siCursorPosition.Title = $"Ln {e.Point.Y + 1}, Col {e.Point.X + 1}";
  255. };
  256. Top.Add (statusBar);
  257. _scrollBar = new (_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. Top.Closed += (s, e) => Thread.CurrentThread.CurrentUICulture = new ("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 ("_Invisible", "", () => SetCursor (CursorVisibility.Invisible))
  526. {
  527. CheckType = MenuItemCheckStyle.Radio,
  528. Checked = _textView.CursorVisibility
  529. == CursorVisibility.Invisible
  530. }
  531. );
  532. menuItems.Add (
  533. new ("_Box", "", () => SetCursor (CursorVisibility.Box))
  534. {
  535. CheckType = MenuItemCheckStyle.Radio,
  536. Checked = _textView.CursorVisibility == CursorVisibility.Box
  537. }
  538. );
  539. menuItems.Add (
  540. new ("_Underline", "", () => SetCursor (CursorVisibility.Underline))
  541. {
  542. CheckType = MenuItemCheckStyle.Radio,
  543. Checked = _textView.CursorVisibility
  544. == CursorVisibility.Underline
  545. }
  546. );
  547. menuItems.Add (new ("", "", () => { }, () => false));
  548. menuItems.Add (new ("xTerm :", "", () => { }, () => false));
  549. menuItems.Add (new ("", "", () => { }, () => false));
  550. menuItems.Add (
  551. new (" _Default", "", () => SetCursor (CursorVisibility.Default))
  552. {
  553. CheckType = MenuItemCheckStyle.Radio,
  554. Checked = _textView.CursorVisibility
  555. == CursorVisibility.Default
  556. }
  557. );
  558. menuItems.Add (
  559. new (" _Vertical", "", () => SetCursor (CursorVisibility.Vertical))
  560. {
  561. CheckType = MenuItemCheckStyle.Radio,
  562. Checked = _textView.CursorVisibility
  563. == CursorVisibility.Vertical
  564. }
  565. );
  566. menuItems.Add (
  567. new (" V_ertical Fix", "", () => SetCursor (CursorVisibility.VerticalFix))
  568. {
  569. CheckType = MenuItemCheckStyle.Radio,
  570. Checked = _textView.CursorVisibility == CursorVisibility.VerticalFix
  571. }
  572. );
  573. menuItems.Add (
  574. new (" B_ox Fix", "", () => SetCursor (CursorVisibility.BoxFix))
  575. {
  576. CheckType = MenuItemCheckStyle.Radio,
  577. Checked = _textView.CursorVisibility
  578. == CursorVisibility.BoxFix
  579. }
  580. );
  581. menuItems.Add (
  582. new (" U_nderline Fix", "", () => SetCursor (CursorVisibility.UnderlineFix))
  583. {
  584. CheckType = MenuItemCheckStyle.Radio,
  585. Checked = _textView.CursorVisibility == CursorVisibility.UnderlineFix
  586. }
  587. );
  588. void SetCursor (CursorVisibility visibility)
  589. {
  590. _textView.CursorVisibility = 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.CursorVisibility;
  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()
  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() { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
  676. _tabView.AddTab (new() { DisplayText = "Find", View = FindTab () }, isFind);
  677. View replace = ReplaceTab ();
  678. _tabView.AddTab (new() { 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. Justification = Justification.Right,
  771. Text = "Find:"
  772. };
  773. d.Add (label);
  774. SetFindText ();
  775. var txtToFind = new TextField
  776. {
  777. X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = _textToFind
  778. };
  779. txtToFind.Enter += (s, e) => txtToFind.Text = _textToFind;
  780. d.Add (txtToFind);
  781. var btnFindNext = new Button
  782. {
  783. X = Pos.Right (txtToFind) + 1,
  784. Y = Pos.Top (label),
  785. Width = 20,
  786. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  787. Justification = Justification.Centered,
  788. IsDefault = true,
  789. Text = "Find _Next"
  790. };
  791. btnFindNext.Accept += (s, e) => FindNext ();
  792. d.Add (btnFindNext);
  793. var btnFindPrevious = new Button
  794. {
  795. X = Pos.Right (txtToFind) + 1,
  796. Y = Pos.Top (btnFindNext) + 1,
  797. Width = 20,
  798. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  799. Justification = Justification.Centered,
  800. Text = "Find _Previous"
  801. };
  802. btnFindPrevious.Accept += (s, e) => FindPrevious ();
  803. d.Add (btnFindPrevious);
  804. txtToFind.TextChanged += (s, e) =>
  805. {
  806. _textToFind = txtToFind.Text;
  807. _textView.FindTextChanged ();
  808. btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  809. btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  810. };
  811. var btnCancel = new Button
  812. {
  813. X = Pos.Right (txtToFind) + 1,
  814. Y = Pos.Top (btnFindPrevious) + 2,
  815. Width = 20,
  816. Justification = Justification.Centered,
  817. Text = "Cancel"
  818. };
  819. btnCancel.Accept += (s, e) => { DisposeWinDialog (); };
  820. d.Add (btnCancel);
  821. var ckbMatchCase = new CheckBox
  822. {
  823. X = 0, Y = Pos.Top (txtToFind) + 2, Checked = _matchCase, Text = "Match c_ase"
  824. };
  825. ckbMatchCase.Toggled += (s, e) => _matchCase = (bool)ckbMatchCase.Checked;
  826. d.Add (ckbMatchCase);
  827. var ckbMatchWholeWord = new CheckBox
  828. {
  829. X = 0, Y = Pos.Top (ckbMatchCase) + 1, Checked = _matchWholeWord, Text = "Match _whole word"
  830. };
  831. ckbMatchWholeWord.Toggled += (s, e) => _matchWholeWord = (bool)ckbMatchWholeWord.Checked;
  832. d.Add (ckbMatchWholeWord);
  833. d.Width = label.Width + txtToFind.Width + btnFindNext.Width + 2;
  834. d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4;
  835. return d;
  836. }
  837. private MenuItem [] GetSupportedCultures ()
  838. {
  839. List<MenuItem> supportedCultures = new ();
  840. int index = -1;
  841. foreach (CultureInfo c in _cultureInfos)
  842. {
  843. var culture = new MenuItem { CheckType = MenuItemCheckStyle.Checked };
  844. if (index == -1)
  845. {
  846. culture.Title = "_English";
  847. culture.Help = "en-US";
  848. culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == "en-US";
  849. CreateAction (supportedCultures, culture);
  850. supportedCultures.Add (culture);
  851. index++;
  852. culture = new() { CheckType = MenuItemCheckStyle.Checked };
  853. }
  854. culture.Title = $"_{c.Parent.EnglishName}";
  855. culture.Help = c.Name;
  856. culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == c.Name;
  857. CreateAction (supportedCultures, culture);
  858. supportedCultures.Add (culture);
  859. }
  860. return supportedCultures.ToArray ();
  861. void CreateAction (List<MenuItem> supportedCultures, MenuItem culture)
  862. {
  863. culture.Action += () =>
  864. {
  865. Thread.CurrentThread.CurrentUICulture = new (culture.Help);
  866. culture.Checked = true;
  867. foreach (MenuItem item in supportedCultures)
  868. {
  869. item.Checked = item.Help == Thread.CurrentThread.CurrentUICulture.Name;
  870. }
  871. };
  872. }
  873. }
  874. private void LoadFile ()
  875. {
  876. if (_fileName != null)
  877. {
  878. // FIXED: BUGBUG: #452 TextView.LoadFile keeps file open and provides no way of closing it
  879. _textView.Load (_fileName);
  880. //_textView.Text = System.IO.File.ReadAllText (_fileName);
  881. _originalText = Encoding.Unicode.GetBytes (_textView.Text);
  882. Win.Title = _fileName;
  883. _saved = true;
  884. }
  885. }
  886. private void New (bool checkChanges = true)
  887. {
  888. if (checkChanges && !CanCloseFile ())
  889. {
  890. return;
  891. }
  892. Win.Title = "Untitled.txt";
  893. _fileName = null;
  894. _originalText = new MemoryStream ().ToArray ();
  895. _textView.Text = Encoding.Unicode.GetString (_originalText);
  896. }
  897. private void Open ()
  898. {
  899. if (!CanCloseFile ())
  900. {
  901. return;
  902. }
  903. List<IAllowedType> aTypes = new ()
  904. {
  905. new AllowedType (
  906. "Text",
  907. ".txt;.bin;.xml;.json",
  908. ".txt",
  909. ".bin",
  910. ".xml",
  911. ".json"
  912. ),
  913. new AllowedTypeAny ()
  914. };
  915. var d = new OpenDialog { Title = "Open", AllowedTypes = aTypes, AllowsMultipleSelection = false };
  916. Application.Run (d);
  917. if (!d.Canceled && d.FilePaths.Count > 0)
  918. {
  919. _fileName = d.FilePaths [0];
  920. LoadFile ();
  921. }
  922. d.Dispose ();
  923. }
  924. private void Paste ()
  925. {
  926. if (_textView != null)
  927. {
  928. _textView.Paste ();
  929. }
  930. }
  931. private void Quit ()
  932. {
  933. if (!CanCloseFile ())
  934. {
  935. return;
  936. }
  937. Application.RequestStop ();
  938. }
  939. private void Replace () { CreateFindReplace (false); }
  940. private void ReplaceAll ()
  941. {
  942. if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _winDialog == null))
  943. {
  944. Replace ();
  945. return;
  946. }
  947. if (_textView.ReplaceAllText (_textToFind, _matchCase, _matchWholeWord, _textToReplace))
  948. {
  949. MessageBox.Query (
  950. "Replace All",
  951. $"All occurrences were replaced for the following specified text: '{_textToReplace}'",
  952. "Ok"
  953. );
  954. }
  955. else
  956. {
  957. MessageBox.Query (
  958. "Replace All",
  959. $"None of the following specified text was found: '{_textToFind}'",
  960. "Ok"
  961. );
  962. }
  963. }
  964. private void ReplaceNext () { ContinueFind (true, true); }
  965. private void ReplacePrevious () { ContinueFind (false, true); }
  966. private View ReplaceTab ()
  967. {
  968. var d = new View ();
  969. d.DrawContent += (s, e) =>
  970. {
  971. foreach (View v in d.Subviews)
  972. {
  973. v.SetNeedsDisplay ();
  974. }
  975. };
  976. int lblWidth = "Replace:".Length;
  977. var label = new Label
  978. {
  979. Y = 1,
  980. Width = lblWidth,
  981. Justification = Justification.Right,
  982. Text = "Find:"
  983. };
  984. d.Add (label);
  985. SetFindText ();
  986. var txtToFind = new TextField
  987. {
  988. X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = _textToFind
  989. };
  990. txtToFind.Enter += (s, e) => txtToFind.Text = _textToFind;
  991. d.Add (txtToFind);
  992. var btnFindNext = new Button
  993. {
  994. X = Pos.Right (txtToFind) + 1,
  995. Y = Pos.Top (label),
  996. Width = 20,
  997. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  998. Justification = Justification.Centered,
  999. IsDefault = true,
  1000. Text = "Replace _Next"
  1001. };
  1002. btnFindNext.Accept += (s, e) => ReplaceNext ();
  1003. d.Add (btnFindNext);
  1004. label = new() { X = Pos.Left (label), Y = Pos.Top (label) + 1, Text = "Replace:" };
  1005. d.Add (label);
  1006. SetFindText ();
  1007. var txtToReplace = new TextField
  1008. {
  1009. X = Pos.Right (label) + 1, Y = Pos.Top (label), Width = 20, Text = _textToReplace
  1010. };
  1011. txtToReplace.TextChanged += (s, e) => _textToReplace = txtToReplace.Text;
  1012. d.Add (txtToReplace);
  1013. var btnFindPrevious = new Button
  1014. {
  1015. X = Pos.Right (txtToFind) + 1,
  1016. Y = Pos.Top (btnFindNext) + 1,
  1017. Width = 20,
  1018. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  1019. Justification = Justification.Centered,
  1020. Text = "Replace _Previous"
  1021. };
  1022. btnFindPrevious.Accept += (s, e) => ReplacePrevious ();
  1023. d.Add (btnFindPrevious);
  1024. var btnReplaceAll = new Button
  1025. {
  1026. X = Pos.Right (txtToFind) + 1,
  1027. Y = Pos.Top (btnFindPrevious) + 1,
  1028. Width = 20,
  1029. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  1030. Justification = Justification.Centered,
  1031. Text = "Replace _All"
  1032. };
  1033. btnReplaceAll.Accept += (s, e) => ReplaceAll ();
  1034. d.Add (btnReplaceAll);
  1035. txtToFind.TextChanged += (s, e) =>
  1036. {
  1037. _textToFind = txtToFind.Text;
  1038. _textView.FindTextChanged ();
  1039. btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  1040. btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  1041. btnReplaceAll.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  1042. };
  1043. var btnCancel = new Button
  1044. {
  1045. X = Pos.Right (txtToFind) + 1,
  1046. Y = Pos.Top (btnReplaceAll) + 1,
  1047. Width = 20,
  1048. Justification = Justification.Centered,
  1049. Text = "Cancel"
  1050. };
  1051. btnCancel.Accept += (s, e) => { DisposeWinDialog (); };
  1052. d.Add (btnCancel);
  1053. var ckbMatchCase = new CheckBox
  1054. {
  1055. X = 0, Y = Pos.Top (txtToFind) + 2, Checked = _matchCase, Text = "Match c_ase"
  1056. };
  1057. ckbMatchCase.Toggled += (s, e) => _matchCase = (bool)ckbMatchCase.Checked;
  1058. d.Add (ckbMatchCase);
  1059. var ckbMatchWholeWord = new CheckBox
  1060. {
  1061. X = 0, Y = Pos.Top (ckbMatchCase) + 1, Checked = _matchWholeWord, Text = "Match _whole word"
  1062. };
  1063. ckbMatchWholeWord.Toggled += (s, e) => _matchWholeWord = (bool)ckbMatchWholeWord.Checked;
  1064. d.Add (ckbMatchWholeWord);
  1065. d.Width = lblWidth + txtToFind.Width + btnFindNext.Width + 2;
  1066. d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4;
  1067. return d;
  1068. }
  1069. private bool Save ()
  1070. {
  1071. if (_fileName != null)
  1072. {
  1073. // FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
  1074. // As a result files saved on Windows and then read back will show invalid chars.
  1075. return SaveFile (Win.Title, _fileName);
  1076. }
  1077. return SaveAs ();
  1078. }
  1079. private bool SaveAs ()
  1080. {
  1081. List<IAllowedType> aTypes = new ()
  1082. {
  1083. new AllowedType ("Text Files", ".txt", ".bin", ".xml"), new AllowedTypeAny ()
  1084. };
  1085. var sd = new SaveDialog { Title = "Save file", AllowedTypes = aTypes };
  1086. sd.Path = Win.Title;
  1087. Application.Run (sd);
  1088. bool canceled = sd.Canceled;
  1089. string path = sd.Path;
  1090. string fileName = sd.FileName;
  1091. sd.Dispose ();
  1092. if (!canceled)
  1093. {
  1094. if (File.Exists (path))
  1095. {
  1096. if (MessageBox.Query (
  1097. "Save File",
  1098. "File already exists. Overwrite any way?",
  1099. "No",
  1100. "Ok"
  1101. )
  1102. == 1)
  1103. {
  1104. return SaveFile (fileName, path);
  1105. }
  1106. _saved = false;
  1107. return _saved;
  1108. }
  1109. return SaveFile (fileName, path);
  1110. }
  1111. _saved = false;
  1112. return _saved;
  1113. }
  1114. private bool SaveFile (string title, string file)
  1115. {
  1116. try
  1117. {
  1118. Win.Title = title;
  1119. _fileName = file;
  1120. File.WriteAllText (_fileName, _textView.Text);
  1121. _originalText = Encoding.Unicode.GetBytes (_textView.Text);
  1122. _saved = true;
  1123. _textView.ClearHistoryChanges ();
  1124. MessageBox.Query ("Save File", "File was successfully saved.", "Ok");
  1125. }
  1126. catch (Exception ex)
  1127. {
  1128. MessageBox.ErrorQuery ("Error", ex.Message, "Ok");
  1129. return false;
  1130. }
  1131. return true;
  1132. }
  1133. private void SelectAll () { _textView.SelectAll (); }
  1134. private void SetFindText ()
  1135. {
  1136. _textToFind = !string.IsNullOrEmpty (_textView.SelectedText) ? _textView.SelectedText :
  1137. string.IsNullOrEmpty (_textToFind) ? "" : _textToFind;
  1138. _textToReplace = string.IsNullOrEmpty (_textToReplace) ? "" : _textToReplace;
  1139. }
  1140. }