diff --git a/Terminal.Gui/Core/SearchCollectionNavigator.cs b/Terminal.Gui/Core/SearchCollectionNavigator.cs
index 360aab5cf..6c02b9664 100644
--- a/Terminal.Gui/Core/SearchCollectionNavigator.cs
+++ b/Terminal.Gui/Core/SearchCollectionNavigator.cs
@@ -4,25 +4,100 @@ using System.Linq;
namespace Terminal.Gui {
///
- /// Changes the index in a collection based on keys pressed
- /// and the current state
+ /// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string.
+ /// The is used to find the next item in the collection that matches the search string
+ /// when is called.
+ ///
+ /// If the user types keystrokes that can't be found in the collection,
+ /// the search string is cleared and the next item is found that starts with the last keystroke.
+ ///
+ ///
+ /// If the user pauses keystrokes for a short time (250ms), the search string is cleared.
+ ///
///
- class SearchCollectionNavigator {
- string state = "";
- DateTime lastKeystroke = DateTime.MinValue;
- const int TypingDelay = 250;
+ public class SearchCollectionNavigator {
+ ///
+ /// Constructs a new SearchCollectionNavigator.
+ ///
+ public SearchCollectionNavigator () { }
+
+ ///
+ /// Constructs a new SearchCollectionNavigator for the given collection.
+ ///
+ ///
+ public SearchCollectionNavigator (IEnumerable collection) => Collection = collection;
+
+ DateTime lastKeystroke = DateTime.Now;
+ internal int TypingDelay { get; set; } = 250;
+
+ ///
+ /// The compararer function to use when searching the collection.
+ ///
public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
- private IEnumerable Collection { get => _collection; set => _collection = value; }
- private IEnumerable _collection;
+ ///
+ /// The collection of objects to search. is used to search the collection.
+ ///
+ public IEnumerable Collection { get; set; }
- public SearchCollectionNavigator (IEnumerable collection) { _collection = collection; }
+ ///
+ /// Event arguments for the event.
+ ///
+ public class KeystrokeNavigatorEventArgs {
+ ///
+ /// he current .
+ ///
+ public string SearchString { get; }
+ ///
+ /// Initializes a new instance of
+ ///
+ /// The current .
+ public KeystrokeNavigatorEventArgs (string searchString)
+ {
+ SearchString = searchString;
+ }
+ }
- public int CalculateNewIndex (IEnumerable collection, int currentIndex, char keyStruck)
+ ///
+ /// This event is invoked when changes. Useful for debugging.
+ ///
+ public event Action SearchStringChanged;
+
+ private string _searchString = "";
+ ///
+ /// Gets the current search string. This includes the set of keystrokes that have been pressed
+ /// since the last unsuccessful match or after a 250ms delay. Useful for debugging.
+ ///
+ public string SearchString {
+ get => _searchString;
+ private set {
+ _searchString = value;
+ OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value));
+ }
+ }
+
+ ///
+ /// Invoked when the changes. Useful for debugging. Invokes the event.
+ ///
+ ///
+ public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e)
{
- // if user presses a key
- if (!char.IsControl(keyStruck)) {//char.IsLetterOrDigit (keyStruck) || char.IsPunctuation (keyStruck) || char.IsSymbol(keyStruck)) {
+ SearchStringChanged?.Invoke (e);
+ }
+
+ ///
+ /// Gets the index of the next item in the collection that matches the current plus the provided character (typically
+ /// from a key press).
+ ///
+ /// The index in the collection to start the search from.
+ /// The character of the key the user pressed.
+ /// The index of the item that matches what the user has typed.
+ /// Returns if no item in the collection matched.
+ public int GetNextMatchingItem (int currentIndex, char keyStruck)
+ {
+ AssertCollectionIsNotNull ();
+ if (!char.IsControl (keyStruck)) {
// maybe user pressed 'd' and now presses 'd' again.
// a candidate search is things that begin with "dd"
@@ -31,40 +106,39 @@ namespace Terminal.Gui {
string candidateState = "";
// is it a second or third (etc) keystroke within a short time
- if (state.Length > 0 && DateTime.Now - lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) {
+ if (SearchString.Length > 0 && DateTime.Now - lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) {
// "dd" is a candidate
- candidateState = state + keyStruck;
+ candidateState = SearchString + keyStruck;
} else {
// its a fresh keystroke after some time
// or its first ever key press
- state = new string (keyStruck, 1);
+ SearchString = new string (keyStruck, 1);
}
- var idxCandidate = GetNextIndexMatching (collection, currentIndex, candidateState,
+ var idxCandidate = GetNextMatchingItem (currentIndex, candidateState,
// prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart"
candidateState.Length > 1);
if (idxCandidate != -1) {
// found "dd" so candidate state is accepted
lastKeystroke = DateTime.Now;
- state = candidateState;
+ SearchString = candidateState;
return idxCandidate;
}
-
- // nothing matches "dd" so discard it as a candidate
- // and just cycle "d" instead
+ //// nothing matches "dd" so discard it as a candidate
+ //// and just cycle "d" instead
lastKeystroke = DateTime.Now;
- idxCandidate = GetNextIndexMatching (collection, currentIndex, state);
+ idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
// if no changes to current state manifested
if (idxCandidate == currentIndex || idxCandidate == -1) {
// clear history and treat as a fresh letter
ClearState ();
-
+
// match on the fresh letter alone
- state = new string (keyStruck, 1);
- idxCandidate = GetNextIndexMatching (collection, currentIndex, state);
+ SearchString = new string (keyStruck, 1);
+ idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
return idxCandidate == -1 ? currentIndex : idxCandidate;
}
@@ -72,28 +146,35 @@ namespace Terminal.Gui {
return idxCandidate;
} else {
- // clear state because keypress was non letter
+ // clear state because keypress was a control char
ClearState ();
- // no change in index for non letter keystrokes
- return currentIndex;
+ // control char indicates no selection
+ return -1;
}
}
- public int CalculateNewIndex (int currentIndex, char keyStruck)
- {
- return CalculateNewIndex (Collection, currentIndex, keyStruck);
- }
-
- private int GetNextIndexMatching (IEnumerable collection, int currentIndex, string search, bool preferNotToMoveToNewIndexes = false)
+ ///
+ /// Gets the index of the next item in the collection that matches the current
+ ///
+ /// The index in the collection to start the search from.
+ /// The search string to use.
+ /// Set to to stop the search on the first match
+ /// if there are multiple matches for .
+ /// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If (the default),
+ /// the next matching item will be returned, even if it is above in the collection.
+ ///
+ ///
+ internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)
{
if (string.IsNullOrEmpty (search)) {
return -1;
}
+ AssertCollectionIsNotNull ();
// find indexes of items that start with the search text
- int [] matchingIndexes = collection.Select ((item, idx) => (item, idx))
- .Where (k => k.item?.ToString().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false)
+ int [] matchingIndexes = Collection.Select ((item, idx) => (item, idx))
+ .Where (k => k.item?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false)
.Select (k => k.idx)
.ToArray ();
@@ -109,7 +190,7 @@ namespace Terminal.Gui {
} else {
// the current index is part of the matching collection
- if (preferNotToMoveToNewIndexes) {
+ if (minimizeMovement) {
// if we would rather not jump around (e.g. user is typing lots of text to get this match)
return matchingIndexes [currentlySelected];
}
@@ -123,25 +204,29 @@ namespace Terminal.Gui {
return -1;
}
+ private void AssertCollectionIsNotNull ()
+ {
+ if (Collection == null) {
+ throw new InvalidOperationException ("Collection is null");
+ }
+ }
+
private void ClearState ()
{
- state = "";
- lastKeystroke = DateTime.MinValue;
-
+ SearchString = "";
+ lastKeystroke = DateTime.Now;
}
///
/// Returns true if is a searchable key
/// (e.g. letters, numbers etc) that is valid to pass to to this
- /// class for search filtering
+ /// class for search filtering.
///
///
///
public static bool IsCompatibleKey (KeyEvent kb)
{
- // For some reason, at least on Windows/Windows Terminal, `$` is coming through with `IsAlt == true`
return !kb.IsAlt && !kb.IsCapslock && !kb.IsCtrl && !kb.IsScrolllock && !kb.IsNumlock;
- //return !kb.IsCapslock && !kb.IsCtrl && !kb.IsScrolllock && !kb.IsNumlock;
}
}
}
diff --git a/Terminal.Gui/Core/Trees/Branch.cs b/Terminal.Gui/Core/Trees/Branch.cs
index 35a81965a..a6d43cb0b 100644
--- a/Terminal.Gui/Core/Trees/Branch.cs
+++ b/Terminal.Gui/Core/Trees/Branch.cs
@@ -89,8 +89,8 @@ namespace Terminal.Gui.Trees {
public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth)
{
// true if the current line of the tree is the selected one and control has focus
- bool isSelected = tree.IsSelected (Model) && tree.HasFocus;
- Attribute lineColor = isSelected ? colorScheme.Focus : colorScheme.Normal;
+ bool isSelected = tree.IsSelected (Model);// && tree.HasFocus;
+ Attribute lineColor = isSelected ? (tree.HasFocus ? colorScheme.HotFocus : colorScheme.HotNormal) : colorScheme.Normal ;
driver.SetAttribute (lineColor);
@@ -418,7 +418,7 @@ namespace Terminal.Gui.Trees {
/// Expands the current branch and all children branches
///
internal void ExpandAll ()
- {
+ {
Expand ();
if (ChildBranches != null) {
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index 95a96dce6..388def50a 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -59,21 +59,6 @@ namespace Terminal.Gui {
IList ToList ();
}
- ///
- /// Implement to provide custom rendering for a that
- /// supports searching for items.
- ///
- public interface IListDataSourceSearchable : IListDataSource {
- ///
- /// Finds the first item that starts with the specified search string. Used by the default implementation
- /// to support typing the first characters of an item to find it and move the selection to i.
- ///
- /// Text to search for.
- /// The index of the first item that starts with .
- /// Returns if was not found.
- int StartsWith (string search);
- }
-
///
/// ListView renders a scrollable list of data where each item can be activated to perform an action.
///
@@ -87,7 +72,7 @@ namespace Terminal.Gui {
/// By default uses to render the items of any
/// object (e.g. arrays, ,
/// and other collections). Alternatively, an object that implements
- /// or can be provided giving full control of what is rendered.
+ /// can be provided giving full control of what is rendered.
///
///
/// can display any object that implements the interface.
@@ -105,8 +90,7 @@ namespace Terminal.Gui {
/// marking style set to false and implement custom rendering.
///
///
- /// By default or if is set to an object that implements
- /// , searching the ListView with the keyboard is supported. Users type the
+ /// Searching the ListView with the keyboard is supported. Users type the
/// first characters of an item, and the first item that starts with what the user types will be selected.
///
///
@@ -126,7 +110,7 @@ namespace Terminal.Gui {
get => source;
set {
source = value;
- navigator = null;
+ Navigator.Collection = source?.ToList ()?.Cast ();
top = 0;
selected = 0;
lastSelectedItem = -1;
@@ -423,7 +407,10 @@ namespace Terminal.Gui {
///
public event Action RowRender;
- private SearchCollectionNavigator navigator;
+ ///
+ /// Gets the that is used to navigate the when searching.
+ ///
+ public SearchCollectionNavigator Navigator { get; private set; } = new SearchCollectionNavigator ();
///
public override bool ProcessKey (KeyEvent kb)
@@ -436,15 +423,12 @@ namespace Terminal.Gui {
if (result != null) {
return (bool)result;
}
-
+
// Enable user to find & select an item by typing text
if (SearchCollectionNavigator.IsCompatibleKey(kb)) {
- if (navigator == null) {
- navigator = new SearchCollectionNavigator (source.ToList ().Cast ());
- }
- var newItem = navigator.CalculateNewIndex (SelectedItem, (char)kb.KeyValue);
- if (newItem != SelectedItem) {
- SelectedItem = newItem;
+ var newItem = Navigator?.GetNextMatchingItem (SelectedItem, (char)kb.KeyValue);
+ if (newItem is int && newItem != -1) {
+ SelectedItem = (int)newItem;
EnsuresVisibilitySelectedItem ();
SetNeedsDisplay ();
return true;
@@ -829,7 +813,7 @@ namespace Terminal.Gui {
}
///
- public class ListWrapper : IListDataSourceSearchable {
+ public class ListWrapper : IListDataSource {
IList src;
BitArray marks;
int count, len;
diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs
index c6c2e0038..5ccf8b8c1 100644
--- a/Terminal.Gui/Views/TreeView.cs
+++ b/Terminal.Gui/Views/TreeView.cs
@@ -1,5 +1,5 @@
// This code is based on http://objectlistview.sourceforge.net (GPLv3 tree/list controls
-// by phillip.piper@gmail.com). Phillip has explicitly granted permission for his design
+// by phillip.piper@gmail.com). Phillip has explicitly granted permission for his design
// and code to be used in this library under the MIT license.
using NStack;
@@ -12,18 +12,18 @@ using Terminal.Gui.Trees;
namespace Terminal.Gui {
///
- /// Interface for all non generic members of
+ /// Interface for all non generic members of .
///
/// See TreeView Deep Dive for more information .
///
public interface ITreeView {
///
- /// Contains options for changing how the tree is rendered
+ /// Contains options for changing how the tree is rendered.
///
TreeStyle Style { get; set; }
///
- /// Removes all objects from the tree and clears selection
+ /// Removes all objects from the tree and clears selection.
///
void ClearObjects ();
@@ -43,7 +43,7 @@ namespace Terminal.Gui {
///
/// Creates a new instance of the tree control with absolute positioning and initialises
- /// with default based builder
+ /// with default based builder.
///
public TreeView ()
{
@@ -53,8 +53,8 @@ namespace Terminal.Gui {
}
///
- /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
- /// when expanded using a user defined
+ /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
+ /// when expanded using a user defined .
///
/// See TreeView Deep Dive for more information .
///
@@ -64,7 +64,7 @@ namespace Terminal.Gui {
///
/// Determines how sub branches of the tree are dynamically built at runtime as the user
- /// expands root nodes
+ /// expands root nodes.
///
///
public ITreeBuilder TreeBuilder { get; set; }
@@ -74,30 +74,27 @@ namespace Terminal.Gui {
///
T selectedObject;
-
///
- /// Contains options for changing how the tree is rendered
+ /// Contains options for changing how the tree is rendered.
///
public TreeStyle Style { get; set; } = new TreeStyle ();
-
///
- /// True to allow multiple objects to be selected at once
+ /// True to allow multiple objects to be selected at once.
///
///
public bool MultiSelect { get; set; } = true;
-
///
/// True makes a letter key press navigate to the next visible branch that begins with
- /// that letter/digit
+ /// that letter/digit.
///
///
public bool AllowLetterBasedNavigation { get; set; } = true;
///
- /// The currently selected object in the tree. When is true this
- /// is the object at which the cursor is at
+ /// The currently selected object in the tree. When is true this
+ /// is the object at which the cursor is at.
///
public T SelectedObject {
get => selectedObject;
@@ -111,16 +108,15 @@ namespace Terminal.Gui {
}
}
-
///
/// This event is raised when an object is activated e.g. by double clicking or
- /// pressing
+ /// pressing .
///
public event Action> ObjectActivated;
///
/// Key which when pressed triggers .
- /// Defaults to Enter
+ /// Defaults to Enter.
///
public Key ObjectActivationKey {
get => objectActivationKey;
@@ -140,15 +136,14 @@ namespace Terminal.Gui {
///
public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
-
///
- /// Delegate for multi colored tree views. Return the to use
+ /// Delegate for multi colored tree views. Return the to use
/// for each passed object or null to use the default.
///
public Func ColorGetter { get; set; }
///
- /// Secondary selected regions of tree when is true
+ /// Secondary selected regions of tree when is true.
///
private Stack> multiSelectedRegions = new Stack> ();
@@ -157,36 +152,35 @@ namespace Terminal.Gui {
///
private IReadOnlyCollection> cachedLineMap;
-
///
/// Error message to display when the control is not properly initialized at draw time
- /// (nodes added but no tree builder set)
+ /// (nodes added but no tree builder set).
///
public static ustring NoBuilderError = "ERROR: TreeBuilder Not Set";
private Key objectActivationKey = Key.Enter;
///
- /// Called when the changes
+ /// Called when the changes.
///
public event EventHandler> SelectionChanged;
///
- /// The root objects in the tree, note that this collection is of root objects only
+ /// The root objects in the tree, note that this collection is of root objects only.
///
public IEnumerable Objects { get => roots.Keys; }
///
- /// Map of root objects to the branches under them. All objects have
- /// a even if that branch has no children
+ /// Map of root objects to the branches under them. All objects have
+ /// a even if that branch has no children.
///
internal Dictionary> roots { get; set; } = new Dictionary> ();
///
/// The amount of tree view that has been scrolled off the top of the screen (by the user
- /// scrolling down)
+ /// scrolling down).
///
- /// Setting a value of less than 0 will result in a offset of 0. To see changes
- /// in the UI call
+ /// Setting a value of less than 0 will result in a offset of 0. To see changes
+ /// in the UI call .
public int ScrollOffsetVertical {
get => scrollOffsetVertical;
set {
@@ -194,12 +188,11 @@ namespace Terminal.Gui {
}
}
-
///
- /// The amount of tree view that has been scrolled to the right (horizontally)
+ /// The amount of tree view that has been scrolled to the right (horizontally).
///
- /// Setting a value of less than 0 will result in a offset of 0. To see changes
- /// in the UI call
+ /// Setting a value of less than 0 will result in a offset of 0. To see changes
+ /// in the UI call .
public int ScrollOffsetHorizontal {
get => scrollOffsetHorizontal;
set {
@@ -208,24 +201,23 @@ namespace Terminal.Gui {
}
///
- /// The current number of rows in the tree (ignoring the controls bounds)
+ /// The current number of rows in the tree (ignoring the controls bounds).
///
public int ContentHeight => BuildLineMap ().Count ();
///
- /// Returns the string representation of model objects hosted in the tree. Default
- /// implementation is to call
+ /// Returns the string representation of model objects hosted in the tree. Default
+ /// implementation is to call .
///
///
public AspectGetterDelegate AspectGetter { get; set; } = (o) => o.ToString () ?? "";
CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
- private SearchCollectionNavigator searchCollectionNavigator;
///
/// Get / Set the wished cursor when the tree is focused.
/// Only applies when is true.
- /// Defaults to
+ /// Defaults to .
///
public CursorVisibility DesiredCursorVisibility {
get {
@@ -242,9 +234,9 @@ namespace Terminal.Gui {
}
///
- /// Creates a new tree view with absolute positioning.
+ /// Creates a new tree view with absolute positioning.
/// Use to set set root objects for the tree.
- /// Children will not be rendered until you set
+ /// Children will not be rendered until you set .
///
public TreeView () : base ()
{
@@ -301,7 +293,7 @@ namespace Terminal.Gui {
///
/// Initialises .Creates a new tree view with absolute
- /// positioning. Use to set set root
+ /// positioning. Use to set set root
/// objects for the tree.
///
public TreeView (ITreeBuilder builder) : this ()
@@ -318,7 +310,7 @@ namespace Terminal.Gui {
}
///
- /// Adds a new root level object unless it is already a root of the tree
+ /// Adds a new root level object unless it is already a root of the tree.
///
///
public void AddObject (T o)
@@ -330,9 +322,8 @@ namespace Terminal.Gui {
}
}
-
///
- /// Removes all objects from the tree and clears
+ /// Removes all objects from the tree and clears .
///
public void ClearObjects ()
{
@@ -347,7 +338,7 @@ namespace Terminal.Gui {
/// Removes the given root object from the tree
///
/// If is the currently then the
- /// selection is cleared
+ /// selection is cleared.
///
public void Remove (T o)
{
@@ -363,9 +354,9 @@ namespace Terminal.Gui {
}
///
- /// Adds many new root level objects. Objects that are already root objects are ignored
+ /// Adds many new root level objects. Objects that are already root objects are ignored.
///
- /// Objects to add as new root level objects
+ /// Objects to add as new root level objects..\
public void AddObjects (IEnumerable collection)
{
bool objectsAdded = false;
@@ -384,13 +375,13 @@ namespace Terminal.Gui {
}
///
- /// Refreshes the state of the object in the tree. This will
- /// recompute children, string representation etc
+ /// Refreshes the state of the object in the tree. This will
+ /// recompute children, string representation etc.
///
/// This has no effect if the object is not exposed in the tree.
///
/// True to also refresh all ancestors of the objects branch
- /// (starting with the root). False to refresh only the passed node
+ /// (starting with the root). False to refresh only the passed node.
public void RefreshObject (T o, bool startAtTop = false)
{
var branch = ObjectToBranch (o);
@@ -405,7 +396,7 @@ namespace Terminal.Gui {
///
/// Rebuilds the tree structure for all exposed objects starting with the root objects.
/// Call this method when you know there are changes to the tree but don't know which
- /// objects have changed (otherwise use )
+ /// objects have changed (otherwise use ).
///
public void RebuildTree ()
{
@@ -418,10 +409,10 @@ namespace Terminal.Gui {
}
///
- /// Returns the currently expanded children of the passed object. Returns an empty
- /// collection if the branch is not exposed or not expanded
+ /// Returns the currently expanded children of the passed object. Returns an empty
+ /// collection if the branch is not exposed or not expanded.
///
- /// An object in the tree
+ /// An object in the tree.
///
public IEnumerable GetChildren (T o)
{
@@ -434,10 +425,10 @@ namespace Terminal.Gui {
return branch.ChildBranches?.Values?.Select (b => b.Model)?.ToArray () ?? new T [0];
}
///
- /// Returns the parent object of in the tree. Returns null if
- /// the object is not exposed in the tree
+ /// Returns the parent object of in the tree. Returns null if
+ /// the object is not exposed in the tree.
///
- /// An object in the tree
+ /// An object in the tree.
///
public T GetParent (T o)
{
@@ -474,20 +465,19 @@ namespace Terminal.Gui {
Driver.SetAttribute (GetNormalColor ());
Driver.AddStr (new string (' ', bounds.Width));
}
-
}
}
///
/// Returns the index of the object if it is currently exposed (it's
- /// parent(s) have been expanded). This can be used with
- /// and to scroll to a specific object
+ /// parent(s) have been expanded). This can be used with
+ /// and to scroll to a specific object.
///
/// Uses the Equals method and returns the first index at which the object is found
- /// or -1 if it is not found
- /// An object that appears in your tree and is currently exposed
+ /// or -1 if it is not found.
+ /// An object that appears in your tree and is currently exposed.
/// The index the object was found at or -1 if it is not currently revealed or
- /// not in the tree at all
+ /// not in the tree at all.
public int GetScrollOffsetOf (T o)
{
var map = BuildLineMap ();
@@ -502,11 +492,11 @@ namespace Terminal.Gui {
}
///
- /// Returns the maximum width line in the tree including prefix and expansion symbols
+ /// Returns the maximum width line in the tree including prefix and expansion symbols.
///
/// True to consider only rows currently visible (based on window
- /// bounds and . False to calculate the width of
- /// every exposed branch in the tree
+ /// bounds and . False to calculate the width of
+ /// every exposed branch in the tree.
///
public int GetContentWidth (bool visible)
{
@@ -537,7 +527,7 @@ namespace Terminal.Gui {
///
/// Calculates all currently visible/expanded branches (including leafs) and outputs them
- /// by index from the top of the screen
+ /// by index from the top of the screen.
///
/// Index 0 of the returned array is the first item that should be visible in the
/// top of the control, index 1 is the next etc.
@@ -554,7 +544,11 @@ namespace Terminal.Gui {
toReturn.AddRange (AddToLineMap (root));
}
- return cachedLineMap = new ReadOnlyCollection> (toReturn);
+ cachedLineMap = new ReadOnlyCollection> (toReturn);
+
+ // Update the collection used for search-typing
+ Navigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray ();
+ return cachedLineMap;
}
private IEnumerable> AddToLineMap (Branch currentBranch)
@@ -562,7 +556,6 @@ namespace Terminal.Gui {
yield return currentBranch;
if (currentBranch.IsExpanded) {
-
foreach (var subBranch in currentBranch.ChildBranches.Values) {
foreach (var sub in AddToLineMap (subBranch)) {
yield return sub;
@@ -571,6 +564,12 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Gets the that is used to navigate the
+ /// when searching with the keyboard.
+ ///
+ public SearchCollectionNavigator Navigator { get; private set; } = new SearchCollectionNavigator ();
+
///
public override bool ProcessKey (KeyEvent keyEvent)
{
@@ -579,7 +578,6 @@ namespace Terminal.Gui {
}
try {
-
// First of all deal with any registered keybindings
var result = InvokeKeybindings (keyEvent);
if (result != null) {
@@ -588,35 +586,24 @@ namespace Terminal.Gui {
// If not a keybinding, is the key a searchable key press?
if (SearchCollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
-
IReadOnlyCollection> map;
- // If there has been a call to InvalidateMap since the last time we allocated a
- // SearchCollectionNavigator then we need a new one to reflect the new exposed
- // tree state
- if (cachedLineMap == null || searchCollectionNavigator == null) {
- map = BuildLineMap ();
- searchCollectionNavigator = new SearchCollectionNavigator (map.Select (b => AspectGetter (b.Model)).ToArray ());
- }
- else {
- // we still need the map, handily its the cached one which means super fast access
- map = BuildLineMap ();
- }
-
+ // If there has been a call to InvalidateMap since the last time
+ // we need a new one to reflect the new exposed tree state
+ map = BuildLineMap ();
+
// Find the current selected object within the tree
var current = map.IndexOf (b => b.Model == SelectedObject);
- var newIndex = searchCollectionNavigator.CalculateNewIndex (current, (char)keyEvent.KeyValue);
+ var newIndex = Navigator?.GetNextMatchingItem (current, (char)keyEvent.KeyValue);
- if (newIndex != current) {
- SelectedObject = map.ElementAt (newIndex).Model;
+ if (newIndex is int && newIndex != -1) {
+ SelectedObject = map.ElementAt ((int)newIndex).Model;
EnsureVisible (selectedObject);
SetNeedsDisplay ();
return true;
}
}
-
} finally {
-
PositionCursor ();
}
@@ -627,7 +614,7 @@ namespace Terminal.Gui {
///
/// Triggers the event with the .
///
- /// This method also ensures that the selected object is visible
+ /// This method also ensures that the selected object is visible.
///
public void ActivateSelectedObjectIfAny ()
{
@@ -663,11 +650,11 @@ namespace Terminal.Gui {
}
///
- /// Moves the to the next item that begins with
- /// This method will loop back to the start of the tree if reaching the end without finding a match
+ /// Moves the to the next item that begins with .
+ /// This method will loop back to the start of the tree if reaching the end without finding a match.
///
- /// The first character of the next item you want selected
- /// Case sensitivity of the search
+ /// The first character of the next item you want selected.
+ /// Case sensitivity of the search.
public void AdjustSelectionToNextItemBeginningWith (char character, StringComparison caseSensitivity = StringComparison.CurrentCultureIgnoreCase)
{
// search for next branch that begins with that letter
@@ -680,7 +667,7 @@ namespace Terminal.Gui {
///
/// Moves the selection up by the height of the control (1 page).
///
- /// True if the navigation should add the covered nodes to the selected current selection
+ /// True if the navigation should add the covered nodes to the selected current selection.
///
public void MovePageUp (bool expandSelection = false)
{
@@ -690,7 +677,7 @@ namespace Terminal.Gui {
///
/// Moves the selection down by the height of the control (1 page).
///
- /// True if the navigation should add the covered nodes to the selected current selection
+ /// True if the navigation should add the covered nodes to the selected current selection.
///
public void MovePageDown (bool expandSelection = false)
{
@@ -698,7 +685,7 @@ namespace Terminal.Gui {
}
///
- /// Scrolls the view area down a single line without changing the current selection
+ /// Scrolls the view area down a single line without changing the current selection.
///
public void ScrollDown ()
{
@@ -707,7 +694,7 @@ namespace Terminal.Gui {
}
///
- /// Scrolls the view area up a single line without changing the current selection
+ /// Scrolls the view area up a single line without changing the current selection.
///
public void ScrollUp ()
{
@@ -716,7 +703,7 @@ namespace Terminal.Gui {
}
///
- /// Raises the event
+ /// Raises the event.
///
///
protected virtual void OnObjectActivated (ObjectActivatedEventArgs e)
@@ -725,15 +712,15 @@ namespace Terminal.Gui {
}
///
- /// Returns the object in the tree list that is currently visible
- /// at the provided row. Returns null if no object is at that location.
+ /// Returns the object in the tree list that is currently visible.
+ /// at the provided row. Returns null if no object is at that location.
///
///
/// If you have screen coordinates then use
/// to translate these into the client area of the .
///
- /// The row of the of the
- /// The object currently displayed on this row or null
+ /// The row of the of the .
+ /// The object currently displayed on this row or null.
public T GetObjectOnRow (int row)
{
return HitTest (row)?.Model;
@@ -758,7 +745,6 @@ namespace Terminal.Gui {
SetFocus ();
}
-
if (me.Flags == MouseFlags.WheeledDown) {
ScrollDown ();
@@ -814,7 +800,6 @@ namespace Terminal.Gui {
multiSelectedRegions.Clear ();
}
} else {
-
// It is a first click somewhere in the current line that doesn't look like an expansion/collapse attempt
SelectedObject = clickedBranch.Model;
multiSelectedRegions.Clear ();
@@ -844,16 +829,15 @@ namespace Terminal.Gui {
// mouse event is handled.
return true;
}
-
return false;
}
///
/// Returns the branch at the given client
- /// coordinate e.g. following a click event
+ /// coordinate e.g. following a click event.
///
- /// Client Y position in the controls bounds
- /// The clicked branch or null if outside of tree region
+ /// Client Y position in the controls bounds.
+ /// The clicked branch or null if outside of tree region.
private Branch HitTest (int y)
{
var map = BuildLineMap ();
@@ -870,7 +854,7 @@ namespace Terminal.Gui {
}
///
- /// Positions the cursor at the start of the selected objects line (if visible)
+ /// Positions the cursor at the start of the selected objects line (if visible).
///
public override void PositionCursor ()
{
@@ -891,11 +875,10 @@ namespace Terminal.Gui {
}
}
-
///
- /// Determines systems behaviour when the left arrow key is pressed. Default behaviour is
+ /// Determines systems behaviour when the left arrow key is pressed. Default behaviour is
/// to collapse the current tree node if possible otherwise changes selection to current
- /// branches parent
+ /// branches parent.
///
protected virtual void CursorLeft (bool ctrl)
{
@@ -919,7 +902,7 @@ namespace Terminal.Gui {
///
/// Changes the to the first root object and resets
- /// the to 0
+ /// the to 0.
///
public void GoToFirst ()
{
@@ -931,7 +914,7 @@ namespace Terminal.Gui {
///
/// Changes the to the last object in the tree and scrolls so
- /// that it is visible
+ /// that it is visible.
///
public void GoToEnd ()
{
@@ -944,8 +927,8 @@ namespace Terminal.Gui {
///
/// Changes the to and scrolls to ensure
- /// it is visible. Has no effect if is not exposed in the tree (e.g.
- /// its parents are collapsed)
+ /// it is visible. Has no effect if is not exposed in the tree (e.g.
+ /// its parents are collapsed).
///
///
public void GoTo (T toSelect)
@@ -960,14 +943,14 @@ namespace Terminal.Gui {
}
///
- /// The number of screen lines to move the currently selected object by. Supports negative
- /// . Each branch occupies 1 line on screen
+ /// The number of screen lines to move the currently selected object by. Supports negative values.
+ /// . Each branch occupies 1 line on screen.
///
/// If nothing is currently selected or the selected object is no longer in the tree
- /// then the first object in the tree is selected instead
+ /// then the first object in the tree is selected instead.
/// Positive to move the selection down the screen, negative to move it up
/// True to expand the selection (assuming
- /// is enabled). False to replace
+ /// is enabled). False to replace.
public void AdjustSelection (int offset, bool expandSelection = false)
{
// if it is not a shift click or we don't allow multi select
@@ -983,7 +966,6 @@ namespace Terminal.Gui {
var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
if (idx == -1) {
-
// The current selection has disapeared!
SelectedObject = roots.Keys.FirstOrDefault ();
} else {
@@ -1007,14 +989,12 @@ namespace Terminal.Gui {
EnsureVisible (SelectedObject);
}
-
}
-
SetNeedsDisplay ();
}
///
- /// Moves the selection to the first child in the currently selected level
+ /// Moves the selection to the first child in the currently selected level.
///
public void AdjustSelectionToBranchStart ()
{
@@ -1054,7 +1034,7 @@ namespace Terminal.Gui {
}
///
- /// Moves the selection to the last child in the currently selected level
+ /// Moves the selection to the last child in the currently selected level.
///
public void AdjustSelectionToBranchEnd ()
{
@@ -1088,13 +1068,12 @@ namespace Terminal.Gui {
currentBranch = next;
next = map.ElementAt (currentIdx);
}
-
GoToEnd ();
}
///
- /// Sets the selection to the next branch that matches the
+ /// Sets the selection to the next branch that matches the .
///
///
private void AdjustSelectionToNext (Func, bool> predicate)
@@ -1132,7 +1111,7 @@ namespace Terminal.Gui {
///
/// Adjusts the to ensure the given
- /// is visible. Has no effect if already visible
+ /// is visible. Has no effect if already visible.
///
public void EnsureVisible (T model)
{
@@ -1159,7 +1138,7 @@ namespace Terminal.Gui {
}
///
- /// Expands the currently
+ /// Expands the currently .
///
public void Expand ()
{
@@ -1168,9 +1147,9 @@ namespace Terminal.Gui {
///
/// Expands the supplied object if it is contained in the tree (either as a root object or
- /// as an exposed branch object)
+ /// as an exposed branch object).
///
- /// The object to expand
+ /// The object to expand.
public void Expand (T toExpand)
{
if (toExpand == null) {
@@ -1183,9 +1162,9 @@ namespace Terminal.Gui {
}
///
- /// Expands the supplied object and all child objects
+ /// Expands the supplied object and all child objects.
///
- /// The object to expand
+ /// The object to expand.
public void ExpandAll (T toExpand)
{
if (toExpand == null) {
@@ -1198,7 +1177,7 @@ namespace Terminal.Gui {
}
///
/// Fully expands all nodes in the tree, if the tree is very big and built dynamically this
- /// may take a while (e.g. for file system)
+ /// may take a while (e.g. for file system).
///
public void ExpandAll ()
{
@@ -1211,7 +1190,7 @@ namespace Terminal.Gui {
}
///
/// Returns true if the given object is exposed in the tree and can be
- /// expanded otherwise false
+ /// expanded otherwise false.
///
///
///
@@ -1222,7 +1201,7 @@ namespace Terminal.Gui {
///
/// Returns true if the given object is exposed in the tree and
- /// expanded otherwise false
+ /// expanded otherwise false.
///
///
///
@@ -1240,26 +1219,26 @@ namespace Terminal.Gui {
}
///
- /// Collapses the supplied object if it is currently expanded
+ /// Collapses the supplied object if it is currently expanded .
///
- /// The object to collapse
+ /// The object to collapse.
public void Collapse (T toCollapse)
{
CollapseImpl (toCollapse, false);
}
///
- /// Collapses the supplied object if it is currently expanded. Also collapses all children
- /// branches (this will only become apparent when/if the user expands it again)
+ /// Collapses the supplied object if it is currently expanded. Also collapses all children
+ /// branches (this will only become apparent when/if the user expands it again).
///
- /// The object to collapse
+ /// The object to collapse.
public void CollapseAll (T toCollapse)
{
CollapseImpl (toCollapse, true);
}
///
- /// Collapses all root nodes in the tree
+ /// Collapses all root nodes in the tree.
///
public void CollapseAll ()
{
@@ -1272,19 +1251,17 @@ namespace Terminal.Gui {
}
///
- /// Implementation of and . Performs
- /// operation and updates selection if disapeared
+ /// Implementation of and . Performs
+ /// operation and updates selection if disapeared.
///
///
///
protected void CollapseImpl (T toCollapse, bool all)
{
-
if (toCollapse == null) {
return;
}
-
var branch = ObjectToBranch (toCollapse);
// Nothing to collapse
@@ -1317,12 +1294,12 @@ namespace Terminal.Gui {
///
/// Returns the corresponding in the tree for
- /// . This will not work for objects hidden
- /// by their parent being collapsed
+ /// . This will not work for objects hidden
+ /// by their parent being collapsed.
///
///
/// The branch for or null if it is not currently
- /// exposed in the tree
+ /// exposed in the tree.
private Branch ObjectToBranch (T toFind)
{
return BuildLineMap ().FirstOrDefault (o => o.Model.Equals (toFind));
@@ -1330,7 +1307,7 @@ namespace Terminal.Gui {
///
/// Returns true if the is either the
- /// or part of a
+ /// or part of a .
///
///
///
@@ -1365,7 +1342,7 @@ namespace Terminal.Gui {
///
/// Selects all objects in the tree when is enabled otherwise
- /// does nothing
+ /// does nothing.
///
public void SelectAll ()
{
@@ -1387,9 +1364,8 @@ namespace Terminal.Gui {
OnSelectionChanged (new SelectionChangedEventArgs (this, SelectedObject, SelectedObject));
}
-
///
- /// Raises the SelectionChanged event
+ /// Raises the SelectionChanged event.
///
///
protected virtual void OnSelectionChanged (SelectionChangedEventArgs e)
@@ -1431,5 +1407,4 @@ namespace Terminal.Gui {
return included.Contains (model);
}
}
-
}
\ No newline at end of file
diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json
index eda283ad8..e1f2b1db2 100644
--- a/UICatalog/Properties/launchSettings.json
+++ b/UICatalog/Properties/launchSettings.json
@@ -44,6 +44,10 @@
"WSL": {
"commandName": "WSL2",
"distributionName": ""
+ },
+ "SearchCollectionNavigatorTester": {
+ "commandName": "Project",
+ "commandLineArgs": "\"Search Collection Nav\""
}
}
}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs b/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs
index 10feff7c3..80b514893 100644
--- a/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs
+++ b/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs
@@ -11,6 +11,7 @@ namespace UICatalog.Scenarios {
[ScenarioCategory ("Controls"), ScenarioCategory ("TreeView")]
[ScenarioCategory ("Controls"), ScenarioCategory ("Text")]
public class SearchCollectionNavigatorTester : Scenario {
+
// Don't create a Window, just return the top-level view
public override void Init (Toplevel top, ColorScheme colorScheme)
{
@@ -94,7 +95,7 @@ namespace UICatalog.Scenarios {
null,
new MenuItem ("_Quit", "", () => Quit(), null, null, Key.Q | Key.CtrlMask),
}),
- new MenuBarItem("_Quit", "CTRL-Q", () => Quit())
+ new MenuBarItem("_Quit", "CTRL-Q", () => Quit()),
});
Top.Add (menu);
@@ -109,7 +110,6 @@ namespace UICatalog.Scenarios {
};
Top.Add (vsep);
CreateTreeView ();
-
}
ListView _listView = null;
@@ -128,7 +128,7 @@ namespace UICatalog.Scenarios {
_listView = new ListView () {
X = 0,
- Y = Pos.Bottom(label),
+ Y = Pos.Bottom (label),
Width = Dim.Percent (50) - 1,
Height = Dim.Fill (),
AllowsMarking = false,
@@ -136,8 +136,12 @@ namespace UICatalog.Scenarios {
ColorScheme = Colors.TopLevel
};
Top.Add (_listView);
-
+
_listView.SetSource (_items);
+
+ _listView.Navigator.SearchStringChanged += (state) => {
+ label.Text = $"ListView: {state.SearchString}";
+ };
}
TreeView _treeView = null;
@@ -147,7 +151,7 @@ namespace UICatalog.Scenarios {
var label = new Label () {
Text = "TreeView",
TextAlignment = TextAlignment.Centered,
- X = Pos.Right(_listView) + 2,
+ X = Pos.Right (_listView) + 2,
Y = 1, // for menu
Width = Dim.Percent (50),
Height = 1,
@@ -162,15 +166,21 @@ namespace UICatalog.Scenarios {
ColorScheme = Colors.TopLevel
};
Top.Add (_treeView);
-
+
var root = new TreeNode ("Alpha examples");
- //root.Children = items.Where (i => char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast().ToList ();
- //_treeView.AddObject (root);
+ root.Children = _items.Where (i => char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast ().ToList ();
+ _treeView.AddObject (root);
root = new TreeNode ("Non-Alpha examples");
root.Children = _items.Where (i => !char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast ().ToList ();
_treeView.AddObject (root);
_treeView.ExpandAll ();
+ _treeView.GoToFirst ();
+
+ _treeView.Navigator.SearchStringChanged += (state) => {
+ label.Text = $"TreeView: {state.SearchString}";
+ };
}
+
private void Quit ()
{
Application.RequestStop ();
diff --git a/UnitTests/ListViewTests.cs b/UnitTests/ListViewTests.cs
index a9a29543d..3c2d12b4a 100644
--- a/UnitTests/ListViewTests.cs
+++ b/UnitTests/ListViewTests.cs
@@ -151,7 +151,7 @@ namespace Terminal.Gui.Views {
public IList ToList ()
{
- throw new NotImplementedException ();
+ return new List () { "One", "Two", "Three" };
}
}
diff --git a/UnitTests/SearchCollectionNavigatorTests.cs b/UnitTests/SearchCollectionNavigatorTests.cs
index b59f8f734..b7e0a4df8 100644
--- a/UnitTests/SearchCollectionNavigatorTests.cs
+++ b/UnitTests/SearchCollectionNavigatorTests.cs
@@ -1,142 +1,334 @@
-using Terminal.Gui;
+using System.Threading;
using Xunit;
namespace Terminal.Gui.Core {
public class SearchCollectionNavigatorTests {
static string [] simpleStrings = new string []{
- "appricot", // 0
- "arm", // 1
- "bat", // 2
- "batman", // 3
- "candle" // 4
- };
+ "appricot", // 0
+ "arm", // 1
+ "bat", // 2
+ "batman", // 3
+ "candle" // 4
+ };
+
[Fact]
- public void TestSearchCollectionNavigator_ShouldAcceptNegativeOne ()
+ public void ShouldAcceptNegativeOne ()
{
var n = new SearchCollectionNavigator (simpleStrings);
-
+
// Expect that index of -1 (i.e. no selection) should work correctly
// and select the first entry of the letter 'b'
- Assert.Equal (2, n.CalculateNewIndex (-1, 'b'));
+ Assert.Equal (2, n.GetNextMatchingItem (-1, 'b'));
}
[Fact]
- public void TestSearchCollectionNavigator_OutOfBoundsShouldBeIgnored()
+ public void OutOfBoundsShouldBeIgnored ()
{
var n = new SearchCollectionNavigator (simpleStrings);
// Expect saying that index 500 is the current selection should not cause
// error and just be ignored (treated as no selection)
- Assert.Equal (2, n.CalculateNewIndex (500, 'b'));
+ Assert.Equal (2, n.GetNextMatchingItem (500, 'b'));
}
[Fact]
- public void TestSearchCollectionNavigator_Cycling ()
+ public void Cycling ()
{
var n = new SearchCollectionNavigator (simpleStrings);
- Assert.Equal (2, n.CalculateNewIndex ( 0, 'b'));
- Assert.Equal (3, n.CalculateNewIndex ( 2, 'b'));
+ Assert.Equal (2, n.GetNextMatchingItem (0, 'b'));
+ Assert.Equal (3, n.GetNextMatchingItem (2, 'b'));
// if 4 (candle) is selected it should loop back to bat
- Assert.Equal (2, n.CalculateNewIndex ( 4, 'b'));
+ Assert.Equal (2, n.GetNextMatchingItem (4, 'b'));
}
[Fact]
- public void TestSearchCollectionNavigator_ToSearchText ()
+ public void ToSearchText ()
{
var strings = new string []{
- "appricot",
- "arm",
- "bat",
- "batman",
- "bbfish",
- "candle"
- };
+ "appricot",
+ "arm",
+ "bat",
+ "batman",
+ "bbfish",
+ "candle"
+ };
+ int current = 0;
var n = new SearchCollectionNavigator (strings);
- Assert.Equal (2, n.CalculateNewIndex (0, 'b'));
- Assert.Equal (4, n.CalculateNewIndex (2, 'b'));
+ Assert.Equal (2, current = n.GetNextMatchingItem (current, 'b')); // match bat
+ Assert.Equal (4, current = n.GetNextMatchingItem (current, 'b')); // match bbfish
// another 'b' means searching for "bbb" which does not exist
// so we go back to looking for "b" as a fresh key strike
- Assert.Equal (4, n.CalculateNewIndex (2, 'b'));
+ Assert.Equal (2, current = n.GetNextMatchingItem (current, 'b')); // match bat
}
[Fact]
- public void TestSearchCollectionNavigator_FullText ()
+ public void FullText ()
{
var strings = new string []{
- "appricot",
- "arm",
- "ta",
- "target",
- "text",
- "egg",
- "candle"
- };
+ "appricot",
+ "arm",
+ "ta",
+ "target",
+ "text",
+ "egg",
+ "candle"
+ };
var n = new SearchCollectionNavigator (strings);
- Assert.Equal (2, n.CalculateNewIndex (0, 't'));
+ Assert.Equal (2, n.GetNextMatchingItem (0, 't'));
// should match "te" in "text"
- Assert.Equal (4, n.CalculateNewIndex (2, 'e'));
+ Assert.Equal (4, n.GetNextMatchingItem (2, 'e'));
// still matches text
- Assert.Equal (4, n.CalculateNewIndex (4, 'x'));
+ Assert.Equal (4, n.GetNextMatchingItem (4, 'x'));
// nothing starts texa so it jumps to a for appricot
- Assert.Equal (0, n.CalculateNewIndex (4, 'a'));
+ Assert.Equal (0, n.GetNextMatchingItem (4, 'a'));
}
[Fact]
- public void TestSearchCollectionNavigator_Unicode ()
+ public void Unicode ()
{
var strings = new string []{
- "appricot",
- "arm",
- "ta",
- "丗丙业丞",
- "丗丙丛",
- "text",
- "egg",
- "candle"
- };
+ "appricot",
+ "arm",
+ "ta",
+ "丗丙业丞",
+ "丗丙丛",
+ "text",
+ "egg",
+ "candle"
+ };
var n = new SearchCollectionNavigator (strings);
- Assert.Equal (3, n.CalculateNewIndex (0, '丗'));
+ Assert.Equal (3, n.GetNextMatchingItem (0, '丗'));
// 丗丙业丞 is as good a match as 丗丙丛
// so when doing multi character searches we should
// prefer to stay on the same index unless we invalidate
// our typed text
- Assert.Equal (3, n.CalculateNewIndex (3, '丙'));
+ Assert.Equal (3, n.GetNextMatchingItem (3, '丙'));
// No longer matches 丗丙业丞 and now only matches 丗丙丛
// so we should move to the new match
- Assert.Equal (4, n.CalculateNewIndex (3, '丛'));
+ Assert.Equal (4, n.GetNextMatchingItem (3, '丛'));
// nothing starts "丗丙丛a" so it jumps to a for appricot
- Assert.Equal (0, n.CalculateNewIndex (4, 'a'));
+ Assert.Equal (0, n.GetNextMatchingItem (4, 'a'));
}
[Fact]
- public void TestSearchCollectionNavigator_AtSymbol ()
+ public void AtSymbol ()
{
var strings = new string []{
- "appricot",
- "arm",
- "ta",
- "@bob",
- "@bb",
- "text",
- "egg",
- "candle"
- };
+ "appricot",
+ "arm",
+ "ta",
+ "@bob",
+ "@bb",
+ "text",
+ "egg",
+ "candle"
+ };
var n = new SearchCollectionNavigator (strings);
- Assert.Equal (3, n.CalculateNewIndex (0, '@'));
- Assert.Equal (3, n.CalculateNewIndex (3, 'b'));
- Assert.Equal (4, n.CalculateNewIndex (3, 'b'));
+ Assert.Equal (3, n.GetNextMatchingItem (0, '@'));
+ Assert.Equal (3, n.GetNextMatchingItem (3, 'b'));
+ Assert.Equal (4, n.GetNextMatchingItem (3, 'b'));
+ }
+
+ [Fact]
+ public void Word ()
+ {
+ var strings = new string []{
+ "appricot",
+ "arm",
+ "bat",
+ "batman",
+ "bates hotel",
+ "candle"
+ };
+ int current = 0;
+ var n = new SearchCollectionNavigator (strings);
+ Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'b')); // match bat
+ Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'a')); // match bat
+ Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 't')); // match bat
+ Assert.Equal (strings.IndexOf ("bates hotel"), current = n.GetNextMatchingItem (current, 'e')); // match bates hotel
+ Assert.Equal (strings.IndexOf ("bates hotel"), current = n.GetNextMatchingItem (current, 's')); // match bates hotel
+ Assert.Equal (strings.IndexOf ("bates hotel"), current = n.GetNextMatchingItem (current, ' ')); // match bates hotel
+
+ // another 'b' means searching for "bates b" which does not exist
+ // so we go back to looking for "b" as a fresh key strike
+ Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'b')); // match bat
+ }
+
+ [Fact]
+ public void Symbols ()
+ {
+ var strings = new string []{
+ "$$",
+ "$100.00",
+ "$101.00",
+ "$101.10",
+ "$200.00",
+ "appricot"
+ };
+ int current = 0;
+ var n = new SearchCollectionNavigator (strings);
+ Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
+ Assert.Equal ("a", n.SearchString);
+
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$", n.SearchString);
+
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, '1'));
+ Assert.Equal ("$1", n.SearchString);
+
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, '0'));
+ Assert.Equal ("$10", n.SearchString);
+
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, '1'));
+ Assert.Equal ("$101", n.SearchString);
+
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, '.'));
+ Assert.Equal ("$101.", n.SearchString);
+
+ Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
+ Assert.Equal ("a", n.SearchString);
+
+ // another '$' means searching for "$" again
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$", n.SearchString);
+
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$$", n.SearchString);
+
+ }
+
+ [Fact]
+ public void Delay ()
+ {
+ var strings = new string []{
+ "$$",
+ "$100.00",
+ "$101.00",
+ "$101.10",
+ "$200.00",
+ "appricot"
+ };
+ int current = 0;
+ var n = new SearchCollectionNavigator (strings);
+
+ // No delay
+ Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
+ Assert.Equal ("a", n.SearchString);
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$", n.SearchString);
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$$", n.SearchString);
+
+ // Delay
+ Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
+ Assert.Equal ("a", n.SearchString);
+
+ Thread.Sleep (n.TypingDelay + 10);
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$", n.SearchString);
+
+ Thread.Sleep (n.TypingDelay + 10);
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$", n.SearchString);
+
+ Thread.Sleep (n.TypingDelay + 10);
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$", n.SearchString);
+
+ Thread.Sleep (n.TypingDelay + 10);
+ Assert.Equal (strings.IndexOf ("$101.10"), current = n.GetNextMatchingItem (current, '$'));
+ Assert.Equal ("$", n.SearchString);
+
+ Thread.Sleep (n.TypingDelay + 10);
+ Assert.Equal (strings.IndexOf ("$101.10"), current = n.GetNextMatchingItem (current, '2')); // Shouldn't move
+ Assert.Equal ("2", n.SearchString);
+ }
+
+ [Fact]
+ public void MinimizeMovement_False_ShouldMoveIfMultipleMatches ()
+ {
+ var strings = new string [] {
+ "$$",
+ "$100.00",
+ "$101.00",
+ "$101.10",
+ "$200.00",
+ "appricot",
+ "c",
+ "car",
+ "cart",
+ };
+ int current = 0;
+ var n = new SearchCollectionNavigator (strings);
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", false));
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", false));
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", false)); // back to top
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", false));
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$", false));
+ Assert.Equal (strings.IndexOf ("$101.10"), current = n.GetNextMatchingItem (current, "$", false));
+ Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$", false));
+
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$", false)); // back to top
+ Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, "a", false));
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$", false)); // back to top
+
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$100.00", false));
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$", false));
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$101.00", false));
+ Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$2", false));
+
+ Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$200.00", false));
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$101.00", false));
+ Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$2", false));
+
+ Assert.Equal (strings.IndexOf ("$101.00"), current = n.GetNextMatchingItem (current, "$101.00", false));
+ Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$2", false));
+
+ Assert.Equal (strings.IndexOf ("car"), current = n.GetNextMatchingItem (current, "car", false));
+ Assert.Equal (strings.IndexOf ("cart"), current = n.GetNextMatchingItem (current, "car", false));
+
+ Assert.Equal (-1, current = n.GetNextMatchingItem (current, "x", false));
+ }
+
+ [Fact]
+ public void MinimizeMovement_True_ShouldStayOnCurrentIfMultipleMatches ()
+ {
+ var strings = new string [] {
+ "$$",
+ "$100.00",
+ "$101.00",
+ "$101.10",
+ "$200.00",
+ "appricot",
+ "c",
+ "car",
+ "cart",
+ };
+ int current = 0;
+ var n = new SearchCollectionNavigator (strings);
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", true));
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$", true));
+ Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", true)); // back to top
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$1", true));
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", true));
+ Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$", true));
+
+ Assert.Equal (strings.IndexOf ("car"), current = n.GetNextMatchingItem (current, "car", true));
+ Assert.Equal (strings.IndexOf ("car"), current = n.GetNextMatchingItem (current, "car", true));
+
+ Assert.Equal (-1, current = n.GetNextMatchingItem (current, "x", true));
}
}
}