private async Task UpdateSnapshot()

in src/Editor/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs [1205:1429]


        private async Task<CompletionModel> UpdateSnapshot(CompletionModel model, CompletionTrigger trigger, SnapshotPoint updateLocation, ITextSnapshot rootSnapshot, int thisId, CancellationToken token)
        {
            // Always record keystrokes, even if filtering is preempted
            _telemetry.RecordKeystroke();

            // Completion got cancelled
            if (token.IsCancellationRequested || model == null)
                return default;

            // Dismiss if we are outside of the applicable span
            var instantaneousSnapshot = updateLocation.Snapshot;
            var currentlyApplicableToSpan = ApplicableToSpan.GetSpan(instantaneousSnapshot);
            if (updateLocation < currentlyApplicableToSpan.Start
                || updateLocation > currentlyApplicableToSpan.End)
            {
                ((IAsyncCompletionSession)this).Dismiss();
                return model;
            }
            // If the applicable to span was empty, is empty again, and user is deleting, then dismiss
            if (currentlyApplicableToSpan.IsEmpty
                && model.ApplicableToSpanWasEmpty
                && (trigger.Reason == CompletionTriggerReason.Deletion || trigger.Reason == CompletionTriggerReason.Backspace))
            {
                _finalSessionState = CompletionSessionState.DismissedDueToBackspace;
                ((IAsyncCompletionSession)this).Dismiss();
                return model;
            }
            // If user is backspacing at the beginning of a span, dismiss
            if (updateLocation == currentlyApplicableToSpan.Start && trigger.Reason == CompletionTriggerReason.Backspace)
            {
                if (_inCaretLocationFallback)
                {
                    // If user was previously at the beginning of the span, this backspace will dismiss completion
                    _finalSessionState = CompletionSessionState.DismissedDueToBackspace;
                    ((IAsyncCompletionSession)this).Dismiss();
                    return model;
                }
                else
                {
                    // Caret just moved to the beginning of the span, enter soft selection
                    _selectionModeBeforeCaretLocationFallback = model.UseSoftSelection;
                    model = model.WithSoftSelection(true);
                    _inCaretLocationFallback = true;
                }
            }

            // Record whether the applicable to span is empty
            model = model.WithApplicableToSpanStatus(currentlyApplicableToSpan.IsEmpty);

            // The model previously received no items, but we are called again because user typed something.
            // There is a chance that language service will provide items this time.
            // Due to timing issues, if we dismiss and start another session, we would miss some user actions.
            // Instead, attempt to get items again within this session.
            if (model.Uninitialized && thisId > 1) // Don't attempt to get items on the very first UpdateSnapshot
            {
                // Attempt to get new completion items
                model = await GetInitialModel(trigger, updateLocation, rootSnapshot, token).ConfigureAwait(true);
                if (model == null) // This happens when computation has been cancelled
                {
                    _finalSessionState = CompletionSessionState.DismissedDueToCancellation;
                    ((IAsyncCompletionSession)this).Dismiss();
                    return model;
                }
            }

            // If we still have no items, dismiss, unless there is another task queued (because user has typed).
            if (model.Uninitialized)
            {
                _finalSessionState = CompletionSessionState.DismissedUninitialized;
                var dismissed = await TryDismissSafely(thisId).ConfigureAwait(true);
                return model;
            }

            // There is another taks queued: We are preempted, store the most recent snapshot for the upcoming invocation of UpdateSnapshot
            if (thisId != _lastFilteringTaskId)
                return model.WithSnapshot(instantaneousSnapshot);

            _telemetry.ComputationStopwatch.Restart();

            var filteredCompletion = await _guardedOperations.CallExtensionPointAsync(
                errorSource: _completionItemManager,
                asyncCall: () => _completionItemManager.UpdateCompletionListAsync(
                    session: this,
                    data: new AsyncCompletionSessionDataSnapshot(
                        model.InitialItems,
                        instantaneousSnapshot,
                        trigger,
                        InitialTrigger,
                        model.Filters,
                        model.UseSoftSelection,
                        model.DisplaySuggestionItem),
                    token: token),
                valueOnThrow: null).ConfigureAwait(true);

            // Error cases are handled by logging them above and dismissing the session.
            if (token.IsCancellationRequested)
            {
                _telemetry.RecordBlockingExtension(_completionItemManager);
                _finalSessionState = CompletionSessionState.DismissedDueToCancellation;
                ((IAsyncCompletionSession)this).Dismiss();
                return model;
            }
            if (filteredCompletion == null)
            {
                _finalSessionState = CompletionSessionState.DismissedDuringFiltering;
                ((IAsyncCompletionSession)this).Dismiss();
                return model;
            }

            // Other error cases that we attribute to the IAsyncCompletionItemManager
            if (filteredCompletion.SelectedItemIndex == -1 && !model.DisplaySuggestionItem)
            {
                _guardedOperations.HandleException(errorSource: _completionItemManager,
                    e: new InvalidOperationException($"{nameof(IAsyncCompletionItemManager)} recommended selecting suggestion item when there is no suggestion item."));
                _finalSessionState = CompletionSessionState.DismissedDuringFiltering;
                ((IAsyncCompletionSession)this).Dismiss();
                return model;
            }

            int selectedIndex = filteredCompletion.SelectedItemIndex;
            bool selectedIndexOverridden = false; // Used when ApplicableToSpan is empty

            // Special experience when there are no returned items:
            ImmutableArray<CompletionItemWithHighlight> returnedItems;
            if (filteredCompletion.Items.IsDefault)
            {
                // Prevent null references when service returns default(ImmutableArray)
                returnedItems = ImmutableArray<CompletionItemWithHighlight>.Empty;
            }
            else if (filteredCompletion.Items.IsEmpty)
            {
                if (model.PresentedItems.IsDefaultOrEmpty)
                {
                    // There were no previously visible results. Return a valid empty array
                    returnedItems = ImmutableArray<CompletionItemWithHighlight>.Empty;
                }
                else
                {
                    // Show previously visible results, without highlighting
                    returnedItems = model.PresentedItems.Select(n => new CompletionItemWithHighlight(n.CompletionItem)).ToImmutableArray();
                    selectedIndex = model.SelectedIndex;
                    if (!_inNoResultFallback)
                    {
                        // Enter the no results mode to preserve the selection state
                        _selectionModeBeforeNoResultFallback = model.UseSoftSelection;
                        _inNoResultFallback = true;
                        model = model.WithSoftSelection(true);
                    }
                }
            }
            else
            {
                // Default behavior, we received completion items
                returnedItems = filteredCompletion.Items;

                if (_inNoResultFallback)
                {
                    // we were in the no result mode and just received no items. Restore the selection mode.
                    model = model.WithSoftSelection(_selectionModeBeforeNoResultFallback);
                    _inNoResultFallback = false;
                }

                // Special experience when ApplicableToSpan is empty: attempt to select last selected item
                if (currentlyApplicableToSpan.IsEmpty && !string.IsNullOrEmpty(_previouslySelectedItemText))
                {
                    int indexOfPreviouslySelectedItem = -1;
                    for (int i = 0; i < filteredCompletion.Items.Length; i++)
                    {
                        if (filteredCompletion.Items[i].CompletionItem.DisplayText.Equals(_previouslySelectedItemText, StringComparison.Ordinal))
                        {
                            indexOfPreviouslySelectedItem = i;
                            break;
                        }
                    }
                    if (indexOfPreviouslySelectedItem != -1)
                    {
                        // We found a matching item
                        model = model.WithSelectedIndex(indexOfPreviouslySelectedItem, preserveSoftSelection: true).WithSoftSelection(true);
                        selectedIndexOverridden = true;
                    }
                }

                // Leave the caret location fallback if user just typed something
                if (_inCaretLocationFallback && trigger.Reason == CompletionTriggerReason.Insertion)
                {
                    // User just typed something, so we can't be at the beginning of applicable to span. Revert the selection mode.
                    model = model.WithSoftSelection(_selectionModeBeforeCaretLocationFallback);
                    _inCaretLocationFallback = false;
                }
            }

            _telemetry.ComputationStopwatch.Stop();
            _telemetry.RecordProcessing(_telemetry.ComputationStopwatch.ElapsedMilliseconds, returnedItems.Length);

            // Allow the item manager to control the selection of the suggestion item
            if (model.DisplaySuggestionItem)
            {
                if (filteredCompletion.SelectedItemIndex == -1)
                    model = model.WithSuggestionItemSelected();
                else if (!selectedIndexOverridden)
                    model = model.WithSelectedIndex(selectedIndex, preserveSoftSelection: true);
                // If suggestion item is present, we default to soft selection.
                model = model.WithSoftSelection(true);

                _previouslySelectedItemText = string.Empty;
            }
            else if (!selectedIndexOverridden && !returnedItems.IsDefaultOrEmpty)
            {
                model = model.WithSelectedIndex(selectedIndex, preserveSoftSelection: true);
                _previouslySelectedItemText = returnedItems[selectedIndex].CompletionItem.DisplayText;
            }

            // Allow the item manager to override the selection style.
            // Our recommendation for extenders is to use UpdateSelectionHint.NoChange whenever possible
            if (filteredCompletion.SelectionHint == UpdateSelectionHint.SoftSelected)
                model = model.WithSoftSelection(true);
            else if (filteredCompletion.SelectionHint == UpdateSelectionHint.Selected)
                model = model.WithSoftSelection(false);

            // Prepare the suggestionItem if user ever activates suggestion mode
            var enteredText = currentlyApplicableToSpan.GetText();
            var suggestionItem = new CompletionItem(enteredText, SuggestionModeCompletionItemSource);

            return model.WithSnapshotItemsAndFilters(updateLocation.Snapshot, returnedItems, filteredCompletion.UniqueItem, suggestionItem, filteredCompletion.Filters);
        }