TextField.cs 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376
  1. //
  2. // TextField.cs: single-line text editor with Emacs keybindings
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Globalization;
  10. using System.Linq;
  11. using System.Threading;
  12. using System.Text;
  13. using Terminal.Gui.Resources;
  14. namespace Terminal.Gui {
  15. /// <summary>
  16. /// Single-line text entry <see cref="View"/>
  17. /// </summary>
  18. /// <remarks>
  19. /// The <see cref="TextField"/> <see cref="View"/> provides editing functionality and mouse support.
  20. /// </remarks>
  21. public class TextField : View {
  22. List<Rune> _text;
  23. int _first, _cursorPosition;
  24. int _selectedStart = -1; // -1 represents there is no text selection.
  25. string _selectedText;
  26. HistoryText _historyText = new HistoryText ();
  27. CultureInfo _currentCulture;
  28. /// <summary>
  29. /// Gets or sets the text to render in control when no value has
  30. /// been entered yet and the <see cref="View"/> does not yet have
  31. /// input focus.
  32. /// </summary>
  33. public string Caption { get; set; }
  34. /// <summary>
  35. /// Gets or sets the foreground <see cref="Color"/> to use when
  36. /// rendering <see cref="Caption"/>.
  37. /// </summary>
  38. public Color CaptionColor { get; set; } = new Color (Color.DarkGray);
  39. /// <summary>
  40. /// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input should be appended at the cursor position, rather than clearing the entry
  41. /// </summary>
  42. public bool Used { get; set; }
  43. /// <summary>
  44. /// If set to true its not allow any changes in the text.
  45. /// </summary>
  46. public bool ReadOnly { get; set; } = false;
  47. /// <summary>
  48. /// Changing event, raised before the <see cref="Text"/> changes and can be canceled or changing the new text.
  49. /// </summary>
  50. public event EventHandler<TextChangingEventArgs> TextChanging;
  51. /// <summary>
  52. /// Changed event, raised when the text has changed.
  53. /// <remarks>
  54. /// This event is raised when the <see cref="Text"/> changes.
  55. /// The passed <see cref="EventArgs"/> is a <see cref="string"/> containing the old value.
  56. /// </remarks>
  57. /// </summary>
  58. public event EventHandler<TextChangedEventArgs> TextChanged;
  59. /// <summary>
  60. /// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Computed"/> positioning.
  61. /// </summary>
  62. public TextField () : this (string.Empty) { }
  63. /// <summary>
  64. /// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Computed"/> positioning.
  65. /// </summary>
  66. /// <param name="text">Initial text contents.</param>
  67. public TextField (string text) : base (text)
  68. {
  69. SetInitialProperties (text, text.GetRuneCount () + 1);
  70. }
  71. /// <summary>
  72. /// Initializes a new instance of the <see cref="TextField"/> class using <see cref="LayoutStyle.Absolute"/> positioning.
  73. /// </summary>
  74. /// <param name="x">The x coordinate.</param>
  75. /// <param name="y">The y coordinate.</param>
  76. /// <param name="w">The width.</param>
  77. /// <param name="text">Initial text contents.</param>
  78. public TextField (int x, int y, int w, string text) : base (new Rect (x, y, w, 1))
  79. {
  80. SetInitialProperties (text, w);
  81. }
  82. void SetInitialProperties (string text, int w)
  83. {
  84. Height = 1;
  85. if (text == null)
  86. text = "";
  87. this._text = text.Split ("\n") [0].EnumerateRunes ().ToList ();
  88. _cursorPosition = text.GetRuneCount ();
  89. _first = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0;
  90. CanFocus = true;
  91. Used = true;
  92. WantMousePositionReports = true;
  93. _savedCursorVisibility = _desiredCursorVisibility;
  94. _historyText.ChangeText += HistoryText_ChangeText;
  95. Initialized += TextField_Initialized;
  96. // Things this view knows how to do
  97. AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
  98. AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
  99. AddCommand (Command.LeftHomeExtend, () => { MoveHomeExtend (); return true; });
  100. AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; });
  101. AddCommand (Command.LeftHome, () => { MoveHome (); return true; });
  102. AddCommand (Command.LeftExtend, () => { MoveLeftExtend (); return true; });
  103. AddCommand (Command.RightExtend, () => { MoveRightExtend (); return true; });
  104. AddCommand (Command.WordLeftExtend, () => { MoveWordLeftExtend (); return true; });
  105. AddCommand (Command.WordRightExtend, () => { MoveWordRightExtend (); return true; });
  106. AddCommand (Command.Left, () => { MoveLeft (); return true; });
  107. AddCommand (Command.RightEnd, () => { MoveEnd (); return true; });
  108. AddCommand (Command.Right, () => { MoveRight (); return true; });
  109. AddCommand (Command.CutToEndLine, () => { KillToEnd (); return true; });
  110. AddCommand (Command.CutToStartLine, () => { KillToStart (); return true; });
  111. AddCommand (Command.Undo, () => { Undo (); return true; });
  112. AddCommand (Command.Redo, () => { Redo (); return true; });
  113. AddCommand (Command.WordLeft, () => { MoveWordLeft (); return true; });
  114. AddCommand (Command.WordRight, () => { MoveWordRight (); return true; });
  115. AddCommand (Command.KillWordForwards, () => { KillWordForwards (); return true; });
  116. AddCommand (Command.KillWordBackwards, () => { KillWordBackwards (); return true; });
  117. AddCommand (Command.ToggleOverwrite, () => { SetOverwrite (!Used); return true; });
  118. AddCommand (Command.EnableOverwrite, () => { SetOverwrite (true); return true; });
  119. AddCommand (Command.DisableOverwrite, () => { SetOverwrite (false); return true; });
  120. AddCommand (Command.Copy, () => { Copy (); return true; });
  121. AddCommand (Command.Cut, () => { Cut (); return true; });
  122. AddCommand (Command.Paste, () => { Paste (); return true; });
  123. AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
  124. AddCommand (Command.DeleteAll, () => { DeleteAll (); return true; });
  125. AddCommand (Command.ShowContextMenu, () => { ShowContextMenu (); return true; });
  126. // Default keybindings for this view
  127. // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
  128. KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
  129. KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
  130. KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);
  131. KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
  132. KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
  133. KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
  134. KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
  135. KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend);
  136. KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
  137. KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
  138. KeyBindings.Add (KeyCode.Home, Command.LeftHome);
  139. KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome);
  140. KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
  141. KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
  142. KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend);
  143. KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
  144. KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend);
  145. KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
  146. KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
  147. KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordLeftExtend);
  148. KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
  149. KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
  150. KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordRightExtend);
  151. KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
  152. KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
  153. KeyBindings.Add (KeyCode.End, Command.RightEnd);
  154. KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd);
  155. KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
  156. KeyBindings.Add (KeyCode.CursorRight, Command.Right);
  157. KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
  158. KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine);
  159. KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine);
  160. KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
  161. KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo);
  162. KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo);
  163. KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
  164. KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft);
  165. KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft);
  166. KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
  167. KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
  168. KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight);
  169. KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask, Command.KillWordForwards);
  170. KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
  171. KeyBindings.Add (KeyCode.InsertChar, Command.ToggleOverwrite);
  172. KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
  173. KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
  174. KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste);
  175. KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
  176. KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll);
  177. KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
  178. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  179. ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
  180. ContextMenu.KeyChanged += ContextMenu_KeyChanged;
  181. KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
  182. }
  183. private MenuBarItem BuildContextMenuBarItem ()
  184. {
  185. return new MenuBarItem (new MenuItem [] {
  186. new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)),
  187. new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)),
  188. new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)),
  189. new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)),
  190. new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)),
  191. new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)),
  192. new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)),
  193. });
  194. }
  195. private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
  196. {
  197. KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode);
  198. }
  199. private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
  200. {
  201. if (obj == null)
  202. return;
  203. Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
  204. CursorPosition = obj.CursorPosition.X;
  205. Adjust ();
  206. }
  207. void TextField_Initialized (object sender, EventArgs e)
  208. {
  209. Autocomplete.HostControl = this;
  210. Autocomplete.PopupInsideContainer = false;
  211. }
  212. ///<inheritdoc/>
  213. public override bool OnEnter (View view)
  214. {
  215. if (IsInitialized) {
  216. Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
  217. }
  218. return base.OnEnter (view);
  219. }
  220. ///<inheritdoc/>
  221. public override bool OnLeave (View view)
  222. {
  223. if (Application.MouseGrabView != null && Application.MouseGrabView == this)
  224. Application.UngrabMouse ();
  225. //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
  226. // ClearAllSelection ();
  227. return base.OnLeave (view);
  228. }
  229. /// <summary>
  230. /// Provides autocomplete context menu based on suggestions at the current cursor
  231. /// position. Configure <see cref="ISuggestionGenerator"/> to enable this feature.
  232. /// </summary>
  233. public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete ();
  234. ///<inheritdoc/>
  235. public override Rect Frame {
  236. get => base.Frame;
  237. set {
  238. if (value.Height > 1) {
  239. base.Frame = new Rect (value.X, value.Y, value.Width, 1);
  240. Height = 1;
  241. } else {
  242. base.Frame = value;
  243. }
  244. Adjust ();
  245. }
  246. }
  247. /// <summary>
  248. /// Sets or gets the text held by the view.
  249. /// </summary>
  250. public new string Text {
  251. get {
  252. return StringExtensions.ToString (_text);
  253. }
  254. set {
  255. var oldText = StringExtensions.ToString (_text);
  256. if (oldText == value)
  257. return;
  258. var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
  259. if (newText.Cancel) {
  260. if (_cursorPosition > _text.Count) {
  261. _cursorPosition = _text.Count;
  262. }
  263. return;
  264. }
  265. ClearAllSelection ();
  266. _text = newText.NewText.EnumerateRunes ().ToList ();
  267. if (!Secret && !_historyText.IsFromHistory) {
  268. _historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCellList (oldText) },
  269. new Point (_cursorPosition, 0));
  270. _historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)
  271. , HistoryText.LineStatus.Replaced);
  272. }
  273. TextChanged?.Invoke (this, new TextChangedEventArgs (oldText));
  274. ProcessAutocomplete ();
  275. if (_cursorPosition > _text.Count) {
  276. _cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
  277. }
  278. Adjust ();
  279. SetNeedsDisplay ();
  280. }
  281. }
  282. /// <summary>
  283. /// Sets the secret property.
  284. /// <remarks>
  285. /// This makes the text entry suitable for entering passwords.
  286. /// </remarks>
  287. /// </summary>
  288. public bool Secret { get; set; }
  289. /// <summary>
  290. /// Sets or gets the current cursor position.
  291. /// </summary>
  292. public virtual int CursorPosition {
  293. get { return _cursorPosition; }
  294. set {
  295. if (value < 0) {
  296. _cursorPosition = 0;
  297. } else if (value > _text.Count) {
  298. _cursorPosition = _text.Count;
  299. } else {
  300. _cursorPosition = value;
  301. }
  302. PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
  303. }
  304. }
  305. /// <summary>
  306. /// Gets the left offset position.
  307. /// </summary>
  308. public int ScrollOffset => _first;
  309. /// <summary>
  310. /// Indicates whatever the text was changed or not.
  311. /// <see langword="true"/> if the text was changed <see langword="false"/> otherwise.
  312. /// </summary>
  313. public bool IsDirty => _historyText.IsDirty (Text);
  314. /// <summary>
  315. /// Indicates whatever the text has history changes or not.
  316. /// <see langword="true"/> if the text has history changes <see langword="false"/> otherwise.
  317. /// </summary>
  318. public bool HasHistoryChanges => _historyText.HasHistoryChanges;
  319. /// <summary>
  320. /// Get the <see cref="ContextMenu"/> for this view.
  321. /// </summary>
  322. public ContextMenu ContextMenu { get; private set; }
  323. /// <summary>
  324. /// Sets the cursor position.
  325. /// </summary>
  326. public override void PositionCursor ()
  327. {
  328. ProcessAutocomplete ();
  329. var col = 0;
  330. for (int idx = _first < 0 ? 0 : _first; idx < _text.Count; idx++) {
  331. if (idx == _cursorPosition)
  332. break;
  333. var cols = _text [idx].GetColumns ();
  334. TextModel.SetCol (ref col, Frame.Width - 1, cols);
  335. }
  336. var pos = _cursorPosition - _first + Math.Min (Frame.X, 0);
  337. var offB = OffSetBackground ();
  338. var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
  339. var thisFrame = BoundsToScreen (Bounds);
  340. if (pos > -1 && col >= pos && pos < Frame.Width + offB
  341. && containerFrame.IntersectsWith (thisFrame)) {
  342. RestoreCursorVisibility ();
  343. Move (col, 0);
  344. } else {
  345. HideCursorVisibility ();
  346. if (pos < 0) {
  347. Move (pos, 0);
  348. } else {
  349. Move (pos - offB, 0);
  350. }
  351. }
  352. }
  353. CursorVisibility _savedCursorVisibility;
  354. void HideCursorVisibility ()
  355. {
  356. if (_desiredCursorVisibility != CursorVisibility.Invisible) {
  357. DesiredCursorVisibility = CursorVisibility.Invisible;
  358. }
  359. }
  360. CursorVisibility _visibility;
  361. void RestoreCursorVisibility ()
  362. {
  363. Application.Driver.GetCursorVisibility (out _visibility);
  364. if (_desiredCursorVisibility != _savedCursorVisibility || _visibility != _savedCursorVisibility) {
  365. DesiredCursorVisibility = _savedCursorVisibility;
  366. }
  367. }
  368. bool _isDrawing = false;
  369. ///<inheritdoc/>
  370. public override void OnDrawContent (Rect contentArea)
  371. {
  372. _isDrawing = true;
  373. var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
  374. SetSelectedStartSelectedLength ();
  375. Driver.SetAttribute (GetNormalColor ());
  376. Move (0, 0);
  377. int p = _first;
  378. int col = 0;
  379. int width = Frame.Width + OffSetBackground ();
  380. var tcount = _text.Count;
  381. var roc = GetReadOnlyColor ();
  382. for (int idx = p; idx < tcount; idx++) {
  383. var rune = _text [idx];
  384. var cols = rune.GetColumns ();
  385. if (idx == _cursorPosition && HasFocus && !Used && _length == 0 && !ReadOnly) {
  386. Driver.SetAttribute (selColor);
  387. } else if (ReadOnly) {
  388. Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : roc);
  389. } else if (!HasFocus && Enabled) {
  390. Driver.SetAttribute (ColorScheme.Focus);
  391. } else if (!Enabled) {
  392. Driver.SetAttribute (roc);
  393. } else {
  394. Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : ColorScheme.Focus);
  395. }
  396. if (col + cols <= width) {
  397. Driver.AddRune ((Secret ? CM.Glyphs.Dot : rune));
  398. }
  399. if (!TextModel.SetCol (ref col, width, cols)) {
  400. break;
  401. }
  402. if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width) {
  403. break;
  404. }
  405. }
  406. Driver.SetAttribute (ColorScheme.Focus);
  407. for (int i = col; i < width; i++) {
  408. Driver.AddRune ((Rune)' ');
  409. }
  410. PositionCursor ();
  411. RenderCaption ();
  412. ProcessAutocomplete ();
  413. _isDrawing = false;
  414. }
  415. private void ProcessAutocomplete ()
  416. {
  417. if (_isDrawing) {
  418. return;
  419. }
  420. if (SelectedLength > 0) {
  421. return;
  422. }
  423. // draw autocomplete
  424. GenerateSuggestions ();
  425. var renderAt = new Point (
  426. Autocomplete.Context.CursorPosition, 0);
  427. Autocomplete.RenderOverlay (renderAt);
  428. }
  429. private void RenderCaption ()
  430. {
  431. if (HasFocus || Caption == null || Caption.Length == 0
  432. || Text?.Length > 0) {
  433. return;
  434. }
  435. var color = new Attribute (CaptionColor, GetNormalColor ().Background);
  436. Driver.SetAttribute (color);
  437. Move (0, 0);
  438. var render = Caption;
  439. if (render.GetColumns () > Bounds.Width) {
  440. render = render [..Bounds.Width];
  441. }
  442. Driver.AddStr (render);
  443. }
  444. private void GenerateSuggestions ()
  445. {
  446. var currentLine = TextModel.ToRuneCellList (Text);
  447. var cursorPosition = Math.Min (this.CursorPosition, currentLine.Count);
  448. Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition,
  449. Autocomplete.Context != null ? Autocomplete.Context.Canceled : false);
  450. Autocomplete.GenerateSuggestions (
  451. Autocomplete.Context);
  452. }
  453. /// <inheritdoc/>
  454. public override Attribute GetNormalColor ()
  455. {
  456. return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
  457. }
  458. Attribute GetReadOnlyColor ()
  459. {
  460. if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {
  461. return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background);
  462. }
  463. return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background);
  464. }
  465. void Adjust ()
  466. {
  467. if (!IsAdded) {
  468. return;
  469. }
  470. int offB = OffSetBackground ();
  471. bool need = NeedsDisplay || !Used;
  472. if (_cursorPosition < _first) {
  473. _first = _cursorPosition;
  474. need = true;
  475. } else if (Frame.Width > 0 && (_first + _cursorPosition - (Frame.Width + offB) == 0 ||
  476. TextModel.DisplaySize (_text, _first, _cursorPosition).size >= Frame.Width + offB)) {
  477. _first = Math.Max (TextModel.CalculateLeftColumn (_text, _first,
  478. _cursorPosition, Frame.Width + offB), 0);
  479. need = true;
  480. }
  481. if (need) {
  482. SetNeedsDisplay ();
  483. } else {
  484. PositionCursor ();
  485. }
  486. }
  487. int OffSetBackground ()
  488. {
  489. int offB = 0;
  490. if (SuperView?.Frame.Right - Frame.Right < 0) {
  491. offB = SuperView.Frame.Right - Frame.Right - 1;
  492. }
  493. return offB;
  494. }
  495. void SetText (List<Rune> newText)
  496. {
  497. Text = StringExtensions.ToString (newText);
  498. }
  499. void SetText (IEnumerable<Rune> newText)
  500. {
  501. SetText (newText.ToList ());
  502. }
  503. ///<inheritdoc/>
  504. public override bool CanFocus {
  505. get => base.CanFocus;
  506. set { base.CanFocus = value; }
  507. }
  508. void SetClipboard (IEnumerable<Rune> text)
  509. {
  510. if (!Secret)
  511. Clipboard.Contents = StringExtensions.ToString (text.ToList ());
  512. }
  513. int _preTextChangedCursorPos;
  514. ///<inheritdoc/>
  515. public override bool? OnInvokingKeyBindings (Key a)
  516. {
  517. // Give autocomplete first opportunity to respond to key presses
  518. if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) {
  519. return true;
  520. }
  521. return base.OnInvokingKeyBindings (a);
  522. }
  523. /// TODO: Flush out these docs
  524. /// <summary>
  525. /// Processes key presses for the <see cref="TextField"/>.
  526. /// <remarks>
  527. /// The <see cref="TextField"/> control responds to the following keys:
  528. /// <list type="table">
  529. /// <listheader>
  530. /// <term>Keys</term>
  531. /// <description>Function</description>
  532. /// </listheader>
  533. /// <item>
  534. /// <term><see cref="Key.Delete"/>, <see cref="Key.Backspace"/></term>
  535. /// <description>Deletes the character before cursor.</description>
  536. /// </item>
  537. /// </list>
  538. /// </remarks>
  539. /// </summary>
  540. /// <param name="a"></param>
  541. /// <returns></returns>
  542. public override bool OnProcessKeyDown (Key a)
  543. {
  544. // Remember the cursor position because the new calculated cursor position is needed
  545. // to be set BEFORE the TextChanged event is triggered.
  546. // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
  547. _preTextChangedCursorPos = _cursorPosition;
  548. // Ignore other control characters.
  549. if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) {
  550. return false;
  551. }
  552. if (ReadOnly) {
  553. return true;
  554. }
  555. InsertText (a, true);
  556. return true;
  557. }
  558. void InsertText (Key a, bool usePreTextChangedCursorPos)
  559. {
  560. _historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
  561. List<Rune> newText = _text;
  562. if (_length > 0) {
  563. newText = DeleteSelectedText ();
  564. _preTextChangedCursorPos = _cursorPosition;
  565. }
  566. if (!usePreTextChangedCursorPos) {
  567. _preTextChangedCursorPos = _cursorPosition;
  568. }
  569. var kbstr = a.AsRune.ToString ().EnumerateRunes ();
  570. if (Used) {
  571. _cursorPosition++;
  572. if (_cursorPosition == newText.Count + 1) {
  573. SetText (newText.Concat (kbstr).ToList ());
  574. } else {
  575. if (_preTextChangedCursorPos > newText.Count) {
  576. _preTextChangedCursorPos = newText.Count;
  577. }
  578. SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count))));
  579. }
  580. } else {
  581. SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0))));
  582. _cursorPosition++;
  583. }
  584. Adjust ();
  585. }
  586. void SetOverwrite (bool overwrite)
  587. {
  588. Used = overwrite;
  589. SetNeedsDisplay ();
  590. }
  591. TextModel GetModel ()
  592. {
  593. var model = new TextModel ();
  594. model.LoadString (Text);
  595. return model;
  596. }
  597. /// <summary>
  598. /// Deletes word backwards.
  599. /// </summary>
  600. public virtual void KillWordBackwards ()
  601. {
  602. ClearAllSelection ();
  603. var newPos = GetModel ().WordBackward (_cursorPosition, 0);
  604. if (newPos == null) return;
  605. if (newPos.Value.col != -1) {
  606. SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)));
  607. _cursorPosition = newPos.Value.col;
  608. }
  609. Adjust ();
  610. }
  611. /// <summary>
  612. /// Deletes word forwards.
  613. /// </summary>
  614. public virtual void KillWordForwards ()
  615. {
  616. ClearAllSelection ();
  617. var newPos = GetModel ().WordForward (_cursorPosition, 0);
  618. if (newPos == null) return;
  619. if (newPos.Value.col != -1) {
  620. SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col)));
  621. }
  622. Adjust ();
  623. }
  624. void MoveWordRight ()
  625. {
  626. ClearAllSelection ();
  627. var newPos = GetModel ().WordForward (_cursorPosition, 0);
  628. if (newPos == null) return;
  629. if (newPos.Value.col != -1)
  630. _cursorPosition = newPos.Value.col;
  631. Adjust ();
  632. }
  633. void MoveWordLeft ()
  634. {
  635. ClearAllSelection ();
  636. var newPos = GetModel ().WordBackward (_cursorPosition, 0);
  637. if (newPos == null) return;
  638. if (newPos.Value.col != -1)
  639. _cursorPosition = newPos.Value.col;
  640. Adjust ();
  641. }
  642. /// <summary>
  643. /// Redoes the latest changes.
  644. /// </summary>
  645. public void Redo ()
  646. {
  647. if (ReadOnly) {
  648. return;
  649. }
  650. _historyText.Redo ();
  651. //if (string.IsNullOrEmpty (Clipboard.Contents))
  652. // return true;
  653. //var clip = TextModel.ToRunes (Clipboard.Contents);
  654. //if (clip == null)
  655. // return true;
  656. //if (point == text.Count) {
  657. // point = text.Count;
  658. // SetText(text.Concat(clip).ToList());
  659. //} else {
  660. // point += clip.Count;
  661. // SetText(text.GetRange(0, oldCursorPos).Concat(clip).Concat(text.GetRange(oldCursorPos, text.Count - oldCursorPos)));
  662. //}
  663. //Adjust ();
  664. }
  665. /// <summary>
  666. /// Undoes the latest changes.
  667. /// </summary>
  668. public void Undo ()
  669. {
  670. if (ReadOnly) {
  671. return;
  672. }
  673. _historyText.Undo ();
  674. }
  675. void KillToStart ()
  676. {
  677. if (ReadOnly)
  678. return;
  679. ClearAllSelection ();
  680. if (_cursorPosition == 0)
  681. return;
  682. SetClipboard (_text.GetRange (0, _cursorPosition));
  683. SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  684. _cursorPosition = 0;
  685. Adjust ();
  686. }
  687. void KillToEnd ()
  688. {
  689. if (ReadOnly)
  690. return;
  691. ClearAllSelection ();
  692. if (_cursorPosition >= _text.Count)
  693. return;
  694. SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
  695. SetText (_text.GetRange (0, _cursorPosition));
  696. Adjust ();
  697. }
  698. void MoveRight ()
  699. {
  700. ClearAllSelection ();
  701. if (_cursorPosition == _text.Count)
  702. return;
  703. _cursorPosition++;
  704. Adjust ();
  705. }
  706. /// <summary>
  707. /// Moves cursor to the end of the typed text.
  708. /// </summary>
  709. public void MoveEnd ()
  710. {
  711. ClearAllSelection ();
  712. _cursorPosition = _text.Count;
  713. Adjust ();
  714. }
  715. void MoveLeft ()
  716. {
  717. ClearAllSelection ();
  718. if (_cursorPosition > 0) {
  719. _cursorPosition--;
  720. Adjust ();
  721. }
  722. }
  723. void MoveWordRightExtend ()
  724. {
  725. if (_cursorPosition < _text.Count) {
  726. int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
  727. var newPos = GetModel ().WordForward (x, 0);
  728. if (newPos == null) return;
  729. if (newPos.Value.col != -1)
  730. _cursorPosition = newPos.Value.col;
  731. PrepareSelection (x, newPos.Value.col - x);
  732. }
  733. }
  734. void MoveWordLeftExtend ()
  735. {
  736. if (_cursorPosition > 0) {
  737. int x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count);
  738. if (x > 0) {
  739. var newPos = GetModel ().WordBackward (x, 0);
  740. if (newPos == null) return;
  741. if (newPos.Value.col != -1)
  742. _cursorPosition = newPos.Value.col;
  743. PrepareSelection (x, newPos.Value.col - x);
  744. }
  745. }
  746. }
  747. void MoveRightExtend ()
  748. {
  749. if (_cursorPosition < _text.Count) {
  750. PrepareSelection (_cursorPosition++, 1);
  751. }
  752. }
  753. void MoveLeftExtend ()
  754. {
  755. if (_cursorPosition > 0) {
  756. PrepareSelection (_cursorPosition--, -1);
  757. }
  758. }
  759. void MoveHome ()
  760. {
  761. ClearAllSelection ();
  762. _cursorPosition = 0;
  763. Adjust ();
  764. }
  765. void MoveEndExtend ()
  766. {
  767. if (_cursorPosition <= _text.Count) {
  768. int x = _cursorPosition;
  769. _cursorPosition = _text.Count;
  770. PrepareSelection (x, _cursorPosition - x);
  771. }
  772. }
  773. void MoveHomeExtend ()
  774. {
  775. if (_cursorPosition > 0) {
  776. int x = _cursorPosition;
  777. _cursorPosition = 0;
  778. PrepareSelection (x, _cursorPosition - x);
  779. }
  780. }
  781. /// <summary>
  782. /// Deletes the character to the left.
  783. /// </summary>
  784. /// <param name="usePreTextChangedCursorPos">If set to <see langword="true">true</see> use the cursor position cached
  785. /// ; otherwise use <see cref="CursorPosition"/>.
  786. /// use .</param>
  787. public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
  788. {
  789. if (ReadOnly)
  790. return;
  791. _historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
  792. if (_length == 0) {
  793. if (_cursorPosition == 0)
  794. return;
  795. if (!usePreTextChangedCursorPos) {
  796. _preTextChangedCursorPos = _cursorPosition;
  797. }
  798. _cursorPosition--;
  799. if (_preTextChangedCursorPos < _text.Count) {
  800. SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos)));
  801. } else {
  802. SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
  803. }
  804. Adjust ();
  805. } else {
  806. var newText = DeleteSelectedText ();
  807. Text = StringExtensions.ToString (newText);
  808. Adjust ();
  809. }
  810. }
  811. /// <summary>
  812. /// Deletes the character to the right.
  813. /// </summary>
  814. public virtual void DeleteCharRight ()
  815. {
  816. if (ReadOnly)
  817. return;
  818. _historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
  819. if (_length == 0) {
  820. if (_text.Count == 0 || _text.Count == _cursorPosition)
  821. return;
  822. SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1))));
  823. Adjust ();
  824. } else {
  825. var newText = DeleteSelectedText ();
  826. Text = StringExtensions.ToString (newText);
  827. Adjust ();
  828. }
  829. }
  830. void ShowContextMenu ()
  831. {
  832. if (_currentCulture != Thread.CurrentThread.CurrentUICulture) {
  833. _currentCulture = Thread.CurrentThread.CurrentUICulture;
  834. ContextMenu.MenuItems = BuildContextMenuBarItem ();
  835. }
  836. ContextMenu.Show ();
  837. }
  838. /// <summary>
  839. /// Selects all text.
  840. /// </summary>
  841. public void SelectAll ()
  842. {
  843. if (_text.Count == 0) {
  844. return;
  845. }
  846. _selectedStart = 0;
  847. MoveEndExtend ();
  848. SetNeedsDisplay ();
  849. }
  850. /// <summary>
  851. /// Deletes all text.
  852. /// </summary>
  853. public void DeleteAll ()
  854. {
  855. if (_text.Count == 0) {
  856. return;
  857. }
  858. _selectedStart = 0;
  859. MoveEndExtend ();
  860. DeleteCharLeft (false);
  861. SetNeedsDisplay ();
  862. }
  863. /// <summary>
  864. /// Start position of the selected text.
  865. /// </summary>
  866. public int SelectedStart {
  867. get => _selectedStart;
  868. set {
  869. if (value < -1) {
  870. _selectedStart = -1;
  871. } else if (value > _text.Count) {
  872. _selectedStart = _text.Count;
  873. } else {
  874. _selectedStart = value;
  875. }
  876. PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
  877. }
  878. }
  879. /// <summary>
  880. /// Length of the selected text.
  881. /// </summary>
  882. public int SelectedLength { get => _length; }
  883. /// <summary>
  884. /// The selected text.
  885. /// </summary>
  886. public string SelectedText {
  887. get => Secret ? null : _selectedText;
  888. private set => _selectedText = value;
  889. }
  890. int _start, _length;
  891. bool _isButtonPressed;
  892. bool _isButtonReleased = true;
  893. ///<inheritdoc/>
  894. public override bool MouseEvent (MouseEvent ev)
  895. {
  896. if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) &&
  897. !ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
  898. !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && !ev.Flags.HasFlag (ContextMenu.MouseFlags)) {
  899. return false;
  900. }
  901. if (!CanFocus) {
  902. return true;
  903. }
  904. if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition) {
  905. SetFocus ();
  906. }
  907. // Give autocomplete first opportunity to respond to mouse clicks
  908. if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) {
  909. return true;
  910. }
  911. if (ev.Flags == MouseFlags.Button1Pressed) {
  912. EnsureHasFocus ();
  913. PositionCursor (ev);
  914. if (_isButtonReleased) {
  915. ClearAllSelection ();
  916. }
  917. _isButtonReleased = true;
  918. _isButtonPressed = true;
  919. } else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed) {
  920. int x = PositionCursor (ev);
  921. _isButtonReleased = false;
  922. PrepareSelection (x);
  923. if (Application.MouseGrabView == null) {
  924. Application.GrabMouse (this);
  925. }
  926. } else if (ev.Flags == MouseFlags.Button1Released) {
  927. _isButtonReleased = true;
  928. _isButtonPressed = false;
  929. Application.UngrabMouse ();
  930. } else if (ev.Flags == MouseFlags.Button1DoubleClicked) {
  931. EnsureHasFocus ();
  932. int x = PositionCursor (ev);
  933. int sbw = x;
  934. if (x == _text.Count || (x > 0 && (char)_text [x - 1].Value != ' ')
  935. || (x > 0 && (char)_text [x].Value == ' ')) {
  936. var newPosBw = GetModel ().WordBackward (x, 0);
  937. if (newPosBw == null) return true;
  938. sbw = newPosBw.Value.col;
  939. }
  940. if (sbw != -1) {
  941. x = sbw;
  942. PositionCursor (x);
  943. }
  944. var newPosFw = GetModel ().WordForward (x, 0);
  945. if (newPosFw == null) return true;
  946. ClearAllSelection ();
  947. if (newPosFw.Value.col != -1 && sbw != -1) {
  948. _cursorPosition = newPosFw.Value.col;
  949. }
  950. PrepareSelection (sbw, newPosFw.Value.col - sbw);
  951. } else if (ev.Flags == MouseFlags.Button1TripleClicked) {
  952. EnsureHasFocus ();
  953. PositionCursor (0);
  954. ClearAllSelection ();
  955. PrepareSelection (0, _text.Count);
  956. } else if (ev.Flags == ContextMenu.MouseFlags) {
  957. ShowContextMenu ();
  958. }
  959. SetNeedsDisplay ();
  960. return true;
  961. void EnsureHasFocus ()
  962. {
  963. if (!HasFocus) {
  964. SetFocus ();
  965. }
  966. }
  967. }
  968. int PositionCursor (MouseEvent ev)
  969. {
  970. // We could also set the cursor position.
  971. int x;
  972. var pX = TextModel.GetColFromX (_text, _first, ev.X);
  973. if (_text.Count == 0) {
  974. x = pX - ev.OfX;
  975. } else {
  976. x = pX;
  977. }
  978. return PositionCursor (x, false);
  979. }
  980. int PositionCursor (int x, bool getX = true)
  981. {
  982. int pX = x;
  983. if (getX) {
  984. pX = TextModel.GetColFromX (_text, _first, x);
  985. }
  986. if (_first + pX > _text.Count) {
  987. _cursorPosition = _text.Count;
  988. } else if (_first + pX < _first) {
  989. _cursorPosition = 0;
  990. } else {
  991. _cursorPosition = _first + pX;
  992. }
  993. return _cursorPosition;
  994. }
  995. void PrepareSelection (int x, int direction = 0)
  996. {
  997. x = x + _first < -1 ? 0 : x;
  998. _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count ? x : _selectedStart;
  999. if (_selectedStart > -1) {
  1000. _length = Math.Abs (x + direction <= _text.Count ? x + direction - _selectedStart : _text.Count - _selectedStart);
  1001. SetSelectedStartSelectedLength ();
  1002. if (_start > -1 && _length > 0) {
  1003. _selectedText = _length > 0 ? StringExtensions.ToString (_text.GetRange (
  1004. _start < 0 ? 0 : _start, _length > _text.Count ? _text.Count : _length)) : "";
  1005. if (_first > _start) {
  1006. _first = _start;
  1007. }
  1008. } else if (_start > -1 && _length == 0) {
  1009. _selectedText = null;
  1010. }
  1011. SetNeedsDisplay ();
  1012. } else if (_length > 0 || _selectedText != null) {
  1013. ClearAllSelection ();
  1014. }
  1015. Adjust ();
  1016. }
  1017. /// <summary>
  1018. /// Clear the selected text.
  1019. /// </summary>
  1020. public void ClearAllSelection ()
  1021. {
  1022. if (_selectedStart == -1 && _length == 0 && string.IsNullOrEmpty (_selectedText)) {
  1023. return;
  1024. }
  1025. _selectedStart = -1;
  1026. _length = 0;
  1027. _selectedText = null;
  1028. _start = 0;
  1029. _length = 0;
  1030. SetNeedsDisplay ();
  1031. }
  1032. void SetSelectedStartSelectedLength ()
  1033. {
  1034. if (SelectedStart > -1 && _cursorPosition < SelectedStart) {
  1035. _start = _cursorPosition;
  1036. } else {
  1037. _start = SelectedStart;
  1038. }
  1039. }
  1040. /// <summary>
  1041. /// Copy the selected text to the clipboard.
  1042. /// </summary>
  1043. public virtual void Copy ()
  1044. {
  1045. if (Secret || _length == 0)
  1046. return;
  1047. Clipboard.Contents = SelectedText;
  1048. }
  1049. /// <summary>
  1050. /// Cut the selected text to the clipboard.
  1051. /// </summary>
  1052. public virtual void Cut ()
  1053. {
  1054. if (ReadOnly || Secret || _length == 0)
  1055. return;
  1056. Clipboard.Contents = SelectedText;
  1057. var newText = DeleteSelectedText ();
  1058. Text = StringExtensions.ToString (newText);
  1059. Adjust ();
  1060. }
  1061. List<Rune> DeleteSelectedText ()
  1062. {
  1063. SetSelectedStartSelectedLength ();
  1064. int selStart = SelectedStart > -1 ? _start : _cursorPosition;
  1065. var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
  1066. StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
  1067. ClearAllSelection ();
  1068. _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
  1069. return newText.ToRuneList ();
  1070. }
  1071. /// <summary>
  1072. /// Paste the selected text from the clipboard.
  1073. /// </summary>
  1074. public virtual void Paste ()
  1075. {
  1076. if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents)) {
  1077. return;
  1078. }
  1079. SetSelectedStartSelectedLength ();
  1080. int selStart = _start == -1 ? CursorPosition : _start;
  1081. string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
  1082. Text = StringExtensions.ToString (_text.GetRange (0, selStart)) +
  1083. cbTxt +
  1084. StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
  1085. _cursorPosition = selStart + cbTxt.GetRuneCount ();
  1086. ClearAllSelection ();
  1087. SetNeedsDisplay ();
  1088. Adjust ();
  1089. }
  1090. /// <summary>
  1091. /// Virtual method that invoke the <see cref="TextChanging"/> event if it's defined.
  1092. /// </summary>
  1093. /// <param name="newText">The new text to be replaced.</param>
  1094. /// <returns>Returns the <see cref="TextChangingEventArgs"/></returns>
  1095. public virtual TextChangingEventArgs OnTextChanging (string newText)
  1096. {
  1097. var ev = new TextChangingEventArgs (newText);
  1098. TextChanging?.Invoke (this, ev);
  1099. return ev;
  1100. }
  1101. CursorVisibility _desiredCursorVisibility = CursorVisibility.Default;
  1102. /// <summary>
  1103. /// Get / Set the wished cursor when the field is focused
  1104. /// </summary>
  1105. public CursorVisibility DesiredCursorVisibility {
  1106. get => _desiredCursorVisibility;
  1107. set {
  1108. if ((_desiredCursorVisibility != value || _visibility != value) && HasFocus) {
  1109. Application.Driver.SetCursorVisibility (value);
  1110. }
  1111. _desiredCursorVisibility = _visibility = value;
  1112. }
  1113. }
  1114. /// <summary>
  1115. /// Inserts the given <paramref name="toAdd"/> text at the current cursor position
  1116. /// exactly as if the user had just typed it
  1117. /// </summary>
  1118. /// <param name="toAdd">Text to add</param>
  1119. /// <param name="useOldCursorPos">Use the previous cursor position.</param>
  1120. public void InsertText (string toAdd, bool useOldCursorPos = true)
  1121. {
  1122. foreach (var ch in toAdd) {
  1123. KeyCode key;
  1124. try {
  1125. key = (KeyCode)ch;
  1126. } catch (Exception) {
  1127. throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
  1128. }
  1129. InsertText (new Key () { KeyCode = key }, useOldCursorPos);
  1130. }
  1131. }
  1132. /// <summary>
  1133. /// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
  1134. /// </summary>
  1135. public void ClearHistoryChanges ()
  1136. {
  1137. _historyText.Clear (Text);
  1138. }
  1139. /// <summary>
  1140. /// Returns <see langword="true"/> if the current cursor position is
  1141. /// at the end of the <see cref="Text"/>. This includes when it is empty.
  1142. /// </summary>
  1143. /// <returns></returns>
  1144. internal bool CursorIsAtEnd ()
  1145. {
  1146. return CursorPosition == Text.Length;
  1147. }
  1148. /// <summary>
  1149. /// Returns <see langword="true"/> if the current cursor position is
  1150. /// at the start of the <see cref="TextField"/>.
  1151. /// </summary>
  1152. /// <returns></returns>
  1153. internal bool CursorIsAtStart ()
  1154. {
  1155. return CursorPosition <= 0;
  1156. }
  1157. }
  1158. /// <summary>
  1159. /// Renders an overlay on another view at a given point that allows selecting
  1160. /// from a range of 'autocomplete' options.
  1161. /// An implementation on a TextField.
  1162. /// </summary>
  1163. public class TextFieldAutocomplete : PopupAutocomplete {
  1164. /// <inheritdoc/>
  1165. protected override void DeleteTextBackwards ()
  1166. {
  1167. ((TextField)HostControl).DeleteCharLeft (false);
  1168. }
  1169. /// <inheritdoc/>
  1170. protected override void InsertText (string accepted)
  1171. {
  1172. ((TextField)HostControl).InsertText (accepted, false);
  1173. }
  1174. /// <inheritdoc/>
  1175. protected override void SetCursorPosition (int column)
  1176. {
  1177. ((TextField)HostControl).CursorPosition = column;
  1178. }
  1179. }
  1180. }