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 (); }
}