コード例 #1
0
        private bool IsCaretOutsideItemBounds(
            Model model,
            SnapshotPoint caretPoint,
            CompletionItem item,
            Dictionary<TextSpan, string> textSpanToText,
            Dictionary<TextSpan, ViewTextSpan> textSpanToViewSpan)
        {
            // Easy first check.  See if the caret point is before the start of the item.
            ViewTextSpan filterSpanInViewBuffer;
            if (!textSpanToViewSpan.TryGetValue(item.FilterSpan, out filterSpanInViewBuffer))
            {
                filterSpanInViewBuffer = model.GetSubjectBufferFilterSpanInViewBuffer(item.FilterSpan);
                textSpanToViewSpan[item.FilterSpan] = filterSpanInViewBuffer;
            }

            if (caretPoint < filterSpanInViewBuffer.TextSpan.Start)
            {
                return true;
            }

            var textSnapshot = caretPoint.Snapshot;

            var currentText = model.GetCurrentTextInSnapshot(item.FilterSpan, textSnapshot, textSpanToText);
            var currentTextSpan = new TextSpan(filterSpanInViewBuffer.TextSpan.Start, currentText.Length);

            return !currentTextSpan.IntersectsWith(caretPoint);
        }
コード例 #2
0
        private bool IsFilterTextEmpty(
            Model model,
            SnapshotPoint caretPoint,
            CompletionItem item,
            Dictionary <TextSpan, string> textSpanToText,
            Dictionary <TextSpan, ViewTextSpan> textSpanToViewSpan)
        {
            // Easy first check.  See if the caret point is before the start of the item.
            ViewTextSpan filterSpanInViewBuffer;

            if (!textSpanToViewSpan.TryGetValue(item.FilterSpan, out filterSpanInViewBuffer))
            {
                filterSpanInViewBuffer = model.GetSubjectBufferFilterSpanInViewBuffer(item.FilterSpan);
                textSpanToViewSpan[item.FilterSpan] = filterSpanInViewBuffer;
            }

            if (caretPoint < filterSpanInViewBuffer.TextSpan.Start)
            {
                return(true);
            }

            var textSnapshot = caretPoint.Snapshot;

            var currentText     = model.GetCurrentTextInSnapshot(item.FilterSpan, textSnapshot, textSpanToText);
            var currentTextSpan = new TextSpan(filterSpanInViewBuffer.TextSpan.Start, currentText.Length);

            return(currentText.Length == 0);
        }
コード例 #3
0
        private string GetCurrentFilterText(Model model, CompletionItem selectedItem)
        {
            var textSnapshot = this.TextView.TextSnapshot;
            var viewSpan     = model.GetSubjectBufferFilterSpanInViewBuffer(selectedItem.FilterSpan);
            var filterText   = model.GetCurrentTextInSnapshot(
                viewSpan, textSnapshot, GetCaretPointInViewBuffer());

            return(filterText);
        }
コード例 #4
0
            private bool IsHardSelection(
                Model model,
                CompletionItem bestFilterMatch,
                ITextSnapshot textSnapshot,
                IList <ICompletionRules> completionRulesList,
                CompletionTriggerInfo triggerInfo,
                CompletionFilterReason reason)
            {
                if (model.Builder != null)
                {
                    return(bestFilterMatch != null && bestFilterMatch.DisplayText == model.Builder.DisplayText);
                }

                if (bestFilterMatch == null || model.UseSuggestionCompletionMode)
                {
                    return(false);
                }

                // We don't have a builder and we have a best match.  Normally this will be hard
                // selected, except for a few cases.  Specifically, if no filter text has been
                // provided, and this is not a preselect match then we will soft select it.  This
                // happens when the completion list comes up implicitly and there is something in
                // the MRU list.  In this case we do want to select it, but not with a hard
                // selection.  Otherwise you can end up with the following problem:
                //
                //  dim i as integer =<space>
                //
                // Completion will comes up after = with 'integer' selected (Because of MRU).  We do
                // not want 'space' to commit this.
                var viewSpan       = model.GetSubjectBufferFilterSpanInViewBuffer(bestFilterMatch.FilterSpan);
                var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null);

                foreach (var completionRules in completionRulesList)
                {
                    var shouldSoftSelect = completionRules.ShouldSoftSelectItem(GetExternallyUsableCompletionItem(bestFilterMatch), fullFilterText, triggerInfo);
                    if (shouldSoftSelect == true)
                    {
                        return(false);
                    }
                }

                // If the user moved the caret left after they started typing, the 'best' match may not match at all
                // against the full text span that this item would be replacing.
                if (!MatchesFilterText(bestFilterMatch, fullFilterText, completionRulesList, triggerInfo, reason))
                {
                    return(false);
                }

                // There was either filter text, or this was a preselect match.  In either case, we
                // can hard select this.
                return(true);
            }
