TextField.cs 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902
  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.LeftStartExtend,
  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.LeftStart,
  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.Context,
  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.LeftStartExtend);
  271. KeyBindings.Add (Key.Home.WithShift.WithCtrl, Command.LeftStartExtend);
  272. KeyBindings.Add (Key.A.WithShift.WithCtrl, Command.LeftStartExtend);
  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.LeftStart);
  277. KeyBindings.Add (Key.Home.WithCtrl, Command.LeftStart);
  278. KeyBindings.Add (Key.A.WithCtrl, Command.LeftStart);
  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.Context);
  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. return GetFocusColor ();
  594. }
  595. /// <summary>
  596. /// Inserts the given <paramref name="toAdd"/> text at the current cursor position exactly as if the user had just
  597. /// typed it
  598. /// </summary>
  599. /// <param name="toAdd">Text to add</param>
  600. /// <param name="useOldCursorPos">Use the previous cursor position.</param>
  601. public void InsertText (string toAdd, bool useOldCursorPos = true)
  602. {
  603. foreach (char ch in toAdd)
  604. {
  605. Key key;
  606. try
  607. {
  608. key = ch;
  609. }
  610. catch (Exception)
  611. {
  612. throw new ArgumentException (
  613. $"Cannot insert character '{ch}' because it does not map to a Key"
  614. );
  615. }
  616. InsertText (key, useOldCursorPos);
  617. }
  618. }
  619. /// <summary>Deletes word backwards.</summary>
  620. public virtual void KillWordBackwards ()
  621. {
  622. ClearAllSelection ();
  623. (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
  624. if (newPos is null)
  625. {
  626. return;
  627. }
  628. if (newPos.Value.col != -1)
  629. {
  630. SetText (
  631. _text.GetRange (0, newPos.Value.col)
  632. .Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition))
  633. );
  634. _cursorPosition = newPos.Value.col;
  635. }
  636. Adjust ();
  637. }
  638. /// <summary>Deletes word forwards.</summary>
  639. public virtual void KillWordForwards ()
  640. {
  641. ClearAllSelection ();
  642. (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
  643. if (newPos is null)
  644. {
  645. return;
  646. }
  647. if (newPos.Value.col != -1)
  648. {
  649. SetText (
  650. _text.GetRange (0, _cursorPosition)
  651. .Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col))
  652. );
  653. }
  654. Adjust ();
  655. }
  656. /// <inheritdoc/>
  657. protected internal override bool OnMouseEvent (MouseEvent ev)
  658. {
  659. if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed)
  660. && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition)
  661. && !ev.Flags.HasFlag (MouseFlags.Button1Released)
  662. && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
  663. && !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)
  664. && !ev.Flags.HasFlag (ContextMenu.MouseFlags))
  665. {
  666. return base.OnMouseEvent (ev);
  667. }
  668. if (!CanFocus)
  669. {
  670. return true;
  671. }
  672. if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition)
  673. {
  674. SetFocus ();
  675. }
  676. // Give autocomplete first opportunity to respond to mouse clicks
  677. if (SelectedLength == 0 && Autocomplete.OnMouseEvent (ev, true))
  678. {
  679. return true;
  680. }
  681. if (ev.Flags == MouseFlags.Button1Pressed)
  682. {
  683. EnsureHasFocus ();
  684. PositionCursor (ev);
  685. if (_isButtonReleased)
  686. {
  687. ClearAllSelection ();
  688. }
  689. _isButtonReleased = true;
  690. _isButtonPressed = true;
  691. }
  692. else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed)
  693. {
  694. int x = PositionCursor (ev);
  695. _isButtonReleased = false;
  696. PrepareSelection (x);
  697. if (Application.MouseGrabView is null)
  698. {
  699. Application.GrabMouse (this);
  700. }
  701. }
  702. else if (ev.Flags == MouseFlags.Button1Released)
  703. {
  704. _isButtonReleased = true;
  705. _isButtonPressed = false;
  706. Application.UngrabMouse ();
  707. }
  708. else if (ev.Flags == MouseFlags.Button1DoubleClicked)
  709. {
  710. EnsureHasFocus ();
  711. int x = PositionCursor (ev);
  712. int sbw = x;
  713. if (x == _text.Count
  714. || (x > 0 && (char)_text [x - 1].Value != ' ')
  715. || (x > 0 && (char)_text [x].Value == ' '))
  716. {
  717. (int col, int row)? newPosBw = GetModel ().WordBackward (x, 0);
  718. if (newPosBw is null)
  719. {
  720. return true;
  721. }
  722. sbw = newPosBw.Value.col;
  723. }
  724. if (sbw != -1)
  725. {
  726. x = sbw;
  727. PositionCursor (x);
  728. }
  729. (int col, int row)? newPosFw = GetModel ().WordForward (x, 0);
  730. if (newPosFw is null)
  731. {
  732. return true;
  733. }
  734. ClearAllSelection ();
  735. if (newPosFw.Value.col != -1 && sbw != -1)
  736. {
  737. _cursorPosition = newPosFw.Value.col;
  738. }
  739. PrepareSelection (sbw, newPosFw.Value.col - sbw);
  740. }
  741. else if (ev.Flags == MouseFlags.Button1TripleClicked)
  742. {
  743. EnsureHasFocus ();
  744. PositionCursor (0);
  745. ClearAllSelection ();
  746. PrepareSelection (0, _text.Count);
  747. }
  748. else if (ev.Flags == ContextMenu.MouseFlags)
  749. {
  750. ShowContextMenu ();
  751. }
  752. //SetNeedsDisplay ();
  753. return true;
  754. void EnsureHasFocus ()
  755. {
  756. if (!HasFocus)
  757. {
  758. SetFocus ();
  759. }
  760. }
  761. }
  762. /// <summary>Moves cursor to the end of the typed text.</summary>
  763. public void MoveEnd ()
  764. {
  765. ClearAllSelection ();
  766. _cursorPosition = _text.Count;
  767. Adjust ();
  768. }
  769. /// <inheritdoc/>
  770. public override void OnDrawContent (Rectangle viewport)
  771. {
  772. _isDrawing = true;
  773. var selColor = new Attribute (GetFocusColor ().Background, GetFocusColor ().Foreground);
  774. SetSelectedStartSelectedLength ();
  775. Driver?.SetAttribute (GetNormalColor ());
  776. Move (0, 0);
  777. int p = ScrollOffset;
  778. var col = 0;
  779. int width = Frame.Width + OffSetBackground ();
  780. int tcount = _text.Count;
  781. Attribute roc = GetReadOnlyColor ();
  782. for (int idx = p; idx < tcount; idx++)
  783. {
  784. Rune rune = _text [idx];
  785. int cols = rune.GetColumns ();
  786. if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly)
  787. {
  788. Driver?.SetAttribute (selColor);
  789. }
  790. else if (ReadOnly)
  791. {
  792. Driver?.SetAttribute (
  793. idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
  794. ? selColor
  795. : roc
  796. );
  797. }
  798. else if (!HasFocus && Enabled)
  799. {
  800. Driver?.SetAttribute (GetFocusColor ());
  801. }
  802. else if (!Enabled)
  803. {
  804. Driver?.SetAttribute (roc);
  805. }
  806. else
  807. {
  808. Driver?.SetAttribute (
  809. idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
  810. ? selColor
  811. : ColorScheme.Focus
  812. );
  813. }
  814. if (col + cols <= width)
  815. {
  816. Driver?.AddRune (Secret ? Glyphs.Dot : rune);
  817. }
  818. if (!TextModel.SetCol (ref col, width, cols))
  819. {
  820. break;
  821. }
  822. if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width)
  823. {
  824. break;
  825. }
  826. }
  827. Driver.SetAttribute (GetFocusColor ());
  828. for (int i = col; i < width; i++)
  829. {
  830. Driver.AddRune ((Rune)' ');
  831. }
  832. PositionCursor ();
  833. RenderCaption ();
  834. DrawAutocomplete ();
  835. _isDrawing = false;
  836. }
  837. /// <inheritdoc/>
  838. public override bool? OnInvokingKeyBindings (Key a, KeyBindingScope scope)
  839. {
  840. // Give autocomplete first opportunity to respond to key presses
  841. if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a))
  842. {
  843. return true;
  844. }
  845. return base.OnInvokingKeyBindings (a, scope);
  846. }
  847. /// <inheritdoc/>
  848. protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
  849. {
  850. if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
  851. {
  852. Application.UngrabMouse ();
  853. }
  854. //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
  855. // ClearAllSelection ();
  856. return;
  857. }
  858. /// TODO: Flush out these docs
  859. /// <summary>
  860. /// Processes key presses for the <see cref="TextField"/>.
  861. /// <remarks>
  862. /// The <see cref="TextField"/> control responds to the following keys:
  863. /// <list type="table">
  864. /// <listheader>
  865. /// <term>Keys</term> <description>Function</description>
  866. /// </listheader>
  867. /// <item>
  868. /// <term><see cref="Key.Delete"/>, <see cref="Key.Backspace"/></term>
  869. /// <description>Deletes the character before cursor.</description>
  870. /// </item>
  871. /// </list>
  872. /// </remarks>
  873. /// </summary>
  874. /// <param name="a"></param>
  875. /// <returns></returns>
  876. public override bool OnProcessKeyDown (Key a)
  877. {
  878. // Remember the cursor position because the new calculated cursor position is needed
  879. // to be set BEFORE the TextChanged event is triggered.
  880. // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
  881. _preTextChangedCursorPos = _cursorPosition;
  882. // Ignore other control characters.
  883. if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask))
  884. {
  885. return false;
  886. }
  887. if (ReadOnly)
  888. {
  889. return true;
  890. }
  891. InsertText (a, true);
  892. return true;
  893. }
  894. /// <summary>Virtual method that invoke the <see cref="TextChanging"/> event if it's defined.</summary>
  895. /// <param name="args">The event arguments.</param>
  896. /// <returns><see langword="true"/> if the event was cancelled.</returns>
  897. public bool OnTextChanging (CancelEventArgs<string> args)
  898. {
  899. TextChanging?.Invoke (this, args);
  900. return args.Cancel;
  901. }
  902. /// <summary>Paste the selected text from the clipboard.</summary>
  903. public virtual void Paste ()
  904. {
  905. if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents))
  906. {
  907. return;
  908. }
  909. SetSelectedStartSelectedLength ();
  910. int selStart = _start == -1 ? CursorPosition : _start;
  911. string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
  912. Text = StringExtensions.ToString (_text.GetRange (0, selStart))
  913. + cbTxt
  914. + StringExtensions.ToString (
  915. _text.GetRange (
  916. selStart + SelectedLength,
  917. _text.Count - (selStart + SelectedLength)
  918. )
  919. );
  920. _cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count);
  921. ClearAllSelection ();
  922. SetNeedsDisplay ();
  923. Adjust ();
  924. }
  925. /// <summary>Sets the cursor position.</summary>
  926. public override Point? PositionCursor ()
  927. {
  928. ProcessAutocomplete ();
  929. var col = 0;
  930. for (int idx = ScrollOffset < 0 ? 0 : ScrollOffset; idx < _text.Count; idx++)
  931. {
  932. if (idx == _cursorPosition)
  933. {
  934. break;
  935. }
  936. int cols = _text [idx].GetColumns ();
  937. TextModel.SetCol (ref col, Frame.Width - 1, cols);
  938. }
  939. int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
  940. Move (pos, 0);
  941. return new Point (pos, 0);
  942. }
  943. /// <summary>Redoes the latest changes.</summary>
  944. public void Redo ()
  945. {
  946. if (ReadOnly)
  947. {
  948. return;
  949. }
  950. _historyText.Redo ();
  951. }
  952. /// <summary>Selects all text.</summary>
  953. public void SelectAll ()
  954. {
  955. if (_text.Count == 0)
  956. {
  957. return;
  958. }
  959. _selectedStart = 0;
  960. MoveEndExtend ();
  961. SetNeedsDisplay ();
  962. }
  963. ///// <summary>
  964. ///// Changed event, raised when the text has changed.
  965. ///// <remarks>
  966. ///// This event is raised when the <see cref="Text"/> changes. The passed <see cref="EventArgs"/> is a
  967. ///// <see cref="string"/> containing the old value.
  968. ///// </remarks>
  969. ///// </summary>
  970. //public event EventHandler<StateEventArgs<string>> TextChanged;
  971. /// <summary>Changing event, raised before the <see cref="Text"/> changes and can be canceled or changing the new text.</summary>
  972. public event EventHandler<CancelEventArgs<string>> TextChanging;
  973. /// <summary>Undoes the latest changes.</summary>
  974. public void Undo ()
  975. {
  976. if (ReadOnly)
  977. {
  978. return;
  979. }
  980. _historyText.Undo ();
  981. }
  982. /// <summary>
  983. /// Returns <see langword="true"/> if the current cursor position is at the end of the <see cref="Text"/>. This
  984. /// includes when it is empty.
  985. /// </summary>
  986. /// <returns></returns>
  987. internal bool CursorIsAtEnd () { return CursorPosition == Text.Length; }
  988. /// <summary>Returns <see langword="true"/> if the current cursor position is at the start of the <see cref="TextField"/>.</summary>
  989. /// <returns></returns>
  990. internal bool CursorIsAtStart () { return CursorPosition <= 0; }
  991. private void Adjust ()
  992. {
  993. if (!IsAdded)
  994. {
  995. return;
  996. }
  997. // TODO: This is a lame prototype proving it should be easy for TextField to
  998. // TODO: support Width = Dim.Auto (DimAutoStyle: Content).
  999. //SetContentSize(new (TextModel.DisplaySize (_text).size, 1));
  1000. int offB = OffSetBackground ();
  1001. bool need = NeedsDisplay || !Used;
  1002. if (_cursorPosition < ScrollOffset)
  1003. {
  1004. ScrollOffset = _cursorPosition;
  1005. need = true;
  1006. }
  1007. else if (Frame.Width > 0
  1008. && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0
  1009. || TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB))
  1010. {
  1011. ScrollOffset = Math.Max (
  1012. TextModel.CalculateLeftColumn (
  1013. _text,
  1014. ScrollOffset,
  1015. _cursorPosition,
  1016. Frame.Width + offB
  1017. ),
  1018. 0
  1019. );
  1020. need = true;
  1021. }
  1022. if (need)
  1023. {
  1024. SetNeedsDisplay ();
  1025. }
  1026. else
  1027. {
  1028. PositionCursor ();
  1029. }
  1030. }
  1031. private MenuBarItem BuildContextMenuBarItem ()
  1032. {
  1033. return new MenuBarItem (
  1034. new MenuItem []
  1035. {
  1036. new (
  1037. Strings.ctxSelectAll,
  1038. "",
  1039. () => SelectAll (),
  1040. null,
  1041. null,
  1042. (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)
  1043. ),
  1044. new (
  1045. Strings.ctxDeleteAll,
  1046. "",
  1047. () => DeleteAll (),
  1048. null,
  1049. null,
  1050. (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)
  1051. ),
  1052. new (
  1053. Strings.ctxCopy,
  1054. "",
  1055. () => Copy (),
  1056. null,
  1057. null,
  1058. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)
  1059. ),
  1060. new (
  1061. Strings.ctxCut,
  1062. "",
  1063. () => Cut (),
  1064. null,
  1065. null,
  1066. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)
  1067. ),
  1068. new (
  1069. Strings.ctxPaste,
  1070. "",
  1071. () => Paste (),
  1072. null,
  1073. null,
  1074. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)
  1075. ),
  1076. new (
  1077. Strings.ctxUndo,
  1078. "",
  1079. () => Undo (),
  1080. null,
  1081. null,
  1082. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)
  1083. ),
  1084. new (
  1085. Strings.ctxRedo,
  1086. "",
  1087. () => Redo (),
  1088. null,
  1089. null,
  1090. (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)
  1091. )
  1092. }
  1093. );
  1094. }
  1095. private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
  1096. {
  1097. KeyBindings.ReplaceKey (e.OldKey.KeyCode, e.NewKey.KeyCode);
  1098. }
  1099. private List<Rune> DeleteSelectedText ()
  1100. {
  1101. SetSelectedStartSelectedLength ();
  1102. int selStart = SelectedStart > -1 ? _start : _cursorPosition;
  1103. string newText = StringExtensions.ToString (_text.GetRange (0, selStart))
  1104. + StringExtensions.ToString (
  1105. _text.GetRange (
  1106. selStart + SelectedLength,
  1107. _text.Count - (selStart + SelectedLength)
  1108. )
  1109. );
  1110. ClearAllSelection ();
  1111. _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
  1112. return newText.ToRuneList ();
  1113. }
  1114. private void GenerateSuggestions ()
  1115. {
  1116. List<RuneCell> currentLine = TextModel.ToRuneCellList (Text);
  1117. int cursorPosition = Math.Min (CursorPosition, currentLine.Count);
  1118. Autocomplete.Context = new AutocompleteContext (
  1119. currentLine,
  1120. cursorPosition,
  1121. Autocomplete.Context != null
  1122. ? Autocomplete.Context.Canceled
  1123. : false
  1124. );
  1125. Autocomplete.GenerateSuggestions (
  1126. Autocomplete.Context
  1127. );
  1128. }
  1129. private TextModel GetModel ()
  1130. {
  1131. var model = new TextModel ();
  1132. model.LoadString (Text);
  1133. return model;
  1134. }
  1135. private Attribute GetReadOnlyColor ()
  1136. {
  1137. ColorScheme cs = ColorScheme;
  1138. if (ColorScheme is null)
  1139. {
  1140. cs = new ColorScheme ();
  1141. }
  1142. if (cs.Disabled.Foreground == cs.Focus.Background)
  1143. {
  1144. return new Attribute (cs.Focus.Foreground, cs.Focus.Background);
  1145. }
  1146. return new Attribute (cs.Disabled.Foreground, cs.Focus.Background);
  1147. }
  1148. private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemEventArgs obj)
  1149. {
  1150. if (obj is null)
  1151. {
  1152. return;
  1153. }
  1154. Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
  1155. CursorPosition = obj.CursorPosition.X;
  1156. Adjust ();
  1157. }
  1158. private void InsertText (Key a, bool usePreTextChangedCursorPos)
  1159. {
  1160. _historyText.Add (
  1161. new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
  1162. new Point (_cursorPosition, 0)
  1163. );
  1164. List<Rune> newText = _text;
  1165. if (SelectedLength > 0)
  1166. {
  1167. newText = DeleteSelectedText ();
  1168. _preTextChangedCursorPos = _cursorPosition;
  1169. }
  1170. if (!usePreTextChangedCursorPos)
  1171. {
  1172. _preTextChangedCursorPos = _cursorPosition;
  1173. }
  1174. StringRuneEnumerator kbstr = a.AsRune.ToString ().EnumerateRunes ();
  1175. if (Used)
  1176. {
  1177. _cursorPosition++;
  1178. if (_cursorPosition == newText.Count + 1)
  1179. {
  1180. SetText (newText.Concat (kbstr).ToList ());
  1181. }
  1182. else
  1183. {
  1184. if (_preTextChangedCursorPos > newText.Count)
  1185. {
  1186. _preTextChangedCursorPos = newText.Count;
  1187. }
  1188. SetText (
  1189. newText.GetRange (0, _preTextChangedCursorPos)
  1190. .Concat (kbstr)
  1191. .Concat (
  1192. newText.GetRange (
  1193. _preTextChangedCursorPos,
  1194. Math.Min (
  1195. newText.Count - _preTextChangedCursorPos,
  1196. newText.Count
  1197. )
  1198. )
  1199. )
  1200. );
  1201. }
  1202. }
  1203. else
  1204. {
  1205. SetText (
  1206. newText.GetRange (0, _preTextChangedCursorPos)
  1207. .Concat (kbstr)
  1208. .Concat (
  1209. newText.GetRange (
  1210. Math.Min (_preTextChangedCursorPos + 1, newText.Count),
  1211. Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0)
  1212. )
  1213. )
  1214. );
  1215. _cursorPosition++;
  1216. }
  1217. Adjust ();
  1218. }
  1219. private void KillToEnd ()
  1220. {
  1221. if (ReadOnly)
  1222. {
  1223. return;
  1224. }
  1225. ClearAllSelection ();
  1226. if (_cursorPosition >= _text.Count)
  1227. {
  1228. return;
  1229. }
  1230. SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  1231. SetText (_text.GetRange (0, _cursorPosition));
  1232. Adjust ();
  1233. }
  1234. private void KillToStart ()
  1235. {
  1236. if (ReadOnly)
  1237. {
  1238. return;
  1239. }
  1240. ClearAllSelection ();
  1241. if (_cursorPosition == 0)
  1242. {
  1243. return;
  1244. }
  1245. SetClipboard (_text.GetRange (0, _cursorPosition));
  1246. SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  1247. _cursorPosition = 0;
  1248. Adjust ();
  1249. }
  1250. private void MoveEndExtend ()
  1251. {
  1252. if (_cursorPosition <= _text.Count)
  1253. {
  1254. int x = _cursorPosition;
  1255. _cursorPosition = _text.Count;
  1256. PrepareSelection (x, _cursorPosition - x);
  1257. }
  1258. }
  1259. private void MoveHome ()
  1260. {
  1261. ClearAllSelection ();
  1262. _cursorPosition = 0;
  1263. Adjust ();
  1264. }
  1265. private void MoveHomeExtend ()
  1266. {
  1267. if (_cursorPosition > 0)
  1268. {
  1269. int x = _cursorPosition;
  1270. _cursorPosition = 0;
  1271. PrepareSelection (x, _cursorPosition - x);
  1272. }
  1273. }
  1274. private bool MoveLeft ()
  1275. {
  1276. if (_cursorPosition > 0)
  1277. {
  1278. ClearAllSelection ();
  1279. _cursorPosition--;
  1280. Adjust ();
  1281. return true;
  1282. }
  1283. return false;
  1284. }
  1285. private void MoveLeftExtend ()
  1286. {
  1287. if (_cursorPosition > 0)
  1288. {
  1289. PrepareSelection (_cursorPosition--, -1);
  1290. }
  1291. }
  1292. private bool MoveRight ()
  1293. {
  1294. if (_cursorPosition == _text.Count)
  1295. {
  1296. return false;
  1297. }
  1298. ClearAllSelection ();
  1299. _cursorPosition++;
  1300. Adjust ();
  1301. return true;
  1302. }
  1303. private void MoveRightExtend ()
  1304. {
  1305. if (_cursorPosition < _text.Count)
  1306. {
  1307. PrepareSelection (_cursorPosition++, 1);
  1308. }
  1309. }
  1310. private void MoveWordLeft ()
  1311. {
  1312. ClearAllSelection ();
  1313. (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
  1314. if (newPos is null)
  1315. {
  1316. return;
  1317. }
  1318. if (newPos.Value.col != -1)
  1319. {
  1320. _cursorPosition = newPos.Value.col;
  1321. }
  1322. Adjust ();
  1323. }
  1324. private void MoveWordLeftExtend ()
  1325. {
  1326. if (_cursorPosition > 0)
  1327. {
  1328. int x = Math.Min (
  1329. _start > -1 && _start > _cursorPosition ? _start : _cursorPosition,
  1330. _text.Count
  1331. );
  1332. if (x > 0)
  1333. {
  1334. (int col, int row)? newPos = GetModel ().WordBackward (x, 0);
  1335. if (newPos is null)
  1336. {
  1337. return;
  1338. }
  1339. if (newPos.Value.col != -1)
  1340. {
  1341. _cursorPosition = newPos.Value.col;
  1342. }
  1343. PrepareSelection (x, newPos.Value.col - x);
  1344. }
  1345. }
  1346. }
  1347. private void MoveWordRight ()
  1348. {
  1349. ClearAllSelection ();
  1350. (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
  1351. if (newPos is null)
  1352. {
  1353. return;
  1354. }
  1355. if (newPos.Value.col != -1)
  1356. {
  1357. _cursorPosition = newPos.Value.col;
  1358. }
  1359. Adjust ();
  1360. }
  1361. private void MoveWordRightExtend ()
  1362. {
  1363. if (_cursorPosition < _text.Count)
  1364. {
  1365. int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
  1366. (int col, int row)? newPos = GetModel ().WordForward (x, 0);
  1367. if (newPos is null)
  1368. {
  1369. return;
  1370. }
  1371. if (newPos.Value.col != -1)
  1372. {
  1373. _cursorPosition = newPos.Value.col;
  1374. }
  1375. PrepareSelection (x, newPos.Value.col - x);
  1376. }
  1377. }
  1378. // BUGBUG: This assumes Frame == Viewport. It's also not clear what the intention is. For now, changed to always return 0.
  1379. private int OffSetBackground ()
  1380. {
  1381. var offB = 0;
  1382. if (SuperView?.Frame.Right - Frame.Right < 0)
  1383. {
  1384. offB = SuperView.Frame.Right - Frame.Right - 1;
  1385. }
  1386. return 0;//offB;
  1387. }
  1388. private int PositionCursor (MouseEvent ev)
  1389. {
  1390. return PositionCursor (TextModel.GetColFromX (_text, ScrollOffset, ev.Position.X), false);
  1391. }
  1392. private int PositionCursor (int x, bool getX = true)
  1393. {
  1394. int pX = x;
  1395. if (getX)
  1396. {
  1397. pX = TextModel.GetColFromX (_text, ScrollOffset, x);
  1398. }
  1399. if (ScrollOffset + pX > _text.Count)
  1400. {
  1401. _cursorPosition = _text.Count;
  1402. }
  1403. else if (ScrollOffset + pX < ScrollOffset)
  1404. {
  1405. _cursorPosition = 0;
  1406. }
  1407. else
  1408. {
  1409. _cursorPosition = ScrollOffset + pX;
  1410. }
  1411. return _cursorPosition;
  1412. }
  1413. private void PrepareSelection (int x, int direction = 0)
  1414. {
  1415. x = x + ScrollOffset < -1 ? 0 : x;
  1416. _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count
  1417. ? x
  1418. : _selectedStart;
  1419. if (_selectedStart > -1)
  1420. {
  1421. SelectedLength = Math.Abs (
  1422. x + direction <= _text.Count
  1423. ? x + direction - _selectedStart
  1424. : _text.Count - _selectedStart
  1425. );
  1426. SetSelectedStartSelectedLength ();
  1427. if (_start > -1 && SelectedLength > 0)
  1428. {
  1429. _selectedText = SelectedLength > 0
  1430. ? StringExtensions.ToString (
  1431. _text.GetRange (
  1432. _start < 0 ? 0 : _start,
  1433. SelectedLength > _text.Count
  1434. ? _text.Count
  1435. : SelectedLength
  1436. )
  1437. )
  1438. : "";
  1439. if (ScrollOffset > _start)
  1440. {
  1441. ScrollOffset = _start;
  1442. }
  1443. }
  1444. else if (_start > -1 && SelectedLength == 0)
  1445. {
  1446. _selectedText = null;
  1447. }
  1448. SetNeedsDisplay ();
  1449. }
  1450. else if (SelectedLength > 0 || _selectedText is { })
  1451. {
  1452. ClearAllSelection ();
  1453. }
  1454. Adjust ();
  1455. }
  1456. private void ProcessAutocomplete ()
  1457. {
  1458. if (_isDrawing)
  1459. {
  1460. return;
  1461. }
  1462. if (SelectedLength > 0)
  1463. {
  1464. return;
  1465. }
  1466. GenerateSuggestions ();
  1467. }
  1468. private void DrawAutocomplete ()
  1469. {
  1470. if (SelectedLength > 0)
  1471. {
  1472. return;
  1473. }
  1474. if (Autocomplete?.Context == null)
  1475. {
  1476. return;
  1477. }
  1478. var renderAt = new Point (
  1479. Autocomplete.Context.CursorPosition,
  1480. 0
  1481. );
  1482. Autocomplete.RenderOverlay (renderAt);
  1483. }
  1484. private void RenderCaption ()
  1485. {
  1486. if (HasFocus
  1487. || Caption == null
  1488. || Caption.Length == 0
  1489. || Text?.Length > 0)
  1490. {
  1491. return;
  1492. }
  1493. var color = new Attribute (CaptionColor, GetNormalColor ().Background);
  1494. Driver.SetAttribute (color);
  1495. Move (0, 0);
  1496. string render = Caption;
  1497. if (render.GetColumns () > Viewport.Width)
  1498. {
  1499. render = render [..Viewport.Width];
  1500. }
  1501. Driver.AddStr (render);
  1502. }
  1503. private void SetClipboard (IEnumerable<Rune> text)
  1504. {
  1505. if (!Secret)
  1506. {
  1507. Clipboard.Contents = StringExtensions.ToString (text.ToList ());
  1508. }
  1509. }
  1510. private void SetOverwrite (bool overwrite)
  1511. {
  1512. Used = overwrite;
  1513. SetNeedsDisplay ();
  1514. }
  1515. private void SetSelectedStartSelectedLength ()
  1516. {
  1517. if (SelectedStart > -1 && _cursorPosition < SelectedStart)
  1518. {
  1519. _start = _cursorPosition;
  1520. }
  1521. else
  1522. {
  1523. _start = SelectedStart;
  1524. }
  1525. }
  1526. private void SetText (List<Rune> newText) { Text = StringExtensions.ToString (newText); }
  1527. private void SetText (IEnumerable<Rune> newText) { SetText (newText.ToList ()); }
  1528. private void ShowContextMenu ()
  1529. {
  1530. if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
  1531. {
  1532. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  1533. }
  1534. ContextMenu.Show (BuildContextMenuBarItem ());
  1535. }
  1536. private void TextField_Added (object sender, SuperViewChangedEventArgs e)
  1537. {
  1538. if (Autocomplete.HostControl is null)
  1539. {
  1540. Autocomplete.HostControl = this;
  1541. Autocomplete.PopupInsideContainer = false;
  1542. }
  1543. }
  1544. private void TextField_Removed (object sender, SuperViewChangedEventArgs e)
  1545. {
  1546. Autocomplete.HostControl = null;
  1547. }
  1548. private void TextField_Initialized (object sender, EventArgs e)
  1549. {
  1550. _cursorPosition = Text.GetRuneCount ();
  1551. if (Viewport.Width > 0)
  1552. {
  1553. ScrollOffset = _cursorPosition > Viewport.Width + 1 ? _cursorPosition - Viewport.Width + 1 : 0;
  1554. }
  1555. if (Autocomplete.HostControl is null)
  1556. {
  1557. Autocomplete.HostControl = this;
  1558. Autocomplete.PopupInsideContainer = false;
  1559. }
  1560. }
  1561. }
  1562. /// <summary>
  1563. /// Renders an overlay on another view at a given point that allows selecting from a range of 'autocomplete'
  1564. /// options. An implementation on a TextField.
  1565. /// </summary>
  1566. public class TextFieldAutocomplete : PopupAutocomplete
  1567. {
  1568. /// <inheritdoc/>
  1569. protected override void DeleteTextBackwards () { ((TextField)HostControl).DeleteCharLeft (false); }
  1570. /// <inheritdoc/>
  1571. protected override void InsertText (string accepted) { ((TextField)HostControl).InsertText (accepted, false); }
  1572. /// <inheritdoc/>
  1573. protected override void SetCursorPosition (int column) { ((TextField)HostControl).CursorPosition = column; }
  1574. }