SingleWordSuggestionGenerator.cs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Rune = System.Rune;
  6. namespace Terminal.Gui {
  7. /// <summary>
  8. /// <see cref="ISuggestionGenerator"/> which suggests from a collection
  9. /// of words those that match the <see cref="AutocompleteContext"/>. You
  10. /// can update <see cref="AllSuggestions"/> at any time to change candidates
  11. /// considered for autocomplete.
  12. /// </summary>
  13. public class SingleWordSuggestionGenerator : ISuggestionGenerator {
  14. /// <summary>
  15. /// The full set of all strings that can be suggested.
  16. /// </summary>
  17. /// <returns></returns>
  18. public virtual List<string> AllSuggestions { get; set; } = new List<string> ();
  19. /// <inheritdoc/>
  20. public IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context)
  21. {
  22. // if there is nothing to pick from
  23. if (AllSuggestions.Count == 0) {
  24. return Enumerable.Empty<Suggestion> ();
  25. }
  26. var currentWord = IdxToWord (context.CurrentLine, context.CursorPosition);
  27. if (string.IsNullOrWhiteSpace (currentWord)) {
  28. return Enumerable.Empty<Suggestion> ();
  29. } else {
  30. return AllSuggestions.Where (o =>
  31. o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) &&
  32. !o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase)
  33. ).Select (o => new Suggestion (currentWord.Length, o))
  34. .ToList ().AsReadOnly ();
  35. }
  36. }
  37. /// <summary>
  38. /// Return true if the given symbol should be considered part of a word
  39. /// and can be contained in matches. Base behavior is to use <see cref="char.IsLetterOrDigit(char)"/>
  40. /// </summary>
  41. /// <param name="rune"></param>
  42. /// <returns></returns>
  43. public virtual bool IsWordChar (Rune rune)
  44. {
  45. return Char.IsLetterOrDigit ((char)rune);
  46. }
  47. /// <summary>
  48. /// <para>
  49. /// Given a <paramref name="line"/> of characters, returns the word which ends at <paramref name="idx"/>
  50. /// or null. Also returns null if the <paramref name="idx"/> is positioned in the middle of a word.
  51. /// </para>
  52. ///
  53. /// <para>
  54. /// Use this method to determine whether autocomplete should be shown when the cursor is at
  55. /// a given point in a line and to get the word from which suggestions should be generated.
  56. /// Use the <paramref name="columnOffset"/> to indicate if search the word at left (negative),
  57. /// at right (positive) or at the current column (zero) which is the default.
  58. /// </para>
  59. /// </summary>
  60. /// <param name="line"></param>
  61. /// <param name="idx"></param>
  62. /// <param name="columnOffset"></param>
  63. /// <returns></returns>
  64. protected virtual string IdxToWord (List<Rune> line, int idx, int columnOffset = 0)
  65. {
  66. StringBuilder sb = new StringBuilder ();
  67. var endIdx = idx;
  68. // get the ending word index
  69. while (endIdx < line.Count) {
  70. if (IsWordChar (line [endIdx])) {
  71. endIdx++;
  72. } else {
  73. break;
  74. }
  75. }
  76. // It isn't a word char then there is no way to autocomplete that word
  77. if (endIdx == idx && columnOffset != 0) {
  78. return null;
  79. }
  80. // we are at the end of a word. Work out what has been typed so far
  81. while (endIdx-- > 0) {
  82. if (IsWordChar (line [endIdx])) {
  83. sb.Insert (0, (char)line [endIdx]);
  84. } else {
  85. break;
  86. }
  87. }
  88. return sb.ToString ();
  89. }
  90. }
  91. }