コード例 #5
0
        private void Commit(CompletionItem item, TextChange textChange, Model model, char?commitChar)
        {
            AssertIsForeground();

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

            item = Controller.GetExternallyUsableCompletionItem(item);

            // 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.StopModelComputation();

            // NOTE(cyrusn): It is intentional that we get the undo history for the
            // surface buffer and not the subject buffer.
            using (var transaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.Intellisense))
            {
                // We want to merge with any of our other programmatic edits (e.g. automatic brace completion)
                transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance;

                // Check if the provider wants to perform custom commit itself.  Otherwise we will
                // handle things.
                var provider = item.CompletionProvider as ICustomCommitCompletionProvider;
                if (provider == null)
                {
                    var viewBuffer = this.TextView.TextBuffer;

                    // Use character based diffing here to avoid overwriting the commit character placed into the editor.
                    var editOptions = new EditOptions(new StringDifferenceOptions
                    {
                        DifferenceType       = StringDifferenceTypes.Character,
                        IgnoreTrimWhiteSpace = EditOptions.DefaultMinimalChange.DifferenceOptions.IgnoreTrimWhiteSpace
                    });

                    using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null))
                    {
                        var viewSpan    = model.GetSubjectBufferFilterSpanInViewBuffer(textChange.Span);
                        var currentSpan = model.GetCurrentSpanInSnapshot(viewSpan, viewBuffer.CurrentSnapshot);

                        // In order to play nicely with automatic brace completion, we need to
                        // not touch the opening paren. We'll check our span and textchange
                        // for ( and adjust them accordingly if we find them.

                        // all this is needed since we don't use completion set mechanism provided by VS but we implement everything ourselves.
                        // due to that, existing brace completion engine in editor that should take care of interaction between brace completion
                        // and intellisense doesn't work for us. so we need this kind of workaround to support it nicely.
                        bool textChanged;
                        var  finalText = GetFinalText(textChange, commitChar.GetValueOrDefault(), out textChanged);
                        currentSpan = GetFinalSpan(currentSpan, commitChar.GetValueOrDefault(), textChanged);

                        var caretPoint        = this.TextView.GetCaretPoint(this.SubjectBuffer);
                        var virtualCaretPoint = this.TextView.GetVirtualCaretPoint(this.SubjectBuffer);

                        if (caretPoint.HasValue && virtualCaretPoint.HasValue)
                        {
                            // TODO(dustinca): We need to call a different API here. TryMoveCaretToAndEnsureVisible might center within the view.
                            this.TextView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(caretPoint.Value));
                        }

                        caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer);

                        // Now that we're doing character level diffing, we need to move the caret to the end of
                        // the span being replaced. Otherwise, we can replace M|ai with Main and wind up with
                        // M|ain, since character based diffing makes that quite legit.
                        if (caretPoint.HasValue)
                        {
                            var endInSubjectBuffer = this.TextView.BufferGraph.MapDownToBuffer(currentSpan.End, PointTrackingMode.Positive, caretPoint.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor);
                            if (caretPoint.Value < endInSubjectBuffer)
                            {
                                this.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(currentSpan.Snapshot.TextBuffer.CurrentSnapshot, currentSpan.End.Position));
                            }
                        }

                        textEdit.Replace(currentSpan, finalText);
                        textEdit.Apply();
                    }

                    // We've manipulated the caret position in order to generate the correct edit. However,
                    // if the insertion is long enough, the caret will scroll out of the visible area.
                    // Re-center the view.
                    using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null))
                    {
                        var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer);
                        if (caretPoint.HasValue)
                        {
                            this.TextView.Caret.EnsureVisible();
                        }
                    }

                    transaction.Complete();
                }
                else
                {
                    // Let the provider handle this.
                    provider.Commit(item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar);
                    transaction.Complete();
                }
            }

            var document          = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            var formattingService = document.GetLanguageService <IEditorFormattingService>();

            if (formattingService != null &&
                (item.ShouldFormatOnCommit || (commitChar != null && formattingService.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault()))))
            {
                // Formatting the completion item affected span is done as a separate transaction because this gives the user
                // the flexibility to undo the formatting but retain the changes associated with the completion item
                using (var formattingTransaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntellisenseCommitFormatting))
                {
                    var changes = formattingService.GetFormattingChangesAsync(document, textChange.Span, CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                    document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None);
                    formattingTransaction.Complete();
                }
            }

            // Let the rules factories know that this item was committed.
            foreach (var rules in GetCompletionRules())
            {
                rules.CompletionItemComitted(item);
            }
        }
