Example #1
0
        public void SetCompletionItems(
            IList <CompletionItem> completionItems,
            CompletionItem selectedItem,
            CompletionItem suggestionModeItem,
            bool suggestionMode,
            bool isSoftSelected,
            ImmutableArray <CompletionItemFilter> completionItemFilters,
            string filterText)
        {
            _foregroundThread.AssertIsForeground();

            foreach (var item in completionItems)
            {
                if (!_displayTextToBoldingTextMap.ContainsKey(item.DisplayText))
                {
                    _displayTextToBoldingTextMap.Add(item.DisplayText, CompletionHelper.GetDisplayTextForMatching(item));
                }
            }

            // Initialize the completion map to a reasonable default initial size (+1 for the builder)
            CompletionItemMap  = CompletionItemMap ?? new Dictionary <CompletionItem, VSCompletion>(completionItems.Count + 1);
            FilterText         = filterText;
            SuggestionModeItem = suggestionModeItem;

            // If more than one filter was provided, then present it to the user.
            if (_showFilters && _filters == null && completionItemFilters.Length > 1)
            {
                _filters = completionItemFilters.Select(f => new IntellisenseFilter2(this, f))
                           .ToArray();
            }

            CreateCompletionListBuilder(selectedItem, suggestionModeItem, suggestionMode);
            CreateNormalCompletionListItems(completionItems);

            var selectedCompletionItem = selectedItem != null?GetVSCompletion(selectedItem) : null;

            SelectionStatus = new CompletionSelectionStatus(
                selectedCompletionItem,
                isSelected: !isSoftSelected, isUnique: selectedCompletionItem != null);
        }
