File: SignatureHelp\Controller.Session_ComputeModel.cs
Web Access
Project: src\src\EditorFeatures\Core.Wpf\Microsoft.CodeAnalysis.EditorFeatures.Wpf_dp51ti4n_wpftmp.csproj (Microsoft.CodeAnalysis.EditorFeatures.Wpf)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SignatureHelp;
using Microsoft.VisualStudio.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp
{
    internal partial class Controller
    {
        internal partial class Session
        {
            public void ComputeModel(
                ImmutableArray<ISignatureHelpProvider> providers,
                SignatureHelpTriggerInfo triggerInfo)
            {
                this.Computation.ThreadingContext.ThrowIfNotOnUIThread();
 
                var caretPosition = Controller.TextView.GetCaretPoint(Controller.SubjectBuffer).Value;
                var disconnectedBufferGraph = new DisconnectedBufferGraph(Controller.SubjectBuffer, Controller.TextView.TextBuffer);
 
                // If we've already computed a model, then just use that.  Otherwise, actually
                // compute a new model and send that along.
                Computation.ChainTaskAndNotifyControllerWhenFinished(
                    (model, cancellationToken) => ComputeModelInBackgroundAsync(
                        model, providers, caretPosition, disconnectedBufferGraph,
                        triggerInfo, cancellationToken));
            }
 
            private async Task<Model> ComputeModelInBackgroundAsync(
                Model currentModel,
                ImmutableArray<ISignatureHelpProvider> providers,
                SnapshotPoint caretPosition,
                DisconnectedBufferGraph disconnectedBufferGraph,
                SignatureHelpTriggerInfo triggerInfo,
                CancellationToken cancellationToken)
            {
                try
                {
                    using (Logger.LogBlock(FunctionId.SignatureHelp_ModelComputation_ComputeModelInBackground, cancellationToken))
                    {
                        this.Computation.ThreadingContext.ThrowIfNotOnBackgroundThread();
                        cancellationToken.ThrowIfCancellationRequested();
 
                        var document = Controller.DocumentProvider.GetDocument(caretPosition.Snapshot, cancellationToken);
                        if (document == null)
                        {
                            return currentModel;
                        }
 
                        // Let LSP handle signature help in the cloud scenario
                        if (Controller.SubjectBuffer.IsInLspEditorContext())
                        {
                            return null;
                        }
 
                        if (triggerInfo.TriggerReason == SignatureHelpTriggerReason.RetriggerCommand)
                        {
                            if (currentModel == null)
                            {
                                return null;
                            }
 
                            if (triggerInfo.TriggerCharacter.HasValue &&
                                !currentModel.Provider.IsRetriggerCharacter(triggerInfo.TriggerCharacter.Value))
                            {
                                return currentModel;
                            }
                        }
 
                        // first try to query the providers that can trigger on the specified character
                        var (provider, items) = await SignatureHelpService.GetSignatureHelpAsync(
                            providers,
                            document,
                            caretPosition,
                            triggerInfo,
                            cancellationToken).ConfigureAwait(false);
 
                        if (provider == null)
                        {
                            // No provider produced items. So we can't produce a model
                            return null;
                        }
 
                        if (currentModel != null &&
                            currentModel.Provider == provider &&
                            currentModel.GetCurrentSpanInSubjectBuffer(disconnectedBufferGraph.SubjectBufferSnapshot).Span.Start == items.ApplicableSpan.Start &&
                            currentModel.Items.IndexOf(currentModel.SelectedItem) == items.SelectedItemIndex &&
                            currentModel.SemanticParameterIndex == items.SemanticParameterIndex &&
                            currentModel.SyntacticArgumentCount == items.SyntacticArgumentCount &&
                            currentModel.ArgumentName == items.ArgumentName)
                        {
                            // The new model is the same as the current model.  Return the currentModel
                            // so we keep the active selection.
                            return currentModel;
                        }
 
                        var selectedItem = GetSelectedItem(currentModel, items, provider, out var userSelected);
 
                        var model = new Model(
                            disconnectedBufferGraph,
                            items.ApplicableSpan,
                            provider,
                            items.Items,
                            selectedItem,
                            items.SemanticParameterIndex,
                            items.SyntacticArgumentCount,
                            items.ArgumentName,
                            selectedParameter: 0,
                            userSelected);
 
                        var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
                        var isCaseSensitive = syntaxFactsService == null || syntaxFactsService.IsCaseSensitive;
                        var selection = DefaultSignatureHelpSelector.GetSelection(
                            model.Items,
                            model.SelectedItem,
                            model.UserSelected,
                            model.SemanticParameterIndex,
                            model.SyntacticArgumentCount,
                            model.ArgumentName,
                            isCaseSensitive);
 
                        return model.WithSelectedItem(selection.SelectedItem, selection.UserSelected)
                                    .WithSelectedParameter(selection.SelectedParameter);
                    }
                }
                catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical))
                {
                    throw ExceptionUtilities.Unreachable();
                }
            }
 
            private static SignatureHelpItem GetSelectedItem(Model currentModel, SignatureHelpItems items, ISignatureHelpProvider provider, out bool userSelected)
            {
                // Try to find the most appropriate item in the list to select by default.
 
                // If it's the same provider as the previous model we have, and we had a user-selection,
                // then try to return the user-selection.
                if (currentModel != null && currentModel.Provider == provider && currentModel.UserSelected)
                {
                    var userSelectedItem = items.Items.FirstOrDefault(i => DisplayPartsMatch(i, currentModel.SelectedItem));
                    if (userSelectedItem != null)
                    {
                        userSelected = true;
                        return userSelectedItem;
                    }
                }
 
                userSelected = false;
 
                // If the provider specified a selected item, then pick that one.
                if (items.SelectedItemIndex.HasValue)
                {
                    return items.Items[items.SelectedItemIndex.Value];
                }
 
                SignatureHelpItem lastSelectionOrDefault = null;
                if (currentModel != null && currentModel.Provider == provider)
                {
                    // If the provider did not pick a default, and it's the same provider as the previous
                    // model we have, then try to return the same item that we had before.
                    lastSelectionOrDefault = items.Items.FirstOrDefault(i => DisplayPartsMatch(i, currentModel.SelectedItem));
                }
 
                // Otherwise, just pick the first item we have.
                lastSelectionOrDefault ??= items.Items.First();
 
                return lastSelectionOrDefault;
            }
 
            private static bool DisplayPartsMatch(SignatureHelpItem i1, SignatureHelpItem i2)
                => i1.GetAllParts().SequenceEqual(i2.GetAllParts(), CompareParts);
 
            private static bool CompareParts(TaggedText p1, TaggedText p2)
                => p1.ToString() == p2.ToString();
        }
    }
}