TextField.cs 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909
  1. using System.Data;
  2. using System.Globalization;
  3. using Terminal.Gui.Resources;
  4. namespace Terminal.Gui;
  5. /// <summary>Single-line text entry <see cref="View"/></summary>
  6. /// <remarks>The <see cref="TextField"/> <see cref="View"/> provides editing functionality and mouse support.</remarks>
  7. public class TextField : View
  8. {
  9. private readonly HistoryText _historyText;
  10. private CultureInfo _currentCulture;
  11. private int _cursorPosition;
  12. private bool _isButtonPressed;
  13. private bool _isButtonReleased;
  14. private bool _isDrawing;
  15. private int _preTextChangedCursorPos;
  16. private int _selectedStart; // -1 represents there is no text selection.
  17. private string _selectedText;
  18. private int _start;
  19. private List<Rune> _text;
  20. /// <summary>
  21. /// Initializes a new instance of the <see cref="TextField"/> class.
  22. /// </summary>
  23. public TextField ()
  24. {
  25. _historyText = new HistoryText ();
  26. _isButtonReleased = true;
  27. _selectedStart = -1;
  28. _text = new List<Rune> ();
  29. CaptionColor = new Color (Color.DarkGray);
  30. ReadOnly = false;
  31. Autocomplete = new TextFieldAutocomplete ();
  32. Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
  33. CanFocus = true;
  34. CursorVisibility = CursorVisibility.Default;
  35. Used = true;
  36. WantMousePositionReports = true;
  37. // By default, disable hotkeys (in case someome sets Title)
  38. HotKeySpecifier = new ('\xffff');
  39. _historyText.ChangeText += HistoryText_ChangeText;
  40. Initialized += TextField_Initialized;
  41. Added += TextField_Added;
  42. Removed += TextField_Removed;
  43. // Things this view knows how to do
  44. AddCommand (
  45. Command.DeleteCharRight,
  46. () =>
  47. {
  48. DeleteCharRight ();
  49. return true;
  50. }
  51. );
  52. AddCommand (
  53. Command.DeleteCharLeft,
  54. () =>
  55. {
  56. DeleteCharLeft (false);
  57. return true;
  58. }
  59. );
  60. AddCommand (
  61. Command.LeftHomeExtend,
  62. () =>
  63. {
  64. MoveHomeExtend ();
  65. return true;
  66. }
  67. );
  68. AddCommand (
  69. Command.RightEndExtend,
  70. () =>
  71. {
  72. MoveEndExtend ();
  73. return true;
  74. }
  75. );
  76. AddCommand (
  77. Command.LeftHome,
  78. () =>
  79. {
  80. MoveHome ();
  81. return true;
  82. }
  83. );
  84. AddCommand (
  85. Command.LeftExtend,
  86. () =>
  87. {
  88. MoveLeftExtend ();
  89. return true;
  90. }
  91. );
  92. AddCommand (
  93. Command.RightExtend,
  94. () =>
  95. {
  96. MoveRightExtend ();
  97. return true;
  98. }
  99. );
  100. AddCommand (
  101. Command.WordLeftExtend,
  102. () =>
  103. {
  104. MoveWordLeftExtend ();
  105. return true;
  106. }
  107. );
  108. AddCommand (
  109. Command.WordRightExtend,
  110. () =>
  111. {
  112. MoveWordRightExtend ();
  113. return true;
  114. }
  115. );
  116. AddCommand (Command.Left, () => MoveLeft ());
  117. AddCommand (
  118. Command.RightEnd,
  119. () =>
  120. {
  121. MoveEnd ();
  122. return true;
  123. }
  124. );
  125. AddCommand (Command.Right, () => MoveRight ());
  126. AddCommand (
  127. Command.CutToEndLine,
  128. () =>
  129. {
  130. KillToEnd ();
  131. return true;
  132. }
  133. );
  134. AddCommand (
  135. Command.CutToStartLine,
  136. () =>
  137. {
  138. KillToStart ();
  139. return true;
  140. }
  141. );
  142. AddCommand (
  143. Command.Undo,
  144. () =>
  145. {
  146. Undo ();
  147. return true;
  148. }
  149. );
  150. AddCommand (
  151. Command.Redo,
  152. () =>
  153. {
  154. Redo ();
  155. return true;
  156. }
  157. );
  158. AddCommand (
  159. Command.WordLeft,
  160. () =>
  161. {
  162. MoveWordLeft ();
  163. return true;
  164. }
  165. );
  166. AddCommand (
  167. Command.WordRight,
  168. () =>
  169. {
  170. MoveWordRight ();
  171. return true;
  172. }
  173. );
  174. AddCommand (
  175. Command.KillWordForwards,
  176. () =>
  177. {
  178. KillWordForwards ();
  179. return true;
  180. }
  181. );
  182. AddCommand (
  183. Command.KillWordBackwards,
  184. () =>
  185. {
  186. KillWordBackwards ();
  187. return true;
  188. }
  189. );
  190. AddCommand (
  191. Command.ToggleOverwrite,
  192. () =>
  193. {
  194. SetOverwrite (!Used);
  195. return true;
  196. }
  197. );
  198. AddCommand (
  199. Command.EnableOverwrite,
  200. () =>
  201. {
  202. SetOverwrite (true);
  203. return true;
  204. }
  205. );
  206. AddCommand (
  207. Command.DisableOverwrite,
  208. () =>
  209. {
  210. SetOverwrite (false);
  211. return true;
  212. }
  213. );
  214. AddCommand (
  215. Command.Copy,
  216. () =>
  217. {
  218. Copy ();
  219. return true;
  220. }
  221. );
  222. AddCommand (
  223. Command.Cut,
  224. () =>
  225. {
  226. Cut ();
  227. return true;
  228. }
  229. );
  230. AddCommand (
  231. Command.Paste,
  232. () =>
  233. {
  234. Paste ();
  235. return true;
  236. }
  237. );
  238. AddCommand (
  239. Command.SelectAll,
  240. () =>
  241. {
  242. SelectAll ();
  243. return true;
  244. }
  245. );
  246. AddCommand (
  247. Command.DeleteAll,
  248. () =>
  249. {
  250. DeleteAll ();
  251. return true;
  252. }
  253. );
  254. AddCommand (
  255. Command.ShowContextMenu,
  256. () =>
  257. {
  258. ShowContextMenu ();
  259. return true;
  260. }
  261. );
  262. // By Default pressing ENTER should be ignored (OnAccept will return false or null). Only cancel if the
  263. // event was fired and set Cancel = true.
  264. AddCommand (Command.Accept, () => OnAccept () == false);
  265. // Default keybindings for this view
  266. // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
  267. KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
  268. KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
  269. KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
  270. KeyBindings.Add (Key.Home.WithShift, Command.LeftHomeExtend);
  271. KeyBindings.Add (Key.Home.WithShift.WithCtrl, Command.LeftHomeExtend);
  272. KeyBindings.Add (Key.A.WithShift.WithCtrl, Command.LeftHomeExtend);
  273. KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
  274. KeyBindings.Add (Key.End.WithShift.WithCtrl, Command.RightEndExtend);
  275. KeyBindings.Add (Key.E.WithShift.WithCtrl, Command.RightEndExtend);
  276. KeyBindings.Add (Key.Home, Command.LeftHome);
  277. KeyBindings.Add (Key.Home.WithCtrl, Command.LeftHome);
  278. KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
  279. KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
  280. KeyBindings.Add (Key.CursorUp.WithShift, Command.LeftExtend);
  281. KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
  282. KeyBindings.Add (Key.CursorDown.WithShift, Command.RightExtend);
  283. KeyBindings.Add (Key.CursorLeft.WithShift.WithCtrl, Command.WordLeftExtend);
  284. KeyBindings.Add (Key.CursorUp.WithShift.WithCtrl, Command.WordLeftExtend);
  285. KeyBindings.Add (Key.CursorRight.WithShift.WithCtrl, Command.WordRightExtend);
  286. KeyBindings.Add (Key.CursorDown.WithShift.WithCtrl, Command.WordRightExtend);
  287. KeyBindings.Add (Key.CursorLeft, Command.Left);
  288. KeyBindings.Add (Key.B.WithCtrl, Command.Left);
  289. KeyBindings.Add (Key.End, Command.RightEnd);
  290. KeyBindings.Add (Key.End.WithCtrl, Command.RightEnd);
  291. KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
  292. KeyBindings.Add (Key.CursorRight, Command.Right);
  293. KeyBindings.Add (Key.F.WithCtrl, Command.Right);
  294. KeyBindings.Add (Key.K.WithCtrl, Command.CutToEndLine);
  295. KeyBindings.Add (Key.K.WithCtrl.WithShift, Command.CutToStartLine);
  296. KeyBindings.Add (Key.Z.WithCtrl, Command.Undo);
  297. KeyBindings.Add (Key.Y.WithCtrl, Command.Redo);
  298. KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.WordLeft);
  299. KeyBindings.Add (Key.CursorUp.WithCtrl, Command.WordLeft);
  300. KeyBindings.Add (Key.CursorRight.WithCtrl, Command.WordRight);
  301. KeyBindings.Add (Key.CursorDown.WithCtrl, Command.WordRight);
  302. #if UNIX_KEY_BINDINGS
  303. KeyBindings.Add (Key.F.WithShift.WithAlt, Command.WordRightExtend);
  304. KeyBindings.Add (Key.K.WithAlt, Command.CutToStartLine);
  305. KeyBindings.Add (Key.B.WithShift.WithAlt, Command.WordLeftExtend);
  306. KeyBindings.Add (Key.B.WithAlt, Command.WordLeft);
  307. KeyBindings.Add (Key.F.WithAlt, Command.WordRight);
  308. KeyBindings.Add (Key.Backspace.WithAlt, Command.Undo);
  309. #endif
  310. KeyBindings.Add (Key.Delete.WithCtrl, Command.KillWordForwards);
  311. KeyBindings.Add (Key.Backspace.WithCtrl, Command.KillWordBackwards);
  312. KeyBindings.Add (Key.InsertChar, Command.ToggleOverwrite);
  313. KeyBindings.Add (Key.C.WithCtrl, Command.Copy);
  314. KeyBindings.Add (Key.X.WithCtrl, Command.Cut);
  315. KeyBindings.Add (Key.V.WithCtrl, Command.Paste);
  316. KeyBindings.Add (Key.T.WithCtrl, Command.SelectAll);
  317. KeyBindings.Add (Key.R.WithCtrl, Command.DeleteAll);
  318. KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
  319. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  320. ContextMenu = new ContextMenu { Host = this };
  321. ContextMenu.KeyChanged += ContextMenu_KeyChanged;
  322. KeyBindings.Add (ContextMenu.Key, KeyBindingScope.HotKey, Command.ShowContextMenu);
  323. KeyBindings.Add (Key.Enter, Command.Accept);
  324. }
  325. /// <summary>
  326. /// Provides autocomplete context menu based on suggestions at the current cursor position. Configure
  327. /// <see cref="ISuggestionGenerator"/> to enable this feature.
  328. /// </summary>
  329. public IAutocomplete Autocomplete { get; set; }
  330. /// <summary>
  331. /// Gets or sets the text to render in control when no value has been entered yet and the <see cref="View"/> does
  332. /// not yet have input focus.
  333. /// </summary>
  334. public string Caption { get; set; }
  335. /// <summary>Gets or sets the foreground <see cref="Color"/> to use when rendering <see cref="Caption"/>.</summary>
  336. public Color CaptionColor { get; set; }
  337. /// <summary>Get the <see cref="ContextMenu"/> for this view.</summary>
  338. public ContextMenu ContextMenu { get; }
  339. /// <summary>Sets or gets the current cursor position.</summary>
  340. public virtual int CursorPosition
  341. {
  342. get => _cursorPosition;
  343. set
  344. {
  345. if (value < 0)
  346. {
  347. _cursorPosition = 0;
  348. }
  349. else if (value > _text.Count)
  350. {
  351. _cursorPosition = _text.Count;
  352. }
  353. else
  354. {
  355. _cursorPosition = value;
  356. }
  357. PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
  358. }
  359. }
  360. /// <summary>
  361. /// Indicates whatever the text has history changes or not. <see langword="true"/> if the text has history changes
  362. /// <see langword="false"/> otherwise.
  363. /// </summary>
  364. public bool HasHistoryChanges => _historyText.HasHistoryChanges;
  365. /// <summary>
  366. /// Indicates whatever the text was changed or not. <see langword="true"/> if the text was changed
  367. /// <see langword="false"/> otherwise.
  368. /// </summary>
  369. public bool IsDirty => _historyText.IsDirty (Text);
  370. /// <summary>If set to true its not allow any changes in the text.</summary>
  371. public bool ReadOnly { get; set; }
  372. /// <summary>Gets the left offset position.</summary>
  373. public int ScrollOffset { get; private set; }
  374. /// <summary>
  375. /// Sets the secret property.
  376. /// <remarks>This makes the text entry suitable for entering passwords.</remarks>
  377. /// </summary>
  378. public bool Secret { get; set; }
  379. /// <summary>Length of the selected text.</summary>
  380. public int SelectedLength { get; private set; }
  381. /// <summary>Start position of the selected text.</summary>
  382. public int SelectedStart
  383. {
  384. get => _selectedStart;
  385. set
  386. {
  387. if (value < -1)
  388. {
  389. _selectedStart = -1;
  390. }
  391. else if (value > _text.Count)
  392. {
  393. _selectedStart = _text.Count;
  394. }
  395. else
  396. {
  397. _selectedStart = value;
  398. }
  399. PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
  400. }
  401. }
  402. /// <summary>The selected text.</summary>
  403. public string SelectedText
  404. {
  405. get => Secret ? null : _selectedText;
  406. private set => _selectedText = value;
  407. }
  408. /// <summary>Sets or gets the text held by the view.</summary>
  409. public new string Text
  410. {
  411. get => StringExtensions.ToString (_text);
  412. set
  413. {
  414. var oldText = StringExtensions.ToString (_text);
  415. if (oldText == value)
  416. {
  417. return;
  418. }
  419. string newText = value.Replace ("\t", "").Split ("\n") [0];
  420. CancelEventArgs<string> args = new (ref oldText, ref newText);
  421. OnTextChanging (args);
  422. if (args.Cancel)
  423. {
  424. if (_cursorPosition > _text.Count)
  425. {
  426. _cursorPosition = _text.Count;
  427. }
  428. return;
  429. }
  430. ClearAllSelection ();
  431. // Note we use NewValue here; TextChanging subscribers may have changed it
  432. _text = args.NewValue.EnumerateRunes ().ToList ();
  433. if (!Secret && !_historyText.IsFromHistory)
  434. {
  435. _historyText.Add (
  436. new List<List<RuneCell>> { TextModel.ToRuneCellList (oldText) },
  437. new Point (_cursorPosition, 0)
  438. );
  439. _historyText.Add (
  440. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  441. new Point (_cursorPosition, 0),
  442. HistoryText.LineStatus.Replaced
  443. );
  444. }
  445. OnTextChanged ();
  446. ProcessAutocomplete ();
  447. if (_cursorPosition > _text.Count)
  448. {
  449. _cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
  450. }
  451. Adjust ();
  452. SetNeedsDisplay ();
  453. }
  454. }
  455. /// <summary>
  456. /// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so
  457. /// new input should be appended at the cursor position, rather than clearing the entry
  458. /// </summary>
  459. public bool Used { get; set; }
  460. /// <summary>Clear the selected text.</summary>
  461. public void ClearAllSelection ()
  462. {
  463. if (_selectedStart == -1 && SelectedLength == 0 && string.IsNullOrEmpty (_selectedText))
  464. {
  465. return;
  466. }
  467. _selectedStart = -1;
  468. SelectedLength = 0;
  469. _selectedText = null;
  470. _start = 0;
  471. SelectedLength = 0;
  472. SetNeedsDisplay ();
  473. }
  474. /// <summary>Allows clearing the <see cref="HistoryText.HistoryTextItemEventArgs"/> items updating the original text.</summary>
  475. public void ClearHistoryChanges () { _historyText.Clear (Text); }
  476. /// <summary>Copy the selected text to the clipboard.</summary>
  477. public virtual void Copy ()
  478. {
  479. if (Secret || SelectedLength == 0)
  480. {
  481. return;
  482. }
  483. Clipboard.Contents = SelectedText;
  484. }
  485. /// <summary>Cut the selected text to the clipboard.</summary>
  486. public virtual void Cut ()
  487. {
  488. if (ReadOnly || Secret || SelectedLength == 0)
  489. {
  490. return;
  491. }
  492. Clipboard.Contents = SelectedText;
  493. List<Rune> newText = DeleteSelectedText ();
  494. Text = StringExtensions.ToString (newText);
  495. Adjust ();
  496. }
  497. /// <summary>Deletes all text.</summary>
  498. public void DeleteAll ()
  499. {
  500. if (_text.Count == 0)
  501. {
  502. return;
  503. }
  504. _selectedStart = 0;
  505. MoveEndExtend ();
  506. DeleteCharLeft (false);
  507. SetNeedsDisplay ();
  508. }
  509. /// <summary>Deletes the character to the left.</summary>
  510. /// <param name="usePreTextChangedCursorPos">
  511. /// If set to <see langword="true">true</see> use the cursor position cached ;
  512. /// otherwise use <see cref="CursorPosition"/>. use .
  513. /// </param>
  514. public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
  515. {
  516. if (ReadOnly)
  517. {
  518. return;
  519. }
  520. _historyText.Add (
  521. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  522. new Point (_cursorPosition, 0)
  523. );
  524. if (SelectedLength == 0)
  525. {
  526. if (_cursorPosition == 0)
  527. {
  528. return;
  529. }
  530. if (!usePreTextChangedCursorPos)
  531. {
  532. _preTextChangedCursorPos = _cursorPosition;
  533. }
  534. _cursorPosition--;
  535. if (_preTextChangedCursorPos < _text.Count)
  536. {
  537. SetText (
  538. _text.GetRange (0, _preTextChangedCursorPos - 1)
  539. .Concat (
  540. _text.GetRange (
  541. _preTextChangedCursorPos,
  542. _text.Count - _preTextChangedCursorPos
  543. )
  544. )
  545. );
  546. }
  547. else
  548. {
  549. SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
  550. }
  551. Adjust ();
  552. }
  553. else
  554. {
  555. List<Rune> newText = DeleteSelectedText ();
  556. Text = StringExtensions.ToString (newText);
  557. Adjust ();
  558. }
  559. }
  560. /// <summary>Deletes the character to the right.</summary>
  561. public virtual void DeleteCharRight ()
  562. {
  563. if (ReadOnly)
  564. {
  565. return;
  566. }
  567. _historyText.Add (
  568. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  569. new Point (_cursorPosition, 0)
  570. );
  571. if (SelectedLength == 0)
  572. {
  573. if (_text.Count == 0 || _text.Count == _cursorPosition)
  574. {
  575. return;
  576. }
  577. SetText (
  578. _text.GetRange (0, _cursorPosition)
  579. .Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1)))
  580. );
  581. Adjust ();
  582. }
  583. else
  584. {
  585. List<Rune> newText = DeleteSelectedText ();
  586. Text = StringExtensions.ToString (newText);
  587. Adjust ();
  588. }
  589. }
  590. /// <inheritdoc/>
  591. public override Attribute GetNormalColor ()
  592. {
  593. ColorScheme cs = ColorScheme;
  594. if (ColorScheme is null)
  595. {
  596. cs = new ColorScheme ();
  597. }
  598. return Enabled ? cs.Focus : cs.Disabled;
  599. }
  600. /// <summary>
  601. /// Inserts the given <paramref name="toAdd"/> text at the current cursor position exactly as if the user had just
  602. /// typed it
  603. /// </summary>
  604. /// <param name="toAdd">Text to add</param>
  605. /// <param name="useOldCursorPos">Use the previous cursor position.</param>
  606. public void InsertText (string toAdd, bool useOldCursorPos = true)
  607. {
  608. foreach (char ch in toAdd)
  609. {
  610. Key key;
  611. try
  612. {
  613. key = ch;
  614. }
  615. catch (Exception)
  616. {
  617. throw new ArgumentException (
  618. $"Cannot insert character '{ch}' because it does not map to a Key"
  619. );
  620. }
  621. InsertText (key, useOldCursorPos);
  622. }
  623. }
  624. /// <summary>Deletes word backwards.</summary>
  625. public virtual void KillWordBackwards ()
  626. {
  627. ClearAllSelection ();
  628. (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
  629. if (newPos is null)
  630. {
  631. return;
  632. }
  633. if (newPos.Value.col != -1)
  634. {
  635. SetText (
  636. _text.GetRange (0, newPos.Value.col)
  637. .Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition))
  638. );
  639. _cursorPosition = newPos.Value.col;
  640. }
  641. Adjust ();
  642. }
  643. /// <summary>Deletes word forwards.</summary>
  644. public virtual void KillWordForwards ()
  645. {
  646. ClearAllSelection ();
  647. (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
  648. if (newPos is null)
  649. {
  650. return;
  651. }
  652. if (newPos.Value.col != -1)
  653. {
  654. SetText (
  655. _text.GetRange (0, _cursorPosition)
  656. .Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col))
  657. );
  658. }
  659. Adjust ();
  660. }
  661. /// <inheritdoc/>
  662. protected internal override bool OnMouseEvent (MouseEvent ev)
  663. {
  664. if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed)
  665. && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition)
  666. && !ev.Flags.HasFlag (MouseFlags.Button1Released)
  667. && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
  668. && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)
  669. && !ev.Flags.HasFlag (ContextMenu.MouseFlags))
  670. {
  671. return base.OnMouseEvent (ev);
  672. }
  673. if (!CanFocus)
  674. {
  675. return true;
  676. }
  677. if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition)
  678. {
  679. SetFocus ();
  680. }
  681. // Give autocomplete first opportunity to respond to mouse clicks
  682. if (SelectedLength == 0 && Autocomplete.OnMouseEvent (ev, true))
  683. {
  684. return true;
  685. }
  686. if (ev.Flags == MouseFlags.Button1Pressed)
  687. {
  688. EnsureHasFocus ();
  689. PositionCursor (ev);
  690. if (_isButtonReleased)
  691. {
  692. ClearAllSelection ();
  693. }
  694. _isButtonReleased = true;
  695. _isButtonPressed = true;
  696. }
  697. else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed)
  698. {
  699. int x = PositionCursor (ev);
  700. _isButtonReleased = false;
  701. PrepareSelection (x);
  702. if (Application.MouseGrabView is null)
  703. {
  704. Application.GrabMouse (this);
  705. }
  706. }
  707. else if (ev.Flags == MouseFlags.Button1Released)
  708. {
  709. _isButtonReleased = true;
  710. _isButtonPressed = false;
  711. Application.UngrabMouse ();
  712. }
  713. else if (ev.Flags == MouseFlags.Button1DoubleClicked)
  714. {
  715. EnsureHasFocus ();
  716. int x = PositionCursor (ev);
  717. int sbw = x;
  718. if (x == _text.Count
  719. || (x > 0 && (char)_text [x - 1].Value != ' ')
  720. || (x > 0 && (char)_text [x].Value == ' '))
  721. {
  722. (int col, int row)? newPosBw = GetModel ().WordBackward (x, 0);
  723. if (newPosBw is null)
  724. {
  725. return true;
  726. }
  727. sbw = newPosBw.Value.col;
  728. }
  729. if (sbw != -1)
  730. {
  731. x = sbw;
  732. PositionCursor (x);
  733. }
  734. (int col, int row)? newPosFw = GetModel ().WordForward (x, 0);
  735. if (newPosFw is null)
  736. {
  737. return true;
  738. }
  739. ClearAllSelection ();
  740. if (newPosFw.Value.col != -1 && sbw != -1)
  741. {
  742. _cursorPosition = newPosFw.Value.col;
  743. }
  744. PrepareSelection (sbw, newPosFw.Value.col - sbw);
  745. }
  746. else if (ev.Flags == MouseFlags.Button1TripleClicked)
  747. {
  748. EnsureHasFocus ();
  749. PositionCursor (0);
  750. ClearAllSelection ();
  751. PrepareSelection (0, _text.Count);
  752. }
  753. else if (ev.Flags == ContextMenu.MouseFlags)
  754. {
  755. ShowContextMenu ();
  756. }
  757. //SetNeedsDisplay ();
  758. return true;
  759. void EnsureHasFocus ()
  760. {
  761. if (!HasFocus)
  762. {
  763. SetFocus ();
  764. }
  765. }
  766. }
  767. /// <summary>Moves cursor to the end of the typed text.</summary>
  768. public void MoveEnd ()
  769. {
  770. ClearAllSelection ();
  771. _cursorPosition = _text.Count;
  772. Adjust ();
  773. }
  774. /// <inheritdoc/>
  775. public override void OnDrawContent (Rectangle viewport)
  776. {
  777. _isDrawing = true;
  778. var selColor = new Attribute (GetFocusColor ().Background, GetFocusColor ().Foreground);
  779. SetSelectedStartSelectedLength ();
  780. Driver?.SetAttribute (GetNormalColor ());
  781. Move (0, 0);
  782. int p = ScrollOffset;
  783. var col = 0;
  784. int width = Frame.Width + OffSetBackground ();
  785. int tcount = _text.Count;
  786. Attribute roc = GetReadOnlyColor ();
  787. for (int idx = p; idx < tcount; idx++)
  788. {
  789. Rune rune = _text [idx];
  790. int cols = rune.GetColumns ();
  791. if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly)
  792. {
  793. Driver?.SetAttribute (selColor);
  794. }
  795. else if (ReadOnly)
  796. {
  797. Driver?.SetAttribute (
  798. idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
  799. ? selColor
  800. : roc
  801. );
  802. }
  803. else if (!HasFocus && Enabled)
  804. {
  805. Driver?.SetAttribute (GetFocusColor ());
  806. }
  807. else if (!Enabled)
  808. {
  809. Driver?.SetAttribute (roc);
  810. }
  811. else
  812. {
  813. Driver?.SetAttribute (
  814. idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
  815. ? selColor
  816. : ColorScheme.Focus
  817. );
  818. }
  819. if (col + cols <= width)
  820. {
  821. Driver?.AddRune (Secret ? Glyphs.Dot : rune);
  822. }
  823. if (!TextModel.SetCol (ref col, width, cols))
  824. {
  825. break;
  826. }
  827. if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width)
  828. {
  829. break;
  830. }
  831. }
  832. Driver.SetAttribute (GetFocusColor ());
  833. for (int i = col; i < width; i++)
  834. {
  835. Driver.AddRune ((Rune)' ');
  836. }
  837. PositionCursor ();
  838. RenderCaption ();
  839. DrawAutocomplete ();
  840. _isDrawing = false;
  841. }
  842. /// <inheritdoc/>
  843. public override bool? OnInvokingKeyBindings (Key a, KeyBindingScope scope)
  844. {
  845. // Give autocomplete first opportunity to respond to key presses
  846. if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a))
  847. {
  848. return true;
  849. }
  850. return base.OnInvokingKeyBindings (a, scope);
  851. }
  852. /// <inheritdoc/>
  853. protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
  854. {
  855. if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
  856. {
  857. Application.UngrabMouse ();
  858. }
  859. //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
  860. // ClearAllSelection ();
  861. return;
  862. }
  863. /// TODO: Flush out these docs
  864. /// <summary>
  865. /// Processes key presses for the <see cref="TextField"/>.
  866. /// <remarks>
  867. /// The <see cref="TextField"/> control responds to the following keys:
  868. /// <list type="table">
  869. /// <listheader>
  870. /// <term>Keys</term> <description>Function</description>
  871. /// </listheader>
  872. /// <item>
  873. /// <term><see cref="Key.Delete"/>, <see cref="Key.Backspace"/></term>
  874. /// <description>Deletes the character before cursor.</description>
  875. /// </item>
  876. /// </list>
  877. /// </remarks>
  878. /// </summary>
  879. /// <param name="a"></param>
  880. /// <returns></returns>
  881. public override bool OnProcessKeyDown (Key a)
  882. {
  883. // Remember the cursor position because the new calculated cursor position is needed
  884. // to be set BEFORE the TextChanged event is triggered.
  885. // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
  886. _preTextChangedCursorPos = _cursorPosition;
  887. // Ignore other control characters.
  888. if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask))
  889. {
  890. return false;
  891. }
  892. if (ReadOnly)
  893. {
  894. return true;
  895. }
  896. InsertText (a, true);
  897. return true;
  898. }
  899. /// <summary>Virtual method that invoke the <see cref="TextChanging"/> event if it's defined.</summary>
  900. /// <param name="args">The event arguments.</param>
  901. /// <returns><see langword="true"/> if the event was cancelled.</returns>
  902. public bool OnTextChanging (CancelEventArgs<string> args)
  903. {
  904. TextChanging?.Invoke (this, args);
  905. return args.Cancel;
  906. }
  907. /// <summary>Paste the selected text from the clipboard.</summary>
  908. public virtual void Paste ()
  909. {
  910. if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents))
  911. {
  912. return;
  913. }
  914. SetSelectedStartSelectedLength ();
  915. int selStart = _start == -1 ? CursorPosition : _start;
  916. string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
  917. Text = StringExtensions.ToString (_text.GetRange (0, selStart))
  918. + cbTxt
  919. + StringExtensions.ToString (
  920. _text.GetRange (
  921. selStart + SelectedLength,
  922. _text.Count - (selStart + SelectedLength)
  923. )
  924. );
  925. _cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count);
  926. ClearAllSelection ();
  927. SetNeedsDisplay ();
  928. Adjust ();
  929. }
  930. /// <summary>Sets the cursor position.</summary>
  931. public override Point? PositionCursor ()
  932. {
  933. ProcessAutocomplete ();
  934. var col = 0;
  935. for (int idx = ScrollOffset < 0 ? 0 : ScrollOffset; idx < _text.Count; idx++)
  936. {
  937. if (idx == _cursorPosition)
  938. {
  939. break;
  940. }
  941. int cols = _text [idx].GetColumns ();
  942. TextModel.SetCol (ref col, Frame.Width - 1, cols);
  943. }
  944. int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
  945. Move (pos, 0);
  946. return new Point (pos, 0);
  947. }
  948. /// <summary>Redoes the latest changes.</summary>
  949. public void Redo ()
  950. {
  951. if (ReadOnly)
  952. {
  953. return;
  954. }
  955. _historyText.Redo ();
  956. }
  957. /// <summary>Selects all text.</summary>
  958. public void SelectAll ()
  959. {
  960. if (_text.Count == 0)
  961. {
  962. return;
  963. }
  964. _selectedStart = 0;
  965. MoveEndExtend ();
  966. SetNeedsDisplay ();
  967. }
  968. ///// <summary>
  969. ///// Changed event, raised when the text has changed.
  970. ///// <remarks>
  971. ///// This event is raised when the <see cref="Text"/> changes. The passed <see cref="EventArgs"/> is a
  972. ///// <see cref="string"/> containing the old value.
  973. ///// </remarks>
  974. ///// </summary>
  975. //public event EventHandler<StateEventArgs<string>> TextChanged;
  976. /// <summary>Changing event, raised before the <see cref="Text"/> changes and can be canceled or changing the new text.</summary>
  977. public event EventHandler<CancelEventArgs<string>> TextChanging;
  978. /// <summary>Undoes the latest changes.</summary>
  979. public void Undo ()
  980. {
  981. if (ReadOnly)
  982. {
  983. return;
  984. }
  985. _historyText.Undo ();
  986. }
  987. /// <summary>
  988. /// Returns <see langword="true"/> if the current cursor position is at the end of the <see cref="Text"/>. This
  989. /// includes when it is empty.
  990. /// </summary>
  991. /// <returns></returns>
  992. internal bool CursorIsAtEnd () { return CursorPosition == Text.Length; }
  993. /// <summary>Returns <see langword="true"/> if the current cursor position is at the start of the <see cref="TextField"/>.</summary>
  994. /// <returns></returns>
  995. internal bool CursorIsAtStart () { return CursorPosition <= 0; }
  996. private void Adjust ()
  997. {
  998. if (!IsAdded)
  999. {
  1000. return;
  1001. }
  1002. // TODO: This is a lame prototype proving it should be easy for TextField to
  1003. // TODO: support Width = Dim.Auto (DimAutoStyle: Content).
  1004. //SetContentSize(new (TextModel.DisplaySize (_text).size, 1));
  1005. int offB = OffSetBackground ();
  1006. bool need = NeedsDisplay || !Used;
  1007. if (_cursorPosition < ScrollOffset)
  1008. {
  1009. ScrollOffset = _cursorPosition;
  1010. need = true;
  1011. }
  1012. else if (Frame.Width > 0
  1013. && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0
  1014. || TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB))
  1015. {
  1016. ScrollOffset = Math.Max (
  1017. TextModel.CalculateLeftColumn (
  1018. _text,
  1019. ScrollOffset,
  1020. _cursorPosition,
  1021. Frame.Width + offB
  1022. ),
  1023. 0
  1024. );
  1025. need = true;
  1026. }
  1027. if (need)
  1028. {
  1029. SetNeedsDisplay ();
  1030. }
  1031. else
  1032. {
  1033. PositionCursor ();
  1034. }
  1035. }
  1036. private MenuBarItem BuildContextMenuBarItem ()
  1037. {
  1038. return new MenuBarItem (
  1039. new MenuItem []
  1040. {
  1041. new (
  1042. Strings.ctxSelectAll,
  1043. "",
  1044. () => SelectAll (),
  1045. null,
  1046. null,
  1047. (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)
  1048. ),
  1049. new (
  1050. Strings.ctxDeleteAll,
  1051. "",
  1052. () => DeleteAll (),
  1053. null,
  1054. null,
  1055. (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)
  1056. ),
  1057. new (
  1058. Strings.ctxCopy,
  1059. "",
  1060. () => Copy (),
  1061. null,
  1062. null,
  1063. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)
  1064. ),
  1065. new (
  1066. Strings.ctxCut,
  1067. "",
  1068. () => Cut (),
  1069. null,
  1070. null,
  1071. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)
  1072. ),
  1073. new (
  1074. Strings.ctxPaste,
  1075. "",
  1076. () => Paste (),
  1077. null,
  1078. null,
  1079. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)
  1080. ),
  1081. new (
  1082. Strings.ctxUndo,
  1083. "",
  1084. () => Undo (),
  1085. null,
  1086. null,
  1087. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)
  1088. ),
  1089. new (
  1090. Strings.ctxRedo,
  1091. "",
  1092. () => Redo (),
  1093. null,
  1094. null,
  1095. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)
  1096. )
  1097. }
  1098. );
  1099. }
  1100. private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
  1101. {
  1102. KeyBindings.ReplaceKey (e.OldKey.KeyCode, e.NewKey.KeyCode);
  1103. }
  1104. private List<Rune> DeleteSelectedText ()
  1105. {
  1106. SetSelectedStartSelectedLength ();
  1107. int selStart = SelectedStart > -1 ? _start : _cursorPosition;
  1108. string newText = StringExtensions.ToString (_text.GetRange (0, selStart))
  1109. + StringExtensions.ToString (
  1110. _text.GetRange (
  1111. selStart + SelectedLength,
  1112. _text.Count - (selStart + SelectedLength)
  1113. )
  1114. );
  1115. ClearAllSelection ();
  1116. _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
  1117. return newText.ToRuneList ();
  1118. }
  1119. private void GenerateSuggestions ()
  1120. {
  1121. List<RuneCell> currentLine = TextModel.ToRuneCellList (Text);
  1122. int cursorPosition = Math.Min (CursorPosition, currentLine.Count);
  1123. Autocomplete.Context = new AutocompleteContext (
  1124. currentLine,
  1125. cursorPosition,
  1126. Autocomplete.Context != null
  1127. ? Autocomplete.Context.Canceled
  1128. : false
  1129. );
  1130. Autocomplete.GenerateSuggestions (
  1131. Autocomplete.Context
  1132. );
  1133. }
  1134. private TextModel GetModel ()
  1135. {
  1136. var model = new TextModel ();
  1137. model.LoadString (Text);
  1138. return model;
  1139. }
  1140. private Attribute GetReadOnlyColor ()
  1141. {
  1142. ColorScheme cs = ColorScheme;
  1143. if (ColorScheme is null)
  1144. {
  1145. cs = new ColorScheme ();
  1146. }
  1147. if (cs.Disabled.Foreground == cs.Focus.Background)
  1148. {
  1149. return new Attribute (cs.Focus.Foreground, cs.Focus.Background);
  1150. }
  1151. return new Attribute (cs.Disabled.Foreground, cs.Focus.Background);
  1152. }
  1153. private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemEventArgs obj)
  1154. {
  1155. if (obj is null)
  1156. {
  1157. return;
  1158. }
  1159. Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
  1160. CursorPosition = obj.CursorPosition.X;
  1161. Adjust ();
  1162. }
  1163. private void InsertText (Key a, bool usePreTextChangedCursorPos)
  1164. {
  1165. _historyText.Add (
  1166. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  1167. new Point (_cursorPosition, 0)
  1168. );
  1169. List<Rune> newText = _text;
  1170. if (SelectedLength > 0)
  1171. {
  1172. newText = DeleteSelectedText ();
  1173. _preTextChangedCursorPos = _cursorPosition;
  1174. }
  1175. if (!usePreTextChangedCursorPos)
  1176. {
  1177. _preTextChangedCursorPos = _cursorPosition;
  1178. }
  1179. StringRuneEnumerator kbstr = a.AsRune.ToString ().EnumerateRunes ();
  1180. if (Used)
  1181. {
  1182. _cursorPosition++;
  1183. if (_cursorPosition == newText.Count + 1)
  1184. {
  1185. SetText (newText.Concat (kbstr).ToList ());
  1186. }
  1187. else
  1188. {
  1189. if (_preTextChangedCursorPos > newText.Count)
  1190. {
  1191. _preTextChangedCursorPos = newText.Count;
  1192. }
  1193. SetText (
  1194. newText.GetRange (0, _preTextChangedCursorPos)
  1195. .Concat (kbstr)
  1196. .Concat (
  1197. newText.GetRange (
  1198. _preTextChangedCursorPos,
  1199. Math.Min (
  1200. newText.Count - _preTextChangedCursorPos,
  1201. newText.Count
  1202. )
  1203. )
  1204. )
  1205. );
  1206. }
  1207. }
  1208. else
  1209. {
  1210. SetText (
  1211. newText.GetRange (0, _preTextChangedCursorPos)
  1212. .Concat (kbstr)
  1213. .Concat (
  1214. newText.GetRange (
  1215. Math.Min (_preTextChangedCursorPos + 1, newText.Count),
  1216. Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0)
  1217. )
  1218. )
  1219. );
  1220. _cursorPosition++;
  1221. }
  1222. Adjust ();
  1223. }
  1224. private void KillToEnd ()
  1225. {
  1226. if (ReadOnly)
  1227. {
  1228. return;
  1229. }
  1230. ClearAllSelection ();
  1231. if (_cursorPosition >= _text.Count)
  1232. {
  1233. return;
  1234. }
  1235. SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  1236. SetText (_text.GetRange (0, _cursorPosition));
  1237. Adjust ();
  1238. }
  1239. private void KillToStart ()
  1240. {
  1241. if (ReadOnly)
  1242. {
  1243. return;
  1244. }
  1245. ClearAllSelection ();
  1246. if (_cursorPosition == 0)
  1247. {
  1248. return;
  1249. }
  1250. SetClipboard (_text.GetRange (0, _cursorPosition));
  1251. SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  1252. _cursorPosition = 0;
  1253. Adjust ();
  1254. }
  1255. private void MoveEndExtend ()
  1256. {
  1257. if (_cursorPosition <= _text.Count)
  1258. {
  1259. int x = _cursorPosition;
  1260. _cursorPosition = _text.Count;
  1261. PrepareSelection (x, _cursorPosition - x);
  1262. }
  1263. }
  1264. private void MoveHome ()
  1265. {
  1266. ClearAllSelection ();
  1267. _cursorPosition = 0;
  1268. Adjust ();
  1269. }
  1270. private void MoveHomeExtend ()
  1271. {
  1272. if (_cursorPosition > 0)
  1273. {
  1274. int x = _cursorPosition;
  1275. _cursorPosition = 0;
  1276. PrepareSelection (x, _cursorPosition - x);
  1277. }
  1278. }
  1279. private bool MoveLeft ()
  1280. {
  1281. if (_cursorPosition > 0)
  1282. {
  1283. ClearAllSelection ();
  1284. _cursorPosition--;
  1285. Adjust ();
  1286. return true;
  1287. }
  1288. return false;
  1289. }
  1290. private void MoveLeftExtend ()
  1291. {
  1292. if (_cursorPosition > 0)
  1293. {
  1294. PrepareSelection (_cursorPosition--, -1);
  1295. }
  1296. }
  1297. private bool MoveRight ()
  1298. {
  1299. if (_cursorPosition == _text.Count)
  1300. {
  1301. return false;
  1302. }
  1303. ClearAllSelection ();
  1304. _cursorPosition++;
  1305. Adjust ();
  1306. return true;
  1307. }
  1308. private void MoveRightExtend ()
  1309. {
  1310. if (_cursorPosition < _text.Count)
  1311. {
  1312. PrepareSelection (_cursorPosition++, 1);
  1313. }
  1314. }
  1315. private void MoveWordLeft ()
  1316. {
  1317. ClearAllSelection ();
  1318. (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
  1319. if (newPos is null)
  1320. {
  1321. return;
  1322. }
  1323. if (newPos.Value.col != -1)
  1324. {
  1325. _cursorPosition = newPos.Value.col;
  1326. }
  1327. Adjust ();
  1328. }
  1329. private void MoveWordLeftExtend ()
  1330. {
  1331. if (_cursorPosition > 0)
  1332. {
  1333. int x = Math.Min (
  1334. _start > -1 && _start > _cursorPosition ? _start : _cursorPosition,
  1335. _text.Count
  1336. );
  1337. if (x > 0)
  1338. {
  1339. (int col, int row)? newPos = GetModel ().WordBackward (x, 0);
  1340. if (newPos is null)
  1341. {
  1342. return;
  1343. }
  1344. if (newPos.Value.col != -1)
  1345. {
  1346. _cursorPosition = newPos.Value.col;
  1347. }
  1348. PrepareSelection (x, newPos.Value.col - x);
  1349. }
  1350. }
  1351. }
  1352. private void MoveWordRight ()
  1353. {
  1354. ClearAllSelection ();
  1355. (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
  1356. if (newPos is null)
  1357. {
  1358. return;
  1359. }
  1360. if (newPos.Value.col != -1)
  1361. {
  1362. _cursorPosition = newPos.Value.col;
  1363. }
  1364. Adjust ();
  1365. }
  1366. private void MoveWordRightExtend ()
  1367. {
  1368. if (_cursorPosition < _text.Count)
  1369. {
  1370. int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
  1371. (int col, int row)? newPos = GetModel ().WordForward (x, 0);
  1372. if (newPos is null)
  1373. {
  1374. return;
  1375. }
  1376. if (newPos.Value.col != -1)
  1377. {
  1378. _cursorPosition = newPos.Value.col;
  1379. }
  1380. PrepareSelection (x, newPos.Value.col - x);
  1381. }
  1382. }
  1383. // BUGBUG: This assumes Frame == Viewport. It's also not clear what the intention is. For now, changed to always return 0.
  1384. private int OffSetBackground ()
  1385. {
  1386. var offB = 0;
  1387. if (SuperView?.Frame.Right - Frame.Right < 0)
  1388. {
  1389. offB = SuperView.Frame.Right - Frame.Right - 1;
  1390. }
  1391. return 0;//offB;
  1392. }
  1393. private int PositionCursor (MouseEvent ev)
  1394. {
  1395. return PositionCursor (TextModel.GetColFromX (_text, ScrollOffset, ev.Position.X), false);
  1396. }
  1397. private int PositionCursor (int x, bool getX = true)
  1398. {
  1399. int pX = x;
  1400. if (getX)
  1401. {
  1402. pX = TextModel.GetColFromX (_text, ScrollOffset, x);
  1403. }
  1404. if (ScrollOffset + pX > _text.Count)
  1405. {
  1406. _cursorPosition = _text.Count;
  1407. }
  1408. else if (ScrollOffset + pX < ScrollOffset)
  1409. {
  1410. _cursorPosition = 0;
  1411. }
  1412. else
  1413. {
  1414. _cursorPosition = ScrollOffset + pX;
  1415. }
  1416. return _cursorPosition;
  1417. }
  1418. private void PrepareSelection (int x, int direction = 0)
  1419. {
  1420. x = x + ScrollOffset < -1 ? 0 : x;
  1421. _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count
  1422. ? x
  1423. : _selectedStart;
  1424. if (_selectedStart > -1)
  1425. {
  1426. SelectedLength = Math.Abs (
  1427. x + direction <= _text.Count
  1428. ? x + direction - _selectedStart
  1429. : _text.Count - _selectedStart
  1430. );
  1431. SetSelectedStartSelectedLength ();
  1432. if (_start > -1 && SelectedLength > 0)
  1433. {
  1434. _selectedText = SelectedLength > 0
  1435. ? StringExtensions.ToString (
  1436. _text.GetRange (
  1437. _start < 0 ? 0 : _start,
  1438. SelectedLength > _text.Count
  1439. ? _text.Count
  1440. : SelectedLength
  1441. )
  1442. )
  1443. : "";
  1444. if (ScrollOffset > _start)
  1445. {
  1446. ScrollOffset = _start;
  1447. }
  1448. }
  1449. else if (_start > -1 && SelectedLength == 0)
  1450. {
  1451. _selectedText = null;
  1452. }
  1453. SetNeedsDisplay ();
  1454. }
  1455. else if (SelectedLength > 0 || _selectedText is { })
  1456. {
  1457. ClearAllSelection ();
  1458. }
  1459. Adjust ();
  1460. }
  1461. private void ProcessAutocomplete ()
  1462. {
  1463. if (_isDrawing)
  1464. {
  1465. return;
  1466. }
  1467. if (SelectedLength > 0)
  1468. {
  1469. return;
  1470. }
  1471. GenerateSuggestions ();
  1472. }
  1473. private void DrawAutocomplete ()
  1474. {
  1475. if (SelectedLength > 0)
  1476. {
  1477. return;
  1478. }
  1479. if (Autocomplete?.Context == null)
  1480. {
  1481. return;
  1482. }
  1483. var renderAt = new Point (
  1484. Autocomplete.Context.CursorPosition,
  1485. 0
  1486. );
  1487. Autocomplete.RenderOverlay (renderAt);
  1488. }
  1489. private void RenderCaption ()
  1490. {
  1491. if (HasFocus
  1492. || Caption == null
  1493. || Caption.Length == 0
  1494. || Text?.Length > 0)
  1495. {
  1496. return;
  1497. }
  1498. var color = new Attribute (CaptionColor, GetNormalColor ().Background);
  1499. Driver.SetAttribute (color);
  1500. Move (0, 0);
  1501. string render = Caption;
  1502. if (render.GetColumns () > Viewport.Width)
  1503. {
  1504. render = render [..Viewport.Width];
  1505. }
  1506. Driver.AddStr (render);
  1507. }
  1508. private void SetClipboard (IEnumerable<Rune> text)
  1509. {
  1510. if (!Secret)
  1511. {
  1512. Clipboard.Contents = StringExtensions.ToString (text.ToList ());
  1513. }
  1514. }
  1515. private void SetOverwrite (bool overwrite)
  1516. {
  1517. Used = overwrite;
  1518. SetNeedsDisplay ();
  1519. }
  1520. private void SetSelectedStartSelectedLength ()
  1521. {
  1522. if (SelectedStart > -1 && _cursorPosition < SelectedStart)
  1523. {
  1524. _start = _cursorPosition;
  1525. }
  1526. else
  1527. {
  1528. _start = SelectedStart;
  1529. }
  1530. }
  1531. private void SetText (List<Rune> newText) { Text = StringExtensions.ToString (newText); }
  1532. private void SetText (IEnumerable<Rune> newText) { SetText (newText.ToList ()); }
  1533. private void ShowContextMenu ()
  1534. {
  1535. if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
  1536. {
  1537. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  1538. }
  1539. ContextMenu.Show (BuildContextMenuBarItem ());
  1540. }
  1541. private void TextField_Added (object sender, SuperViewChangedEventArgs e)
  1542. {
  1543. if (Autocomplete.HostControl is null)
  1544. {
  1545. Autocomplete.HostControl = this;
  1546. Autocomplete.PopupInsideContainer = false;
  1547. }
  1548. }
  1549. private void TextField_Removed (object sender, SuperViewChangedEventArgs e)
  1550. {
  1551. Autocomplete.HostControl = null;
  1552. }
  1553. private void TextField_Initialized (object sender, EventArgs e)
  1554. {
  1555. _cursorPosition = Text.GetRuneCount ();
  1556. if (Viewport.Width > 0)
  1557. {
  1558. ScrollOffset = _cursorPosition > Viewport.Width + 1 ? _cursorPosition - Viewport.Width + 1 : 0;
  1559. }
  1560. if (Autocomplete.HostControl is null)
  1561. {
  1562. Autocomplete.HostControl = this;
  1563. Autocomplete.PopupInsideContainer = false;
  1564. }
  1565. }
  1566. }
  1567. /// <summary>
  1568. /// Renders an overlay on another view at a given point that allows selecting from a range of 'autocomplete'
  1569. /// options. An implementation on a TextField.
  1570. /// </summary>
  1571. public class TextFieldAutocomplete : PopupAutocomplete
  1572. {
  1573. /// <inheritdoc/>
  1574. protected override void DeleteTextBackwards () { ((TextField)HostControl).DeleteCharLeft (false); }
  1575. /// <inheritdoc/>
  1576. protected override void InsertText (string accepted) { ((TextField)HostControl).InsertText (accepted, false); }
  1577. /// <inheritdoc/>
  1578. protected override void SetCursorPosition (int column) { ((TextField)HostControl).CursorPosition = column; }
  1579. }