File: Completion\CompletionList.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Completion;
 
/// <summary>
/// The set of completions to present to the user.
/// </summary>
public sealed class CompletionList
{
    private readonly Lazy<ImmutableArray<CompletionItem>> _lazyItems;
 
    /// <summary>
    /// The completion items to present to the user.
    /// </summary>
    [Obsolete($"This property is obsolete. Use {nameof(ItemsList)} instead", error: false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public ImmutableArray<CompletionItem> Items => _lazyItems.Value;
 
    /// <summary>
    /// The completion items to present to the user.
    /// This property is preferred over `Items` because of the flexibility it provides. 
    /// For example, the list can be backed by types like SegmentedList to avoid LOH allocations.
    /// </summary>
    public IReadOnlyList<CompletionItem> ItemsList { get; }
 
    /// <summary>
    /// The span of the syntax element at the caret position when the <see cref="CompletionList"/> was created.
    /// Individual <see cref="CompletionItem"/> spans may vary.
    /// </summary>
    [Obsolete("Not used anymore.  CompletionList.Span is used instead.", error: true)]
    public TextSpan DefaultSpan { get; }
 
    /// <summary>
    /// The span of the syntax element at the caret position when the <see cref="CompletionList"/> 
    /// was created.
    /// 
    /// The span identifies the text in the document that is used to filter the initial list 
    /// presented to the user, and typically represents the region of the document that will 
    /// be changed if this item is committed.
    /// The latter is not always the case because each provider is free to make more complex changes 
    /// to the document. If this is the case, <see cref="CompletionItem.IsComplexTextEdit"/> must be
    /// set to <see langword="true"/>.
    /// </summary>
    public TextSpan Span { get; }
 
    /// <summary>
    /// The rules used to control behavior of the completion list shown to the user during typing.
    /// </summary>
    public CompletionRules Rules { get; }
 
    /// <summary>
    /// An optional <see cref="CompletionItem"/> that appears selected in the list presented to the user during suggestion mode.
    /// Suggestion mode disables auto-selection of items in the list, giving preference to the text typed by the user unless a specific item is selected manually.
    /// Specifying a <see cref="SuggestionModeItem"/> is a request that the completion host operate in suggestion mode.
    /// The item specified determines the text displayed and the description associated with it unless a different item is manually selected.
    /// No text is ever inserted when this item is completed, leaving the text the user typed instead.
    /// </summary>
    public CompletionItem? SuggestionModeItem { get; }
 
    /// <summary>
    /// Whether the items in this list should be the only items presented to the user.
    /// </summary>
    internal bool IsExclusive { get; }
 
    private CompletionList(
        TextSpan defaultSpan,
        IReadOnlyList<CompletionItem> itemsList,
        CompletionRules? rules,
        CompletionItem? suggestionModeItem,
        bool isExclusive)
    {
        Span = defaultSpan;
        ItemsList = itemsList;
        _lazyItems = new(() => ItemsList.ToImmutableArrayOrEmpty(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
 
        Rules = rules ?? CompletionRules.Default;
        SuggestionModeItem = suggestionModeItem;
        IsExclusive = isExclusive;
 
        foreach (var item in ItemsList)
        {
            item.Span = defaultSpan;
        }
    }
 
    /// <summary>
    /// Creates a new <see cref="CompletionList"/> instance.
    /// </summary>
    /// <param name="defaultSpan">The span of the syntax element at the caret position when the <see cref="CompletionList"/> was created.</param>
    /// <param name="items">The completion items to present to the user.</param>
    /// <param name="rules">The rules used to control behavior of the completion list shown to the user during typing.</param>
    /// <param name="suggestionModeItem">An optional <see cref="CompletionItem"/> that appears selected in the list presented to the user during suggestion mode.</param>
    /// <returns></returns>
    public static CompletionList Create(
        TextSpan defaultSpan,
        ImmutableArray<CompletionItem> items,
        CompletionRules? rules = null,
        CompletionItem? suggestionModeItem = null)
    {
        return Create(defaultSpan, items, rules, suggestionModeItem, isExclusive: false);
    }
 
    internal static CompletionList Create(
        TextSpan defaultSpan,
        IReadOnlyList<CompletionItem> itemsList,
        CompletionRules? rules,
        CompletionItem? suggestionModeItem,
        bool isExclusive)
    {
        return new CompletionList(defaultSpan, itemsList, rules, suggestionModeItem, isExclusive);
    }
 
    private CompletionList With(
        Optional<TextSpan> span = default,
        Optional<IReadOnlyList<CompletionItem>> itemsList = default,
        Optional<CompletionRules> rules = default,
        Optional<CompletionItem> suggestionModeItem = default)
    {
        var newSpan = span.HasValue ? span.Value : Span;
        var newItemsList = itemsList.HasValue ? itemsList.Value : ItemsList;
        var newRules = rules.HasValue ? rules.Value : Rules;
        var newSuggestionModeItem = suggestionModeItem.HasValue ? suggestionModeItem.Value : SuggestionModeItem;
 
        if (newSpan == Span &&
            newItemsList == ItemsList &&
            newRules == Rules &&
            newSuggestionModeItem == SuggestionModeItem)
        {
            return this;
        }
        else
        {
            return Create(newSpan, newItemsList, newRules, newSuggestionModeItem, IsExclusive);
        }
    }
 
    /// <summary>
    /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="DefaultSpan"/> property changed.
    /// </summary>
    [Obsolete("Not used anymore.  Use WithSpan instead.", error: true)]
    public CompletionList WithDefaultSpan(TextSpan span)
        => With(span: span);
 
    public CompletionList WithSpan(TextSpan span)
        => With(span: span);
 
#pragma warning disable RS0030 // Do not used banned APIs
    /// <summary>
    /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="Items"/> property changed.
    /// </summary>
    public CompletionList WithItems(ImmutableArray<CompletionItem> items)
#pragma warning restore RS0030 // Do not used banned APIs
        => With(itemsList: items);
 
    /// <summary>
    /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="ItemsList"/> property changed.
    /// </summary>
    internal CompletionList WithItemsList(IReadOnlyList<CompletionItem> itemsList)
        => With(itemsList: new(itemsList));
 
    /// <summary>
    /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="Rules"/> property changed.
    /// </summary>
    public CompletionList WithRules(CompletionRules rules)
        => With(rules: rules);
 
    /// <summary>
    /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="SuggestionModeItem"/> property changed.
    /// </summary>
    public CompletionList WithSuggestionModeItem(CompletionItem suggestionModeItem)
        => With(suggestionModeItem: suggestionModeItem);
 
    /// <summary>
    /// The default <see cref="CompletionList"/> returned when no items are found to populate the list.
    /// </summary>
    public static readonly CompletionList Empty = new(
        default, [], CompletionRules.Default,
        suggestionModeItem: null, isExclusive: false);
 
    internal bool IsEmpty => ItemsList.Count == 0 && SuggestionModeItem is null;
}