コード例 #6
0
        private void Commit(CompletionItem item, TextChange textChange, Model model, char? commitChar)
        {
            AssertIsForeground();

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

            item = Controller.GetExternallyUsableCompletionItem(item);

            // 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.StopModelComputation();

            // NOTE(cyrusn): It is intentional that we get the undo history for the
            // surface buffer and not the subject buffer.
            using (var transaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSense))
            {
                // We want to merge with any of our other programmatic edits (e.g. automatic brace completion)
                transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance;

                // Check if the provider wants to perform custom commit itself.  Otherwise we will
                // handle things.
                var provider = item.CompletionProvider as ICustomCommitCompletionProvider;
                if (provider == null)
                {
                    var viewBuffer = this.TextView.TextBuffer;

                    // Use character based diffing here to avoid overwriting the commit character placed into the editor.
                    var editOptions = new EditOptions(new StringDifferenceOptions
                    {
                        DifferenceType = StringDifferenceTypes.Character,
                        IgnoreTrimWhiteSpace = EditOptions.DefaultMinimalChange.DifferenceOptions.IgnoreTrimWhiteSpace
                    });

                    using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null))
                    {
                        var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(textChange.Span);
                        var currentSpan = model.GetCurrentSpanInSnapshot(viewSpan, viewBuffer.CurrentSnapshot);

                        // In order to play nicely with automatic brace completion, we need to 
                        // not touch the opening paren. We'll check our span and textchange 
                        // for ( and adjust them accordingly if we find them.

                        // all this is needed since we don't use completion set mechanism provided by VS but we implement everything ourselves.
                        // due to that, existing brace completion engine in editor that should take care of interaction between brace completion
                        // and intellisense doesn't work for us. so we need this kind of workaround to support it nicely.
                        bool textChanged;
                        var finalText = GetFinalText(textChange, commitChar.GetValueOrDefault(), out textChanged);
                        currentSpan = GetFinalSpan(currentSpan, commitChar.GetValueOrDefault(), textChanged);

                        var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer);
                        var virtualCaretPoint = this.TextView.GetVirtualCaretPoint(this.SubjectBuffer);

                        if (caretPoint.HasValue && virtualCaretPoint.HasValue)
                        {
                            // TODO(dustinca): We need to call a different API here. TryMoveCaretToAndEnsureVisible might center within the view.
                            this.TextView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(caretPoint.Value));
                        }

                        caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer);

                        // Now that we're doing character level diffing, we need to move the caret to the end of 
                        // the span being replaced. Otherwise, we can replace M|ai with Main and wind up with 
                        // M|ain, since character based diffing makes that quite legit.
                        if (caretPoint.HasValue)
                        {
                            var endInSubjectBuffer = this.TextView.BufferGraph.MapDownToBuffer(currentSpan.End, PointTrackingMode.Positive, caretPoint.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor);
                            if (caretPoint.Value < endInSubjectBuffer)
                            {
                                this.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(currentSpan.Snapshot.TextBuffer.CurrentSnapshot, currentSpan.End.Position));
                            }
                        }

                        textEdit.Replace(currentSpan, finalText);
                        textEdit.Apply();
                    }

                    // We've manipulated the caret position in order to generate the correct edit. However, 
                    // if the insertion is long enough, the caret will scroll out of the visible area.
                    // Re-center the view.
                    using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null))
                    {
                        var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer);
                        if (caretPoint.HasValue)
                        {
                            this.TextView.Caret.EnsureVisible();
                        }
                    }

                    transaction.Complete();
                }
                else
                {
                    // Let the provider handle this.
                    provider.Commit(item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar);
                    transaction.Complete();
                }
            }

            var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            var formattingService = document.GetLanguageService<IEditorFormattingService>();

            var commitCharTriggersFormatting = commitChar != null && 
                    (formattingService?.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault()) 
                     ?? false);

            if (formattingService != null && (item.ShouldFormatOnCommit || commitCharTriggersFormatting))
            {
                // Formatting the completion item affected span is done as a separate transaction because this gives the user
                // the flexibility to undo the formatting but retain the changes associated with the completion item
                using (var formattingTransaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSenseCommitFormatting))
                {
                    var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer);
                    IList<TextChange> changes;
                    if (commitCharTriggersFormatting && caretPoint.HasValue)
                    {
                        // if the commit character is supported by formatting service, then let the formatting service
                        // find the appropriate range to format.
                        changes = formattingService.GetFormattingChangesAsync(document, commitChar.Value, caretPoint.Value.Position, CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                    }
                    else
                    {
                        // if this is not a supported trigger character for formatting service (space or tab etc.)
                        // then format the span of the textchange.
                        changes = formattingService.GetFormattingChangesAsync(document, textChange.Span, CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                    }

                    if (changes != null && !changes.IsEmpty())
                    {
                        document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None);
                    }

                    formattingTransaction.Complete();
                }
            }

            // Let the completion rules know that this item was committed.
            GetCompletionRules().CompletionItemCommitted(item);
        }
