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