TextField.cs 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911
  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, MenuItems = BuildContextMenuBarItem () };
  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 (_currentCulture != Thread.CurrentThread.CurrentUICulture)
  1536. {
  1537. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  1538. ContextMenu.MenuItems = BuildContextMenuBarItem ();
  1539. }
  1540. ContextMenu.Show ();
  1541. }
  1542. private void TextField_Added (object sender, SuperViewChangedEventArgs e)
  1543. {
  1544. if (Autocomplete.HostControl is null)
  1545. {
  1546. Autocomplete.HostControl = this;
  1547. Autocomplete.PopupInsideContainer = false;
  1548. }
  1549. }
  1550. private void TextField_Removed (object sender, SuperViewChangedEventArgs e)
  1551. {
  1552. Autocomplete.HostControl = null;
  1553. }
  1554. private void TextField_Initialized (object sender, EventArgs e)
  1555. {
  1556. _cursorPosition = Text.GetRuneCount ();
  1557. if (Viewport.Width > 0)
  1558. {
  1559. ScrollOffset = _cursorPosition > Viewport.Width + 1 ? _cursorPosition - Viewport.Width + 1 : 0;
  1560. }
  1561. if (Autocomplete.HostControl is null)
  1562. {
  1563. Autocomplete.HostControl = this;
  1564. Autocomplete.PopupInsideContainer = false;
  1565. }
  1566. }
  1567. }
  1568. /// <summary>
  1569. /// Renders an overlay on another view at a given point that allows selecting from a range of 'autocomplete'
  1570. /// options. An implementation on a TextField.
  1571. /// </summary>
  1572. public class TextFieldAutocomplete : PopupAutocomplete
  1573. {
  1574. /// <inheritdoc/>
  1575. protected override void DeleteTextBackwards () { ((TextField)HostControl).DeleteCharLeft (false); }
  1576. /// <inheritdoc/>
  1577. protected override void InsertText (string accepted) { ((TextField)HostControl).InsertText (accepted, false); }
  1578. /// <inheritdoc/>
  1579. protected override void SetCursorPosition (int column) { ((TextField)HostControl).CursorPosition = column; }
  1580. }