AppendAutocomplete.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. namespace Terminal.Gui {
  5. /// <summary>
  6. /// Autocomplete for a <see cref="TextField"/> which shows suggestions within the box.
  7. /// Displayed suggestions can be completed using the tab key.
  8. /// </summary>
  9. public class AppendAutocomplete : AutocompleteBase {
  10. private TextField textField;
  11. /// <inheritdoc/>
  12. public override View HostControl { get => textField; set => textField = (TextField)value; }
  13. /// <summary>
  14. /// The color used for rendering the appended text. Note that only
  15. /// <see cref="ColorScheme.Normal"/> is used and then only <see cref="Attribute.Foreground"/>
  16. /// (Background comes from <see cref="HostControl"/>).
  17. /// </summary>
  18. public override ColorScheme ColorScheme { get; set; }
  19. /// <summary>
  20. /// Creates a new instance of the <see cref="AppendAutocomplete"/> class.
  21. /// </summary>
  22. public AppendAutocomplete (TextField textField)
  23. {
  24. this.textField = textField;
  25. SelectionKey = Key.Tab;
  26. ColorScheme = new ColorScheme{
  27. Normal = new Attribute(Color.DarkGray,0),
  28. Focus = new Attribute(Color.DarkGray,0),
  29. HotNormal = new Attribute(Color.DarkGray,0),
  30. HotFocus = new Attribute(Color.DarkGray,0),
  31. Disabled = new Attribute(Color.DarkGray,0),
  32. };
  33. }
  34. /// <inheritdoc/>
  35. public override void ClearSuggestions ()
  36. {
  37. base.ClearSuggestions ();
  38. textField.SetNeedsDisplay ();
  39. }
  40. /// <inheritdoc/>
  41. public override bool MouseEvent (MouseEvent me, bool fromHost = false)
  42. {
  43. return false;
  44. }
  45. /// <inheritdoc/>
  46. public override bool ProcessKey (KeyEvent kb)
  47. {
  48. var key = kb.Key;
  49. if (key == SelectionKey) {
  50. return this.AcceptSelectionIfAny ();
  51. } else
  52. if (key == Key.CursorUp) {
  53. return this.CycleSuggestion (1);
  54. } else
  55. if (key == Key.CursorDown) {
  56. return this.CycleSuggestion (-1);
  57. }
  58. else if(key == CloseKey && Suggestions.Any())
  59. {
  60. ClearSuggestions();
  61. _suspendSuggestions = true;
  62. return true;
  63. }
  64. if(char.IsLetterOrDigit((char)kb.KeyValue))
  65. {
  66. _suspendSuggestions = false;
  67. }
  68. return false;
  69. }
  70. bool _suspendSuggestions = false;
  71. /// <inheritdoc/>
  72. public override void GenerateSuggestions (AutocompleteContext context)
  73. {
  74. if(_suspendSuggestions)
  75. {
  76. return;
  77. }
  78. base.GenerateSuggestions (context);
  79. }
  80. /// <summary>
  81. /// Renders the current suggestion into the <see cref="TextField"/>
  82. /// </summary>
  83. public override void RenderOverlay (Point renderAt)
  84. {
  85. if (!this.MakingSuggestion ()) {
  86. return;
  87. }
  88. // draw it like its selected even though its not
  89. Application.Driver.SetAttribute (new Attribute (ColorScheme.Normal.Foreground, textField.ColorScheme.Focus.Background));
  90. textField.Move (textField.Text.Length, 0);
  91. var suggestion = this.Suggestions.ElementAt (this.SelectedIdx);
  92. var fragment = suggestion.Replacement.Substring (suggestion.Remove);
  93. int spaceAvailable = textField.Bounds.Width - textField.Text.ConsoleWidth;
  94. int spaceRequired = fragment.Sum(c=>Rune.ColumnWidth(c));
  95. if(spaceAvailable < spaceRequired)
  96. {
  97. fragment = new string(
  98. fragment.TakeWhile(c=> (spaceAvailable -= Rune.ColumnWidth(c)) >= 0)
  99. .ToArray()
  100. );
  101. }
  102. Application.Driver.AddStr (fragment);
  103. }
  104. /// <summary>
  105. /// Accepts the current autocomplete suggestion displaying in the text box.
  106. /// Returns true if a valid suggestion was being rendered and acceptable or
  107. /// false if no suggestion was showing.
  108. /// </summary>
  109. /// <returns></returns>
  110. internal bool AcceptSelectionIfAny ()
  111. {
  112. if (this.MakingSuggestion ()) {
  113. var insert = this.Suggestions.ElementAt (this.SelectedIdx);
  114. var newText = textField.Text.ToString ();
  115. newText = newText.Substring (0, newText.Length - insert.Remove);
  116. newText += insert.Replacement;
  117. textField.Text = newText;
  118. this.MoveCursorToEnd ();
  119. this.ClearSuggestions ();
  120. return true;
  121. }
  122. return false;
  123. }
  124. internal void MoveCursorToEnd ()
  125. {
  126. textField.ClearAllSelection ();
  127. textField.CursorPosition = textField.Text.Length;
  128. }
  129. internal void SetTextTo (FileSystemInfo fileSystemInfo)
  130. {
  131. var newText = fileSystemInfo.FullName;
  132. if (fileSystemInfo is DirectoryInfo) {
  133. newText += System.IO.Path.DirectorySeparatorChar;
  134. }
  135. textField.Text = newText;
  136. this.MoveCursorToEnd ();
  137. }
  138. internal bool CursorIsAtEnd ()
  139. {
  140. return textField.CursorPosition == textField.Text.Length;
  141. }
  142. /// <summary>
  143. /// Returns true if there is a suggestion that can be made and the control
  144. /// is in a state where user would expect to see auto-complete (i.e. focused and
  145. /// cursor in right place).
  146. /// </summary>
  147. /// <returns></returns>
  148. private bool MakingSuggestion ()
  149. {
  150. return Suggestions.Any () && this.SelectedIdx != -1 && textField.HasFocus && this.CursorIsAtEnd ();
  151. }
  152. private bool CycleSuggestion (int direction)
  153. {
  154. if (this.Suggestions.Count <= 1) {
  155. return false;
  156. }
  157. this.SelectedIdx = (this.SelectedIdx + direction) % this.Suggestions.Count;
  158. if (this.SelectedIdx < 0) {
  159. this.SelectedIdx = this.Suggestions.Count () - 1;
  160. }
  161. textField.SetNeedsDisplay ();
  162. return true;
  163. }
  164. }
  165. }