using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using Rune = System.Rune; namespace Terminal.Gui { public interface ISuggestionGenerator { /// /// Populates with all strings in that /// match with the current cursor position/text in the . /// /// The column offset. Current (zero - default), left (negative), right (positive). IEnumerable GenerateSuggestions (List currentLine, int idx); bool IsWordChar (Rune rune); } public class SingleWordSuggestionGenerator : ISuggestionGenerator { /// /// The full set of all strings that can be suggested. /// /// public virtual List AllSuggestions { get; set; } = new List (); public IEnumerable GenerateSuggestions (List currentLine, int idx) { // if there is nothing to pick from if (AllSuggestions.Count == 0) { return Enumerable.Empty(); } var currentWord = IdxToWord (currentLine, idx); if (string.IsNullOrWhiteSpace (currentWord)) { return Enumerable.Empty(); } else { return AllSuggestions.Where (o => o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) && !o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase) ).Select(o=>new Suggestion(currentWord.Length,o)) .ToList ().AsReadOnly (); } } /// /// Return true if the given symbol should be considered part of a word /// and can be contained in matches. Base behavior is to use /// /// /// public virtual bool IsWordChar (Rune rune) { return Char.IsLetterOrDigit ((char)rune); } /// /// /// Given a of characters, returns the word which ends at /// or null. Also returns null if the is positioned in the middle of a word. /// /// /// /// Use this method to determine whether autocomplete should be shown when the cursor is at /// a given point in a line and to get the word from which suggestions should be generated. /// Use the to indicate if search the word at left (negative), /// at right (positive) or at the current column (zero) which is the default. /// /// /// /// /// /// protected virtual string IdxToWord (List line, int idx, int columnOffset = 0) { StringBuilder sb = new StringBuilder (); var endIdx = idx; // get the ending word index while (endIdx < line.Count) { if (IsWordChar (line [endIdx])) { endIdx++; } else { break; } } // It isn't a word char then there is no way to autocomplete that word if (endIdx == idx && columnOffset != 0) { return null; } // we are at the end of a word. Work out what has been typed so far while (endIdx-- > 0) { if (IsWordChar (line [endIdx])) { sb.Insert (0, (char)line [endIdx]); } else { break; } } return sb.ToString (); } } public abstract class AutocompleteBase : IAutocomplete { /// public abstract View HostControl { get; set; } /// public bool PopupInsideContainer { get; set; } public ISuggestionGenerator SuggestionGenerator {get;set;} = new SingleWordSuggestionGenerator(); /// public virtual int MaxWidth { get; set; } = 10; /// public virtual int MaxHeight { get; set; } = 6; /// /// public virtual bool Visible { get; set; } /// public virtual ReadOnlyCollection Suggestions { get; set; } = new ReadOnlyCollection (new Suggestion [0]); /// public virtual int SelectedIdx { get; set; } /// public abstract ColorScheme ColorScheme { get; set; } /// public virtual Key SelectionKey { get; set; } = Key.Enter; /// public virtual Key CloseKey { get; set; } = Key.Esc; /// public virtual Key Reopen { get; set; } = Key.Space | Key.CtrlMask | Key.AltMask; /// public abstract bool MouseEvent (MouseEvent me, bool fromHost = false); /// public abstract bool ProcessKey (KeyEvent kb); /// public abstract void RenderOverlay (Point renderAt); /// > public virtual void ClearSuggestions () { Suggestions = Enumerable.Empty ().ToList ().AsReadOnly (); } /// public virtual void GenerateSuggestions (List currentLine, int idx) { Suggestions = SuggestionGenerator.GenerateSuggestions(currentLine, idx).ToList().AsReadOnly(); EnsureSelectedIdxIsValid (); } /// /// Updates to be a valid index within /// public virtual void EnsureSelectedIdxIsValid () { SelectedIdx = Math.Max (0, Math.Min (Suggestions.Count - 1, SelectedIdx)); } } }