namespace Terminal.Gui; /// /// Autocomplete for a which shows suggestions within the box. Displayed suggestions can /// be completed using the tab key. /// public class AppendAutocomplete : AutocompleteBase { private bool _suspendSuggestions; private TextField textField; /// Creates a new instance of the class. public AppendAutocomplete (TextField textField) { this.textField = textField; SelectionKey = KeyCode.Tab; ColorScheme = new ColorScheme { Normal = new Attribute (Color.DarkGray, Color.Black), Focus = new Attribute (Color.DarkGray, Color.Black), HotNormal = new Attribute (Color.DarkGray, Color.Black), HotFocus = new Attribute (Color.DarkGray, Color.Black), Disabled = new Attribute (Color.DarkGray, Color.Black) }; } /// /// The color used for rendering the appended text. Note that only is used and /// then only (Background comes from ). /// public override ColorScheme ColorScheme { get; set; } /// public override View HostControl { get => textField; set => textField = (TextField)value; } /// public override void ClearSuggestions () { base.ClearSuggestions (); textField.SetNeedsDraw (); } /// public override void GenerateSuggestions (AutocompleteContext context) { if (_suspendSuggestions) { _suspendSuggestions = false; return; } base.GenerateSuggestions (context); } /// public override bool OnMouseEvent (MouseEventArgs me, bool fromHost = false) { return false; } /// public override bool ProcessKey (Key a) { Key key = a.KeyCode; if (key == SelectionKey) { return AcceptSelectionIfAny (); } if (key == Key.CursorUp) { return CycleSuggestion (1); } if (key == Key.CursorDown) { return CycleSuggestion (-1); } if (key == CloseKey && Suggestions.Any ()) { ClearSuggestions (); _suspendSuggestions = true; return true; } if (char.IsLetterOrDigit ((char)a)) { _suspendSuggestions = false; } return false; } /// Renders the current suggestion into the public override void RenderOverlay (Point renderAt) { if (!MakingSuggestion ()) { return; } // draw it like it's selected, even though it's not textField.SetAttribute ( new Attribute ( ColorScheme.Normal.Foreground, textField.ColorScheme.Focus.Background ) ); textField.Move (textField.Text.Length, 0); Suggestion suggestion = Suggestions.ElementAt (SelectedIdx); string fragment = suggestion.Replacement.Substring (suggestion.Remove); int spaceAvailable = textField.Viewport.Width - textField.Text.GetColumns (); int spaceRequired = fragment.EnumerateRunes ().Sum (c => c.GetColumns ()); if (spaceAvailable < spaceRequired) { fragment = new string ( fragment.TakeWhile (c => (spaceAvailable -= ((Rune)c).GetColumns ()) >= 0) .ToArray () ); } Application.Driver?.AddStr (fragment); } /// /// Accepts the current autocomplete suggestion displaying in the text box. Returns true if a valid suggestion was /// being rendered and acceptable or false if no suggestion was showing. /// /// internal bool AcceptSelectionIfAny () { if (MakingSuggestion ()) { Suggestion insert = Suggestions.ElementAt (SelectedIdx); string newText = textField.Text; newText = newText.Substring (0, newText.Length - insert.Remove); newText += insert.Replacement; textField.Text = newText; textField.MoveEnd (); ClearSuggestions (); return true; } return false; } internal void SetTextTo (FileSystemInfo fileSystemInfo) { string newText = fileSystemInfo.FullName; if (fileSystemInfo is DirectoryInfo) { newText += Path.DirectorySeparatorChar; } textField.Text = newText; textField.MoveEnd (); } private bool CycleSuggestion (int direction) { if (Suggestions.Count <= 1) { return false; } SelectedIdx = (SelectedIdx + direction) % Suggestions.Count; if (SelectedIdx < 0) { SelectedIdx = Suggestions.Count () - 1; } textField.SetNeedsDraw (); return true; } /// /// Returns true if there is a suggestion that can be made and the control is in a state where user would expect /// to see auto-complete (i.e. focused and cursor in right place). /// /// private bool MakingSuggestion () { return Suggestions.Any () && SelectedIdx != -1 && textField.HasFocus && textField.CursorIsAtEnd (); } }