コード例 #7
0
            private bool IsHardSelection(
                Model model,
                CompletionItem bestFilterMatch,
                ITextSnapshot textSnapshot,
                CompletionRules completionRules,
                CompletionTriggerInfo triggerInfo,
                CompletionFilterReason reason)
            {
                if (model.Builder != null)
                {
                    return bestFilterMatch != null && bestFilterMatch.DisplayText == model.Builder.DisplayText;
                }

                if (bestFilterMatch == null || model.UseSuggestionCompletionMode)
                {
                    return false;
                }

                // We don't have a builder and we have a best match.  Normally this will be hard
                // selected, except for a few cases.  Specifically, if no filter text has been
                // provided, and this is not a preselect match then we will soft select it.  This
                // happens when the completion list comes up implicitly and there is something in
                // the MRU list.  In this case we do want to select it, but not with a hard
                // selection.  Otherwise you can end up with the following problem:
                //
                //  dim i as integer =<space>
                //
                // Completion will comes up after = with 'integer' selected (Because of MRU).  We do
                // not want 'space' to commit this.
                var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(bestFilterMatch.FilterSpan);
                var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null);

                var shouldSoftSelect = completionRules.ShouldSoftSelectItem(GetExternallyUsableCompletionItem(bestFilterMatch), fullFilterText, triggerInfo);
                if (shouldSoftSelect)
                {
                    return false;
                }

                // If the user moved the caret left after they started typing, the 'best' match may not match at all
                // against the full text span that this item would be replacing.
                if (!completionRules.MatchesFilterText(bestFilterMatch, fullFilterText, triggerInfo, reason))
                {
                    return false;
                }

                // There was either filter text, or this was a preselect match.  In either case, we
                // can hard select this.
                return true;
            }