AppendAutocomplete.cs 4.3 KB

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