public ListPromptState <T> Show(ListPromptTree <T> tree, int requestedPageSize = 15)
        {
            if (tree is null)
            {
                throw new ArgumentNullException(nameof(tree));
            }

            if (!_console.Profile.Capabilities.Interactive)
            {
                throw new NotSupportedException(
                          "Cannot show selection prompt since the current " +
                          "terminal isn't interactive.");
            }

            if (!_console.Profile.Capabilities.Ansi)
            {
                throw new NotSupportedException(
                          "Cannot show selection prompt since the current " +
                          "terminal does not support ANSI escape sequences.");
            }

            var nodes = tree.Traverse().ToList();
            var state = new ListPromptState <T>(nodes, _strategy.CalculatePageSize(_console, nodes.Count, requestedPageSize));
            var hook  = new ListPromptRenderHook <T>(_console, () => BuildRenderable(state));

            using (new RenderHookScope(_console, hook))
            {
                _console.Cursor.Hide();
                hook.Refresh();

                while (true)
                {
                    var key = _console.Input.ReadKey(true);

                    var result = _strategy.HandleInput(key, state);
                    if (result == ListPromptInputResult.Submit)
                    {
                        break;
                    }

                    if (state.Update(key.Key) || result == ListPromptInputResult.Refresh)
                    {
                        hook.Refresh();
                    }
                }
            }

            hook.Clear();
            _console.Cursor.Show();

            return(state);
        }
        private IRenderable BuildRenderable(ListPromptState <T> state)
        {
            var pageSize     = state.PageSize;
            var middleOfList = pageSize / 2;

            var skip        = 0;
            var take        = state.ItemCount;
            var cursorIndex = state.Index;

            var scrollable = state.ItemCount > pageSize;

            if (scrollable)
            {
                skip = Math.Max(0, state.Index - middleOfList);
                take = Math.Min(pageSize, state.ItemCount - skip);

                if (state.ItemCount - state.Index < middleOfList)
                {
                    // Pointer should be below the end of the list
                    var diff = middleOfList - (state.ItemCount - state.Index);
                    skip       -= diff;
                    take       += diff;
                    cursorIndex = middleOfList + diff;
                }
                else
                {
                    // Take skip into account
                    cursorIndex -= skip;
                }
            }

            // Build the renderable
            return(_strategy.Render(
                       _console,
                       scrollable, cursorIndex,
                       state.Items.Skip(skip).Take(take)
                       .Select((node, index) => (index, node))));
        }
        /// <inheritdoc/>
        ListPromptInputResult IListPromptStrategy <T> .HandleInput(ConsoleKeyInfo key, ListPromptState <T> state)
        {
            if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Spacebar)
            {
                // Selecting a non leaf in "leaf mode" is not allowed
                if (state.Current.IsGroup && Mode == SelectionMode.Leaf)
                {
                    return(ListPromptInputResult.None);
                }

                return(ListPromptInputResult.Submit);
            }

            return(ListPromptInputResult.None);
        }
        /// <inheritdoc/>
        ListPromptInputResult IListPromptStrategy <T> .HandleInput(ConsoleKeyInfo key, ListPromptState <T> state)
        {
            if (key.Key == ConsoleKey.Enter)
            {
                if (Required && state.Items.None(x => x.Selected))
                {
                    // Selection not permitted
                    return(ListPromptInputResult.None);
                }

                // Submit
                return(ListPromptInputResult.Submit);
            }

            if (key.Key == ConsoleKey.Spacebar)
            {
                var current = state.Items[state.Index];
                var select  = !current.Selected;

                if (Mode == SelectionMode.Leaf)
                {
                    // Select the node and all it's children
                    foreach (var item in current.Traverse(includeSelf: true))
                    {
                        item.Selected = select;
                    }

                    // Visit every parent and evaluate if it's selection
                    // status need to be updated
                    var parent = current.Parent;
                    while (parent != null)
                    {
                        parent.Selected = parent.Traverse(includeSelf: false).All(x => x.Selected);
                        parent          = parent.Parent;
                    }
                }
                else
                {
                    current.Selected = !current.Selected;
                }

                // Refresh the list
                return(ListPromptInputResult.Refresh);
            }

            return(ListPromptInputResult.None);
        }