diff --git a/Terminal.Gui/Core/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Core/Autocomplete/AppendAutocomplete.cs index 2b7eb9244..b8b48d0fe 100644 --- a/Terminal.Gui/Core/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Core/Autocomplete/AppendAutocomplete.cs @@ -8,43 +8,33 @@ namespace Terminal.Gui { /// Autocomplete for a which shows suggestions within the box. /// Displayed suggestions can be completed using the tab key. /// - public class AppendAutocomplete : IAutocomplete { + public class AppendAutocomplete : AutocompleteBase { private int? currentFragment = null; private string [] validFragments = new string [0]; private TextField textField; - public View HostControl { get => textField; set => textField = (TextField)value; } - public bool PopupInsideContainer { get; set; } - public int MaxWidth { get; set; } - public int MaxHeight { get; set; } - public bool Visible { get; set; } - public ReadOnlyCollection Suggestions { get; set; } - public List AllSuggestions { get; set; } - public int SelectedIdx { get; set; } - public ColorScheme ColorScheme { get; set; } - public Key SelectionKey { get; set; } = Key.Tab; - public Key CloseKey { get; set; } - public Key Reopen { get; set; } + public override View HostControl { get => textField; set => textField = (TextField)value; } + public override ColorScheme ColorScheme { get; set; } - public void ClearSuggestions () + public override void ClearSuggestions () { this.currentFragment = null; this.validFragments = new string [0]; textField.SetNeedsDisplay (); } - public void GenerateSuggestions (int columnOffset = 0) + public override void GenerateSuggestions (int columnOffset = 0) { validFragments = new string []{ "fish", "flipper", "fun" }; } - public bool MouseEvent (MouseEvent me, bool fromHost = false) + public override bool MouseEvent (MouseEvent me, bool fromHost = false) { return false; } - public bool ProcessKey (KeyEvent kb) + public override bool ProcessKey (KeyEvent kb) { var key = kb.Key; if (key == SelectionKey) { @@ -60,7 +50,7 @@ namespace Terminal.Gui { return false; } - public void RenderOverlay (Point renderAt) + public override void RenderOverlay (Point renderAt) { if (!this.MakingSuggestion ()) { return; @@ -142,5 +132,10 @@ namespace Terminal.Gui { textField.SetNeedsDisplay (); return true; } + + protected override string GetCurrentWord (int columnOffset = 0) + { + throw new System.NotImplementedException (); + } } } diff --git a/Terminal.Gui/Core/Autocomplete/AutocompleteBase.cs b/Terminal.Gui/Core/Autocomplete/AutocompleteBase.cs new file mode 100644 index 000000000..ed16a320d --- /dev/null +++ b/Terminal.Gui/Core/Autocomplete/AutocompleteBase.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using Rune = System.Rune; + +namespace Terminal.Gui { + public abstract class AutocompleteBase : IAutocomplete + { + public abstract View HostControl { get; set; } + public bool PopupInsideContainer { get; set; } + + /// + /// The maximum width of the autocomplete dropdown + /// + public virtual int MaxWidth { get; set; } = 10; + + /// + /// The maximum number of visible rows in the autocomplete dropdown to render + /// + public virtual int MaxHeight { get; set; } = 6; + + /// + + + /// + /// True if the autocomplete should be considered open and visible + /// + public virtual bool Visible { get; set; } + + /// + /// The strings that form the current list of suggestions to render + /// based on what the user has typed so far. + /// + public virtual ReadOnlyCollection Suggestions { get; set; } = new ReadOnlyCollection (new string [0]); + + /// + /// The full set of all strings that can be suggested. + /// + /// + public virtual List AllSuggestions { get; set; } = new List (); + + /// + /// The currently selected index into that the user has highlighted + /// + 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); + + /// + /// Clears + /// + public virtual void ClearSuggestions () + { + Suggestions = Enumerable.Empty ().ToList ().AsReadOnly (); + } + + + /// + /// Populates with all strings in that + /// match with the current cursor position/text in the + /// + /// The column offset. + public virtual void GenerateSuggestions (int columnOffset = 0) + { + // if there is nothing to pick from + if (AllSuggestions.Count == 0) { + ClearSuggestions (); + return; + } + + var currentWord = GetCurrentWord (columnOffset); + + if (string.IsNullOrWhiteSpace (currentWord)) { + ClearSuggestions (); + } else { + Suggestions = AllSuggestions.Where (o => + o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) && + !o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase) + ).ToList ().AsReadOnly (); + + EnsureSelectedIdxIsValid (); + } + } + + /// + /// Updates to be a valid index within + /// + public virtual void EnsureSelectedIdxIsValid () + { + SelectedIdx = Math.Max (0, Math.Min (Suggestions.Count - 1, SelectedIdx)); + } + + /// + /// 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); + } + + + /// + /// Returns the currently selected word from the . + /// + /// When overriding this method views can make use of + /// + /// + /// The column offset. + /// + protected abstract string GetCurrentWord (int columnOffset = 0); + + /// + /// + /// 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 (); + } + } +} + diff --git a/Terminal.Gui/Core/Autocomplete/Autocomplete.cs b/Terminal.Gui/Core/Autocomplete/PopupAutocomplete.cs similarity index 69% rename from Terminal.Gui/Core/Autocomplete/Autocomplete.cs rename to Terminal.Gui/Core/Autocomplete/PopupAutocomplete.cs index 9b225e5b8..827248d4c 100644 --- a/Terminal.Gui/Core/Autocomplete/Autocomplete.cs +++ b/Terminal.Gui/Core/Autocomplete/PopupAutocomplete.cs @@ -11,12 +11,12 @@ namespace Terminal.Gui { /// Renders an overlay on another view at a given point that allows selecting /// from a range of 'autocomplete' options. /// - public abstract class Autocomplete : IAutocomplete { + public abstract class PopupAutocomplete : AutocompleteBase { private class Popup : View { - Autocomplete autocomplete; + PopupAutocomplete autocomplete; - public Popup (Autocomplete autocomplete) + public Popup (PopupAutocomplete autocomplete) { this.autocomplete = autocomplete; CanFocus = true; @@ -61,7 +61,7 @@ namespace Terminal.Gui { /// /// The host control to handle. /// - public virtual View HostControl { + public override View HostControl { get => hostControl; set { hostControl = value; @@ -74,6 +74,11 @@ namespace Terminal.Gui { } } + public PopupAutocomplete () + { + PopupInsideContainer = true; + } + private void Top_Removed (object sender, SuperViewChangedEventArgs e) { Visible = false; @@ -112,42 +117,6 @@ namespace Terminal.Gui { } } - /// - /// Gets or sets If the popup is displayed inside or outside the host limits. - /// - public bool PopupInsideContainer { get; set; } = true; - - /// - /// The maximum width of the autocomplete dropdown - /// - public virtual int MaxWidth { get; set; } = 10; - - /// - /// The maximum number of visible rows in the autocomplete dropdown to render - /// - public virtual int MaxHeight { get; set; } = 6; - - /// - /// True if the autocomplete should be considered open and visible - /// - public virtual bool Visible { get; set; } - - /// - /// The strings that form the current list of suggestions to render - /// based on what the user has typed so far. - /// - public virtual ReadOnlyCollection Suggestions { get; set; } = new ReadOnlyCollection (new string [0]); - - /// - /// The full set of all strings that can be suggested. - /// - /// - public virtual List AllSuggestions { get; set; } = new List (); - - /// - /// The currently selected index into that the user has highlighted - /// - public virtual int SelectedIdx { get; set; } /// /// When more suggestions are available than can be rendered the user @@ -160,7 +129,7 @@ namespace Terminal.Gui { /// The colors to use to render the overlay. Accessing this property before /// the Application has been initialized will cause an error /// - public virtual ColorScheme ColorScheme { + public override ColorScheme ColorScheme { get { if (colorScheme == null) { colorScheme = Colors.Menu; @@ -172,27 +141,12 @@ namespace Terminal.Gui { } } - /// - /// The key that the user must press to accept the currently selected autocomplete suggestion - /// - public virtual Key SelectionKey { get; set; } = Key.Enter; - - /// - /// The key that the user can press to close the currently popped autocomplete menu - /// - public virtual Key CloseKey { get; set; } = Key.Esc; - - /// - /// The key that the user can press to reopen the currently popped autocomplete menu - /// - public virtual Key Reopen { get; set; } = Key.Space | Key.CtrlMask | Key.AltMask; - /// /// Renders the autocomplete dialog inside or outside the given at the /// given point. /// /// - public virtual void RenderOverlay (Point renderAt) + public override void RenderOverlay (Point renderAt) { if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0) { LastPopupPos = null; @@ -294,12 +248,10 @@ namespace Terminal.Gui { } } - /// - /// Updates to be a valid index within - /// - public virtual void EnsureSelectedIdxIsValid () + public override void EnsureSelectedIdxIsValid () { - SelectedIdx = Math.Max (0, Math.Min (Suggestions.Count - 1, SelectedIdx)); + base.EnsureSelectedIdxIsValid (); + // if user moved selection up off top of current scroll window if (SelectedIdx < ScrollOffset) { @@ -311,7 +263,6 @@ namespace Terminal.Gui { ScrollOffset++; } } - /// /// Handle key events before e.g. to make key events like /// up/down apply to the autocomplete control instead of changing the cursor position in @@ -319,7 +270,7 @@ namespace Terminal.Gui { /// /// The key event. /// trueif the key can be handled falseotherwise. - public virtual bool ProcessKey (KeyEvent kb) + public override bool ProcessKey (KeyEvent kb) { if (IsWordChar ((char)kb.Key)) { Visible = true; @@ -381,7 +332,7 @@ namespace Terminal.Gui { /// The mouse event. /// If was called from the popup or from the host. /// trueif the mouse can be handled falseotherwise. - public virtual bool MouseEvent (MouseEvent me, bool fromHost = false) + public override bool MouseEvent (MouseEvent me, bool fromHost = false) { if (fromHost) { if (!Visible) { @@ -450,53 +401,7 @@ namespace Terminal.Gui { } } - /// - /// Clears - /// - public virtual void ClearSuggestions () - { - Suggestions = Enumerable.Empty ().ToList ().AsReadOnly (); - } - - - /// - /// Populates with all strings in that - /// match with the current cursor position/text in the - /// - /// The column offset. - public virtual void GenerateSuggestions (int columnOffset = 0) - { - // if there is nothing to pick from - if (AllSuggestions.Count == 0) { - ClearSuggestions (); - return; - } - - var currentWord = GetCurrentWord (columnOffset); - - if (string.IsNullOrWhiteSpace (currentWord)) { - ClearSuggestions (); - } else { - Suggestions = AllSuggestions.Where (o => - o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) && - !o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase) - ).ToList ().AsReadOnly (); - - EnsureSelectedIdxIsValid (); - } - } - - - /// - /// 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); - } + /// /// Completes the autocomplete selection process. Called when user hits the . @@ -541,62 +446,6 @@ namespace Terminal.Gui { return false; } - /// - /// Returns the currently selected word from the . - /// - /// When overriding this method views can make use of - /// - /// - /// The column offset. - /// - protected abstract string GetCurrentWord (int columnOffset = 0); - - /// - /// - /// 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 (); - } /// /// Deletes the text backwards before insert the selected text in the . @@ -664,3 +513,4 @@ namespace Terminal.Gui { } } } + diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index d4171ea8e..0df6149a4 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1315,7 +1315,7 @@ namespace Terminal.Gui { /// from a range of 'autocomplete' options. /// An implementation on a TextField. /// - public class TextFieldAutocomplete : Autocomplete { + public class TextFieldAutocomplete : PopupAutocomplete { /// protected override void DeleteTextBackwards () diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index fe5a3abe5..37dbe2998 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -1146,7 +1146,7 @@ namespace Terminal.Gui { /// /// Provides autocomplete context menu based on suggestions at the current cursor - /// position. Populate to enable this feature + /// position. Populate to enable this feature /// public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete (); @@ -1734,7 +1734,6 @@ namespace Terminal.Gui { } Height = 1; LayoutStyle = lyout; - Autocomplete.PopupInsideContainer = false; SetNeedsDisplay (); } else if (multiline && savedHeight != null) { var lyout = LayoutStyle; @@ -1743,7 +1742,6 @@ namespace Terminal.Gui { } Height = savedHeight; LayoutStyle = lyout; - Autocomplete.PopupInsideContainer = true; SetNeedsDisplay (); } } @@ -4425,7 +4423,7 @@ namespace Terminal.Gui { /// from a range of 'autocomplete' options. /// An implementation on a TextView. /// - public class TextViewAutocomplete : Autocomplete { + public class TextViewAutocomplete : PopupAutocomplete { /// protected override string GetCurrentWord (int columnOffset = 0)