#nullable disable  namespace Terminal.Gui.Views; /// /// 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) { _textField = textField; base.SelectionKey = KeyCode.Tab; Scheme = new Scheme { 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 Scheme Scheme { 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 ( Scheme.Normal.Foreground, _textField.GetAttributeForRole(VisualRole.Focus).Background, Scheme.Normal.Style ) ); _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 () ); } _textField.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 (); } }