Example #2
0
        private void Commit(
            CompletionItem item, Model model, char?commitChar,
            ITextSnapshot initialTextSnapshot, Action nextHandler)
        {
            AssertIsForeground();

            // We could only be called if we had a model at this point.
            Contract.ThrowIfNull(model);

            // Now that we've captured the model at this point, we can stop ourselves entirely.
            // This is also desirable as we may have reentrancy problems when we call into
            // custom commit completion providers.  I.e. if the custom provider moves the caret,
            // then we do not want to process that move as it may put us into an unexpected state.
            //
            // TODO(cyrusn): We still have a general reentrancy problem where calling into a custom
            // commit provider (or just calling into the editor) may cause something to call back
            // into us.  However, for now, we just hope that no such craziness will occur.
            this.DismissSessionIfActive();

            CompletionChange completionChange;

            using (var transaction = CaretPreservingEditTransaction.TryCreate(
                       EditorFeaturesResources.IntelliSense, TextView, _undoHistoryRegistry, _editorOperationsFactoryService))
            {
                if (transaction == null)
                {
                    // This text buffer has no undo history and has probably been unmapped.
                    // (Workflow unmaps its projections when losing focus (such as double clicking the completion list)).
                    // Bail on committing completion because we won't be able to find a Document to update either.

                    return;
                }

                // We want to merge with any of our other programmatic edits (e.g. automatic brace completion)
                transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance;

                if (GetCompletionProvider(item) is ICustomCommitCompletionProvider provider)
                {
                    provider.Commit(item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar);
                }
                else
                {
                    // Right before calling Commit, we may have passed the commitChar through to the
                    // editor.  That was so that undoing completion will get us back to the state we
                    // we would be in if completion had done nothing.  However, now that we're going
                    // to actually commit, we want to roll back to where we were before we pushed
                    // commit character into the buffer.  This has multiple benefits:
                    //
                    //   1) the buffer is in a state we expect it to be in.  i.e. we don't have to
                    //      worry about what might have happened (like brace-completion) when the
                    //      commit char was inserted.
                    //   2) after we commit the item, we'll pass the commit character again into the
                    //      buffer (unless the items asks us not to).  By doing this, we can make sure
                    //      that things like brace-completion or formatting trigger as we expect them
                    //      to.
                    var characterWasSentIntoBuffer = commitChar != null &&
                                                     initialTextSnapshot.Version.VersionNumber != this.SubjectBuffer.CurrentSnapshot.Version.VersionNumber;
                    if (characterWasSentIntoBuffer)
                    {
                        RollbackToBeforeTypeChar(initialTextSnapshot);
                    }

                    // Now, get the change the item wants to make.  Note that the change will be relative
                    // to the initial snapshot/document the item was triggered from.  We'll map that change
                    // forward, then apply it to our current snapshot.
                    var triggerDocument = model.TriggerDocument;
                    var triggerSnapshot = model.TriggerSnapshot;

                    var completionService = CompletionService.GetService(triggerDocument);
                    Contract.ThrowIfNull(completionService, nameof(completionService));

                    completionChange = completionService.GetChangeAsync(
                        triggerDocument, item, commitChar, CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                    var textChange = completionChange.TextChange;

                    var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan());
                    var mappedSpan          = triggerSnapshotSpan.TranslateTo(
                        this.SubjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);

                    var adjustedNewText = AdjustForVirtualSpace(textChange);
                    var editOptions     = GetEditOptions(mappedSpan, adjustedNewText);

                    // The immediate window is always marked read-only and the language service is
                    // responsible for asking the buffer to make itself writable. We'll have to do that for
                    // commit, so we need to drag the IVsTextLines around, too.
                    // We have to ask the buffer to make itself writable, if it isn't already
                    uint immediateWindowBufferUpdateCookie = 0;
                    if (_isImmediateWindow)
                    {
                        immediateWindowBufferUpdateCookie = ((IDebuggerTextView)TextView).StartBufferUpdate();
                    }

                    // Now actually make the text change to the document.
                    using (var textEdit = this.SubjectBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null))
                    {
                        textEdit.Replace(mappedSpan.Span, adjustedNewText);
                        textEdit.ApplyAndLogExceptions();
                    }

                    if (_isImmediateWindow)
                    {
                        ((IDebuggerTextView)TextView).EndBufferUpdate(immediateWindowBufferUpdateCookie);
                    }

                    // If the completion change requested a new position for the caret to go,
                    // then set the caret to go directly to that point.
                    if (completionChange.NewPosition.HasValue)
                    {
                        SetCaretPosition(desiredCaretPosition: completionChange.NewPosition.Value);
                    }
                    else if (editOptions.ComputeMinimalChange)
                    {
                        // Or, If we're doing a minimal change, then the edit that we make to the
                        // buffer may not make the total text change that places the caret where we
                        // would expect it to go based on the requested change. In this case,
                        // determine where the item should go and set the care manually.

                        // Note: we only want to move the caret if the caret would have been moved
                        // by the edit.  i.e. if the caret was actually in the mapped span that
                        // we're replacing.
                        if (TextView.GetCaretPoint(this.SubjectBuffer) is SnapshotPoint caretPositionInBuffer &&
                            mappedSpan.IntersectsWith(caretPositionInBuffer))
                        {
                            SetCaretPosition(desiredCaretPosition: mappedSpan.Start.Position + adjustedNewText.Length);
                        }
                    }

                    // Now, pass along the commit character unless the completion item said not to
                    if (characterWasSentIntoBuffer && !completionChange.IncludesCommitCharacter)
                    {
                        nextHandler();
                    }

                    if (item.Rules.FormatOnCommit)
                    {
                        var spanToFormat = triggerSnapshotSpan.TranslateTo(
                            this.SubjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
                        var document          = this.GetDocument();
                        var formattingService = document?.GetLanguageService <IEditorFormattingService>();

                        if (formattingService != null)
                        {
                            var changes = formattingService.GetFormattingChangesAsync(
                                document, spanToFormat.Span.ToTextSpan(), CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                            document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None);
                        }
                    }

                    // If the insertion is long enough, the caret will scroll out of the visible area.
                    // Re-center the view.
                    this.TextView.Caret.EnsureVisible();
                }

                transaction.Complete();
            }

            // Let the completion rules know that this item was committed.
            this.MakeMostRecentItem(CompletionHelper.GetDisplayTextForMatching(item));
        }
            private static int GetRecentItemIndex(ImmutableArray <string> recentItems, CompletionItem item)
            {
                var index = recentItems.IndexOf(CompletionHelper.GetDisplayTextForMatching(item));

                return(-index);
            }