Beispiel #1
0
        /// <summary>
        /// Attempts to convert VS Completion trigger into Roslyn completion trigger
        /// </summary>
        /// <param name="trigger">VS completion trigger</param>
        /// <param name="triggerLocation">Character.
        /// VS provides Backspace and Delete characters inside the trigger while Roslyn needs the char deleted by the trigger.
        /// Therefore, we provide this character separately and use it for Delete and Backspace cases only.
        /// We retrieve this character from triggerLocation.
        /// </param>
        /// <returns>Roslyn completion trigger</returns>
        internal static RoslynTrigger GetRoslynTrigger(AsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation)
        {
            switch (trigger.Reason)
            {
            case AsyncCompletionData.CompletionTriggerReason.Insertion:
                return(RoslynTrigger.CreateInsertionTrigger(trigger.Character));

            case AsyncCompletionData.CompletionTriggerReason.Deletion:
            case AsyncCompletionData.CompletionTriggerReason.Backspace:
                var  snapshotBeforeEdit = trigger.ViewSnapshotBeforeTrigger;
                char characterRemoved;
                if (triggerLocation.Position >= 0 && triggerLocation.Position < snapshotBeforeEdit.Length)
                {
                    // If multiple characters were removed (selection), this finds the first character from the left.
                    characterRemoved = snapshotBeforeEdit[triggerLocation.Position];
                }
                else
                {
                    characterRemoved = (char)0;
                }

                return(RoslynTrigger.CreateDeletionTrigger(characterRemoved));

            case AsyncCompletionData.CompletionTriggerReason.SnippetsMode:
                return(new RoslynTrigger(CompletionTriggerKind.Snippets));

            default:
                return(RoslynTrigger.Invoke);
            }
        }
Beispiel #2
0
 /// <summary>
 /// Gets the completions available at the caret position.
 /// </summary>
 /// <param name="document">The document that completion is occuring within.</param>
 /// <param name="caretPosition">The position of the caret after the triggering action.</param>
 /// <param name="trigger">The triggering action.</param>
 /// <param name="roles">Optional set of roles associated with the editor state.</param>
 /// <param name="options">Optional options that override the default options.</param>
 /// <param name="cancellationToken"></param>
 public abstract Task<CompletionList> GetCompletionsAsync(
     Document document,
     int caretPosition,
     CompletionTrigger trigger = default(CompletionTrigger),
     ImmutableHashSet<string> roles = null,
     OptionSet options = null,
     CancellationToken cancellationToken = default(CancellationToken));
        private void VerifyTextualTriggerCharacterWorker(
            string markup, bool expectedTriggerCharacter, bool triggerOnLetter)
        {
            using (var workspace = CreateWorkspace(markup))
            {
                var document = workspace.Documents.Single();
                var position = document.CursorPosition.Value;
                var text     = document.TextBuffer.CurrentSnapshot.AsText();
                var options  = workspace.Options.WithChangedOption(
                    CompletionOptions.TriggerOnTypingLetters, document.Project.Language, triggerOnLetter);
                var trigger = CompletionTrigger.CreateInsertionTrigger(text[position]);

                var service = GetCompletionService(workspace);
                var isTextualTriggerCharacterResult = service.ShouldTriggerCompletion(text, position + 1, trigger, options: options);

                if (expectedTriggerCharacter)
                {
                    var assertText = "'" + text.ToString(new TextSpan(position, 1)) + "' expected to be textual trigger character";
                    Assert.True(isTextualTriggerCharacterResult, assertText);
                }
                else
                {
                    var assertText = "'" + text.ToString(new TextSpan(position, 1)) + "' expected to NOT be textual trigger character";
                    Assert.False(isTextualTriggerCharacterResult, assertText);
                }
            }
        }
Beispiel #4
0
        /// <summary>
        /// Creates a <see cref="CompletionContext"/> instance.
        /// </summary>
        public CompletionContext(
            CompletionProvider provider,
            Document document,
            int position,
            TextSpan defaultSpan,
            CompletionTrigger trigger,
            OptionSet options,
            CancellationToken cancellationToken)
        {
            if (provider == null)
            {
                throw new ArgumentNullException(nameof(provider));
            }

            if (document == null)
            {
                throw new ArgumentNullException(nameof(document));
            }

            if (options == null)
            {
                throw new ArgumentException(nameof(options));
            }

            this.Provider = provider;
            this.Document = document;
            this.Position = position;
            this.CompletionListSpan = defaultSpan;
            this.Trigger = trigger;
            this.Options = options;
            this.CancellationToken = cancellationToken;
            _items = new List<CompletionItem>();
        }
        protected async Task CheckResultsAsync(
            Document document, int position, string expectedItemOrNull,
            string expectedDescriptionOrNull, bool usePreviousCharAsTrigger,
            bool checkForAbsence, int?glyph, int?matchPriority,
            bool?hasSuggestionModeItem, string displayTextSuffix)
        {
            var code = (await document.GetTextAsync()).ToString();

            var trigger = CompletionTrigger.Invoke;

            if (usePreviousCharAsTrigger)
            {
                trigger = CompletionTrigger.CreateInsertionTrigger(insertedCharacter: code.ElementAt(position - 1));
            }

            var completionService = GetCompletionService(document.Project.Solution.Workspace);
            var completionList    = await GetCompletionListAsync(completionService, document, position, trigger);

            var items = completionList == null ? ImmutableArray <CompletionItem> .Empty : completionList.Items;

            if (hasSuggestionModeItem != null)
            {
                Assert.Equal(hasSuggestionModeItem.Value, completionList.SuggestionModeItem != null);
            }

            if (checkForAbsence)
            {
                if (items == null)
                {
                    return;
                }

                if (expectedItemOrNull == null)
                {
                    Assert.Empty(items);
                }
                else
                {
                    AssertEx.None(
                        items,
                        c => CompareItems(c.DisplayText, expectedItemOrNull) &&
                        (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true));
                }
            }
            else
            {
                if (expectedItemOrNull == null)
                {
                    Assert.NotEmpty(items);
                }
                else
                {
                    AssertEx.Any(items, c => CompareItems(c.DisplayText, expectedItemOrNull) &&
                                 CompareItems(c.DisplayTextSuffix, displayTextSuffix ?? "") &&
                                 (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true) &&
                                 (glyph.HasValue ? c.Tags.SequenceEqual(GlyphTags.GetTags((Glyph)glyph.Value)) : true) &&
                                 (matchPriority.HasValue ? (int)c.Rules.MatchPriority == matchPriority.Value : true));
                }
            }
        }
Beispiel #6
0
        /// <summary>
        /// Attempts to convert VS Completion trigger into Roslyn completion trigger
        /// </summary>
        /// <param name="trigger">VS completion trigger</param>
        /// <param name="triggerLocation">Character.
        /// VS provides Backspace and Delete characters inside the trigger while Roslyn needs the char deleted by the trigger.
        /// Therefore, we provide this character separately and use it for Delete and Backspace cases only.
        /// We retrieve this character from triggerLocation.
        /// </param>
        /// <returns>Roslyn completion trigger</returns>
        internal static RoslynTrigger GetRoslynTrigger(EditorAsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation)
        {
            var completionTriggerKind = GetRoslynTriggerKind(trigger);

            if (completionTriggerKind == CompletionTriggerKind.Deletion)
            {
                var  snapshotBeforeEdit = trigger.ViewSnapshotBeforeTrigger;
                char characterRemoved;
                if (triggerLocation.Position >= 0 && triggerLocation.Position < snapshotBeforeEdit.Length)
                {
                    // If multiple characters were removed (selection), this finds the first character from the left.
                    characterRemoved = snapshotBeforeEdit[triggerLocation.Position];
                }
                else
                {
                    characterRemoved = (char)0;
                }

                return(RoslynTrigger.CreateDeletionTrigger(characterRemoved));
            }
            else
            {
                return(new RoslynTrigger(completionTriggerKind, trigger.Character));
            }
        }
Beispiel #7
0
 /// <summary>
 /// Returns true if the character recently inserted or deleted in the text should trigger completion.
 /// </summary>
 /// <param name="text">The document text to trigger completion within </param>
 /// <param name="caretPosition">The position of the caret after the triggering action.</param>
 /// <param name="trigger">The potential triggering action.</param>
 /// <param name="roles">Optional set of roles associated with the editor state.</param>
 /// <param name="options">Optional options that override the default options.</param>
 /// <remarks>
 /// This API uses SourceText instead of Document so implementations can only be based on text, not syntax or semantics.
 /// </remarks>
 public virtual bool ShouldTriggerCompletion(
     SourceText text,
     int caretPosition,
     CompletionTrigger trigger,
     ImmutableHashSet<string> roles = null,
     OptionSet options = null)
 {
     return false;
 }
        public override bool ShouldTriggerCompletion(SourceText text, int position, CompletionTrigger trigger, OptionSet options)
        {
            switch (trigger.Kind)
            {
                case CompletionTriggerKind.Insertion:
                    var insertedCharacterPosition = position - 1;
                    return this.IsInsertionTrigger(text, insertedCharacterPosition, options);

                default:
                    return false;
            }
        }
        protected async Task <ImmutableArray <CompletionItem> > GetCompletionItemsAsync(
            string markup, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger = false)
        {
            MarkupTestFile.GetPosition(markup.NormalizeLineEndings(), out var code, out int position);
            var document = WorkspaceFixture.UpdateDocument(code, sourceCodeKind);

            var trigger = usePreviousCharAsTrigger
                ? CompletionTrigger.CreateInsertionTrigger(insertedCharacter: code.ElementAt(position - 1))
                : CompletionTrigger.Invoke;

            var completionService = GetCompletionService(document.Project.Solution.Workspace);
            var completionList    = await GetCompletionListAsync(completionService, document, position, trigger);

            return(completionList == null ? ImmutableArray <CompletionItem> .Empty : completionList.Items);
        }
        private ImmutableArray<CompletionItem> GetItems(SourceText text, Document document, int position, CompletionTrigger triggerInfo, CancellationToken cancellationToken)
        {
            var line = text.Lines.GetLineFromPosition(position);
            var lineText = text.ToString(TextSpan.FromBounds(line.Start, position));
            var match = s_directiveRegex.Match(lineText);
            if (!match.Success)
            {
                return ImmutableArray<CompletionItem>.Empty;
            }

            var quotedPathGroup = match.Groups[1];
            var quotedPath = quotedPathGroup.Value;
            var endsWithQuote = PathCompletionUtilities.EndsWithQuote(quotedPath);
            if (endsWithQuote && (position >= line.Start + match.Length))
            {
                return ImmutableArray<CompletionItem>.Empty;
            }

            var buffer = text.Container.GetTextBuffer();
            var snapshot = text.FindCorrespondingEditorTextSnapshot();
            if (snapshot == null)
            {
                return ImmutableArray<CompletionItem>.Empty;
            }

            var fileSystem = CurrentWorkingDirectoryDiscoveryService.GetService(snapshot);

            // TODO: https://github.com/dotnet/roslyn/issues/5263
            // Avoid dependency on a specific resolver.
            // The search paths should be provided by specialized workspaces:
            // - InteractiveWorkspace for interactive window 
            // - ScriptWorkspace for loose .csx files (we don't have such workspace today)
            var searchPaths = (document.Project.CompilationOptions.SourceReferenceResolver as SourceFileResolver)?.SearchPaths ?? ImmutableArray<string>.Empty;

            var helper = new FileSystemCompletionHelper(
                this,
                GetTextChangeSpan(text, position, quotedPathGroup),
                fileSystem,
                Glyph.OpenFolder,
                Glyph.CSharpFile,
                searchPaths: searchPaths,
                allowableExtensions: new[] { ".csx" },
                itemRules: s_rules);

            var pathThroughLastSlash = this.GetPathThroughLastSlash(text, position, quotedPathGroup);

            return helper.GetItems(pathThroughLastSlash, documentPath: null);
        }
        protected override async Task<CompletionItem> GetSuggestionModeItemAsync(Document document, int position, TextSpan itemSpan, CompletionTrigger trigger, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (trigger.Kind == CompletionTriggerKind.Insertion)
            {
                var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                var token = tree
                    .FindTokenOnLeftOfPosition(position, cancellationToken)
                    .GetPreviousTokenIfTouchingWord(position);

                if (token.Kind() == SyntaxKind.None)
                {
                    return null;
                }

                var semanticModel = await document.GetSemanticModelForNodeAsync(token.Parent, cancellationToken).ConfigureAwait(false);
                var typeInferrer = document.GetLanguageService<ITypeInferenceService>();

                if (IsLambdaExpression(semanticModel, position, token, typeInferrer, cancellationToken))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.LambdaExpression, itemSpan, CSharpFeaturesResources.AutoselectDisabledDueToPotentialLambdaDeclaration);
                }
                else if (IsAnonymousObjectCreation(token) || IsPossibleTupleExpression(token))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.MemberName, itemSpan, CSharpFeaturesResources.AutoselectDisabledDueToPossibleExplicitlyNamesAnonTypeMemCreation);
                }
                else if (token.IsPreProcessorExpressionContext())
                {
                    return CreateEmptySuggestionModeItem(itemSpan);
                }
                else if (IsImplicitArrayCreation(semanticModel, token, position, typeInferrer, cancellationToken))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.ImplicitArrayCreation, itemSpan, CSharpFeaturesResources.AutoselectDisabledDueToPotentialImplicitArray);
                }
                else if (token.IsKindOrHasMatchingText(SyntaxKind.FromKeyword) || token.IsKindOrHasMatchingText(SyntaxKind.JoinKeyword))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.RangeVariable, itemSpan, CSharpFeaturesResources.AutoselectDisabledDueToPotentialRangeVariableDecl);
                }
                else if (tree.IsNamespaceDeclarationNameContext(position, cancellationToken))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.NamespaceName, itemSpan, CSharpFeaturesResources.AutoselectDisabledDueToNamespaceDeclaration);
                }
            }

            return null;
        }
            public void ComputeModel(
                CompletionService completionService,
                CompletionTrigger trigger,
                ImmutableHashSet<string> roles,
                OptionSet options)
            {
                AssertIsForeground();

                // If we've already computed a model then we can just ignore this request and not
                // generate any tasks.
                if (this.Computation.InitialUnfilteredModel != null)
                {
                    return;
                }

                new ModelComputer(this, completionService, trigger, roles, options).Do();
            }
                public ModelComputer(
                    Session session,
                    CompletionService completionService,
                    CompletionTrigger trigger,
                    ImmutableHashSet<string> roles,
                    OptionSet options)
                {
                    _session = session;
                    _completionService = completionService;
                    _options = options;
                    _trigger = trigger;
                    _subjectBufferCaretPosition = session.Controller.TextView.GetCaretPoint(session.Controller.SubjectBuffer).Value;
                    _roles = roles;

                    _text = _subjectBufferCaretPosition.Snapshot.AsText();

                    _useSuggestionMode = session.Controller.SubjectBuffer.GetFeatureOnOffOption(Options.EditorCompletionOptions.UseSuggestionMode);

                    _disconnectedBufferGraph = new DisconnectedBufferGraph(session.Controller.SubjectBuffer, session.Controller.TextView.TextBuffer);
                }
Beispiel #14
0
        private bool StartNewModelComputation(
            CompletionService completionService,
            RoslynCompletionTrigger trigger)
        {
            AssertIsForeground();
            Contract.ThrowIfTrue(sessionOpt != null);

            if (completionService == null)
            {
                return(false);
            }

            if (this.TextView.Selection.Mode == TextSelectionMode.Box)
            {
                // No completion with multiple selection
                return(false);
            }

            // The caret may no longer be mappable into our subject buffer.
            var caret = TextView.GetCaretPoint(SubjectBuffer);

            if (!caret.HasValue)
            {
                return(false);
            }

            if (this.TextView.Caret.Position.VirtualBufferPosition.IsInVirtualSpace)
            {
                // Convert any virtual whitespace to real whitespace by doing an empty edit at the caret position.
                _editorOperationsFactoryService.GetEditorOperations(TextView).InsertText("");
            }

            var computation = new ModelComputation <Model>(ThreadingContext, this, PrioritizedTaskScheduler.AboveNormalInstance);

            this.sessionOpt = new Session(this, computation, Presenter.CreateSession(TextView, SubjectBuffer, null));

            sessionOpt.ComputeModel(completionService, trigger, _roles, GetOptions());
            sessionOpt.FilterModel(trigger.GetFilterReason(), filterState: null);

            return(true);
        }
Beispiel #15
0
        /// <summary>
        /// Returns true if the completion item matches the filter text typed so far.  Returns 'true'
        /// iff the completion item matches and should be included in the filtered completion
        /// results, or false if it should not be.
        /// </summary>
        public virtual bool MatchesFilterText(CompletionItem item, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray<string> recentItems = default(ImmutableArray<string>))
        {
            // If the user hasn't typed anything, and this item was preselected, or was in the
            // MRU list, then we definitely want to include it.
            if (filterText.Length == 0)
            {
                if (item.Rules.Preselect || (!recentItems.IsDefault && GetRecentItemIndex(recentItems, item) < 0))
                {
                    return true;
                }
            }

            if (IsAllDigits(filterText))
            {
                // The user is just typing a number.  We never want this to match against
                // anything we would put in a completion list.
                return false;
            }

            return GetMatch(item, filterText) != null;
        }
Beispiel #16
0
        private Model(
            DisconnectedBufferGraph disconnectedBufferGraph,
            CompletionList originalList,
            ImmutableArray<PresentationItem> totalItems,
            ImmutableArray<PresentationItem> filteredItems,
            PresentationItem selectedItem,
            ImmutableArray<CompletionItemFilter> completionItemFilters,
            ImmutableDictionary<CompletionItemFilter, bool> filterState,
            IReadOnlyDictionary<CompletionItem, string> completionItemToFilterText,
            bool isHardSelection,
            bool isUnique,
            bool useSuggestionMode,
            PresentationItem suggestionModeItem,
            PresentationItem defaultSuggestionModeItem,
            CompletionTrigger trigger,
            ITrackingPoint commitSpanEndPoint,
            bool dismissIfEmpty)
        {
            Contract.ThrowIfNull(selectedItem);
            Contract.ThrowIfFalse(totalItems.Length != 0, "Must have at least one item.");
            Contract.ThrowIfFalse(filteredItems.Length != 0, "Must have at least one filtered item.");
            Contract.ThrowIfFalse(filteredItems.Contains(selectedItem) || defaultSuggestionModeItem == selectedItem, "Selected item must be in filtered items.");

            _disconnectedBufferGraph = disconnectedBufferGraph;
            this.OriginalList = originalList;
            this.TotalItems = totalItems;
            this.FilteredItems = filteredItems;
            this.FilterState = filterState;
            this.SelectedItem = selectedItem;
            this.CompletionItemFilters = completionItemFilters;
            this.CompletionItemToFilterText = completionItemToFilterText;
            this.IsHardSelection = isHardSelection;
            this.IsUnique = isUnique;
            this.UseSuggestionMode = useSuggestionMode;
            this.SuggestionModeItem = suggestionModeItem;
            this.DefaultSuggestionModeItem = defaultSuggestionModeItem;
            this.Trigger = trigger;
            this.CommitTrackingSpanEndPoint = commitSpanEndPoint;
            this.DismissIfEmpty = dismissIfEmpty;
        }
Beispiel #17
0
        private Model(
            Document triggerDocument,
            DisconnectedBufferGraph disconnectedBufferGraph,
            CompletionList originalList,
            ImmutableArray<CompletionItem> filteredItems,
            CompletionItem selectedItem,
            ImmutableArray<CompletionItemFilter> completionItemFilters,
            ImmutableDictionary<CompletionItemFilter, bool> filterState,
            string filterText,
            bool isHardSelection,
            bool isUnique,
            bool useSuggestionMode,
            CompletionItem suggestionModeItem,
            CompletionTrigger trigger,
            ITrackingPoint commitSpanEndPoint,
            bool dismissIfEmpty)
        {
            Contract.ThrowIfFalse(originalList.Items.Length != 0, "Must have at least one item.");
            Contract.ThrowIfNull(selectedItem);

            this.TriggerDocument = triggerDocument;
            _disconnectedBufferGraph = disconnectedBufferGraph;
            this.OriginalList = originalList;
            this.FilteredItems = filteredItems;
            this.FilterState = filterState;
            this.SelectedItem = selectedItem;
            this.CompletionItemFilters = completionItemFilters;
            this.FilterText = filterText;
            this.IsHardSelection = isHardSelection;
            this.IsUnique = isUnique;
            this.Trigger = trigger;
            this.CommitTrackingSpanEndPoint = commitSpanEndPoint;
            this.DismissIfEmpty = dismissIfEmpty;

            this.UseSuggestionMode = useSuggestionMode;
            this.SuggestionModeItem = suggestionModeItem ?? CreateDefaultSuggestionModeItem();
        }
Beispiel #18
0
        /// <summary>
        /// Creates a <see cref="CompletionContext"/> instance.
        /// </summary>
        public CompletionContext(
            CompletionProvider provider,
            Document document,
            int position,
            TextSpan defaultSpan,
            CompletionTrigger trigger,
            OptionSet options,
            CancellationToken cancellationToken)
        {
            if (provider == null)
            {
                throw new ArgumentNullException(nameof(provider));
            }

            if (document == null)
            {
                throw new ArgumentNullException(nameof(document));
            }

            if (options == null)
            {
                throw new ArgumentException(nameof(options));
            }

            this.Provider = provider;
            this.Document = document;
            this.Position = position;
#pragma warning disable CS0618 // Type or member is obsolete
            this.DefaultItemSpan = defaultSpan;
#pragma warning restore CS0618 // Type or member is obsolete
            this.CompletionListSpan = defaultSpan;
            this.Trigger = trigger;
            this.Options = options;
            this.CancellationToken = cancellationToken;
            _items = new List<CompletionItem>();
        }
 private async Task<CompletionList> GetCompletionListAsync(CompletionService completionService, CompletionTrigger trigger, CancellationToken cancellationToken)
 {
     return _documentOpt != null
         ? await completionService.GetCompletionsAsync(_documentOpt, _subjectBufferCaretPosition, trigger, _roles, _options, cancellationToken).ConfigureAwait(false)
         : null;
 }
Beispiel #20
0
 /// <summary>
 /// Returns true if the completion item should be "soft" selected, or false if it should be "hard"
 /// selected.
 /// </summary>
 public virtual bool ShouldSoftSelectItem(CompletionItem item, string filterText, CompletionTrigger trigger)
 {
     return filterText.Length == 0 && item.Rules.MatchPriority == MatchPriority.Default;
 }
Beispiel #21
0
 /// <summary>
 /// This allows Completion Providers that indicated they were triggered textually to use syntax to
 /// confirm they are really triggered, or decide they are not actually triggered and should become
 /// an augmenting provider instead.
 /// </summary>
 internal virtual async Task <bool> IsSyntacticTriggerCharacterAsync(Document document, int caretPosition, CompletionTrigger trigger, OptionSet options, CancellationToken cancellationToken)
 => ShouldTriggerCompletion(await document.GetTextAsync(cancellationToken).ConfigureAwait(false), caretPosition, trigger, options);
        protected override async Task<IEnumerable<CompletionItem>> GetItemsWorkerAsync(Document document, int position, TextSpan span, CompletionTrigger trigger, CancellationToken cancellationToken)
        {
            var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken);
            var parentTrivia = token.GetAncestor<DocumentationCommentTriviaSyntax>();

            if (parentTrivia == null)
            {
                return null;
            }

            var items = new List<CompletionItem>();
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var attachedToken = parentTrivia.ParentTrivia.Token;
            if (attachedToken.Kind() == SyntaxKind.None)
            {
                return null;
            }

            var semanticModel = await document.GetSemanticModelForNodeAsync(attachedToken.Parent, cancellationToken).ConfigureAwait(false);

            ISymbol declaredSymbol = null;
            var memberDeclaration = attachedToken.GetAncestor<MemberDeclarationSyntax>();
            if (memberDeclaration != null)
            {
                declaredSymbol = semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken);
            }
            else
            {
                var typeDeclaration = attachedToken.GetAncestor<TypeDeclarationSyntax>();
                if (typeDeclaration != null)
                {
                    declaredSymbol = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);
                }
            }

            if (declaredSymbol != null)
            {
                items.AddRange(GetTagsForSymbol(declaredSymbol, span, parentTrivia, token));
            }

            if (token.Parent.Kind() == SyntaxKind.XmlEmptyElement || token.Parent.Kind() == SyntaxKind.XmlText ||
                (token.Parent.IsKind(SyntaxKind.XmlElementEndTag) && token.IsKind(SyntaxKind.GreaterThanToken)) ||
                (token.Parent.IsKind(SyntaxKind.XmlName) && token.Parent.IsParentKind(SyntaxKind.XmlEmptyElement)))
            {
                // The user is typing inside an XmlElement
                if (token.Parent.Parent.Kind() == SyntaxKind.XmlElement)
                {
                    items.AddRange(GetNestedTags(span, declaredSymbol));
                }

                if (token.Parent.Parent.Kind() == SyntaxKind.XmlElement && ((XmlElementSyntax)token.Parent.Parent).StartTag.Name.LocalName.ValueText == ListTagName)
                {
                    items.AddRange(GetListItems(span));
                }

                if (token.Parent.IsParentKind(SyntaxKind.XmlEmptyElement) & token.Parent.Parent.IsParentKind(SyntaxKind.XmlElement))
                {
                    var element = (XmlElementSyntax)token.Parent.Parent.Parent;
                    if (element.StartTag.Name.LocalName.ValueText == ListTagName)
                    {
                        items.AddRange(GetListItems(span));
                    }
                }

                if (token.Parent.Parent.Kind() == SyntaxKind.XmlElement && ((XmlElementSyntax)token.Parent.Parent).StartTag.Name.LocalName.ValueText == ListHeaderTagName)
                {
                    items.AddRange(GetListHeaderItems(span));
                }

                if (token.Parent.Parent is DocumentationCommentTriviaSyntax)
                {
                    items.AddRange(GetTopLevelSingleUseNames(parentTrivia, span));
                    items.AddRange(GetTopLevelRepeatableItems(span));
                }
            }

            if (token.Parent.Kind() == SyntaxKind.XmlElementStartTag)
            {
                var startTag = (XmlElementStartTagSyntax)token.Parent;

                if (token == startTag.GreaterThanToken && startTag.Name.LocalName.ValueText == ListTagName)
                {
                    items.AddRange(GetListItems(span));
                }

                if (token == startTag.GreaterThanToken && startTag.Name.LocalName.ValueText == ListHeaderTagName)
                {
                    items.AddRange(GetListHeaderItems(span));
                }
            }

            items.AddRange(GetAlwaysVisibleItems(span));
            return items;
        }
Beispiel #23
0
 /// <summary>
 /// Returns true if the character recently inserted or deleted in the text should trigger completion.
 /// </summary>
 /// <param name="text">The text that completion is occuring within.</param>
 /// <param name="caretPosition">The position of the caret after the triggering action.</param>
 /// <param name="trigger">The triggering action.</param>
 /// <param name="options">The set of options in effect.</param>
 public virtual bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options)
 {
     return false;
 }
Beispiel #24
0
        /// <summary>
        /// Returns true if item1 is a better completion item than item2 given the provided filter
        /// text, or false if it is not better.
        /// </summary>
        public virtual bool IsBetterFilterMatch(CompletionItem item1, CompletionItem item2, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray<string> recentItems = default(ImmutableArray<string>))
        {
            var match1 = GetMatch(item1, GetCultureSpecificQuirks(filterText));
            var match2 = GetMatch(item2, GetCultureSpecificQuirks(filterText));

            if (match1 != null && match2 != null)
            {
                var result = CompareMatches(match1.Value, match2.Value, item1, item2);
                if (result != 0)
                {
                    return result < 0;
                }
            }
            else if (match1 != null)
            {
                return true;
            }
            else if (match2 != null)
            {
                return false;
            }

            // If they both seemed just as good, but they differ on preselection, then
            // item1 is better if it is preselected, otherwise it is worse.
            if (item1.Rules.Preselect != item2.Rules.Preselect)
            {
                return item1.Rules.Preselect;
            }

            // Prefer things with a keyword tag, if the filter texts are the same.
            if (!TagsEqual(item1, item2) && item1.FilterText == item2.FilterText)
            {
                return IsKeywordItem(item1);
            }

            // They matched on everything, including preselection values.  Item1 is better if it
            // has a lower MRU index.

            if (!recentItems.IsDefault)
            {
                var item1MRUIndex = GetRecentItemIndex(recentItems, item1);
                var item2MRUIndex = GetRecentItemIndex(recentItems, item2);

                // The one with the lower index is the better one.
                return item1MRUIndex < item2MRUIndex;
            }

            return false;
        }
        public override async Task <CompletionList> GetCompletionsAsync(
            Document document,
            int caretPosition,
            CompletionTrigger trigger,
            ImmutableHashSet <string> roles,
            OptionSet options,
            CancellationToken cancellationToken)
        {
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var defaultItemSpan = this.GetDefaultCompletionListSpan(text, caretPosition);

            options = options ?? document.Options;
            var providers = GetProviders(roles, trigger);

            var completionProviderToIndex = GetCompletionProviderToIndex(providers);

            var triggeredProviders = ImmutableArray <CompletionProvider> .Empty;

            switch (trigger.Kind)
            {
            case CompletionTriggerKind.Insertion:
            case CompletionTriggerKind.Deletion:
                if (this.ShouldTriggerCompletion(text, caretPosition, trigger, roles, options))
                {
                    triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty();
                    if (triggeredProviders.Length == 0)
                    {
                        triggeredProviders = providers;
                    }
                }
                break;

            default:
                triggeredProviders = providers;
                break;
            }

            // Now, ask all the triggered providers if they can provide a group.
            var completionContexts = new List <CompletionContext>();

            foreach (var provider in triggeredProviders)
            {
                var completionContext = await GetContextAsync(
                    provider, document, caretPosition, trigger,
                    options, defaultItemSpan, cancellationToken).ConfigureAwait(false);

                if (completionContext != null)
                {
                    completionContexts.Add(completionContext);
                }
            }

            // See if there was a group provided that was exclusive and had items in it.  If so, then
            // that's all we'll return.
            var firstExclusiveContext = completionContexts.FirstOrDefault(t => t.IsExclusive && t.Items.Any());

            if (firstExclusiveContext != null)
            {
                return(MergeAndPruneCompletionLists(
                           SpecializedCollections.SingletonEnumerable(firstExclusiveContext), defaultItemSpan,
                           isExclusive: true));
            }

            // If no exclusive providers provided anything, then go through the remaining
            // triggered list and see if any provide items.
            var nonExclusiveLists = completionContexts.Where(t => !t.IsExclusive).ToList();

            // If we still don't have any items, then we're definitely done.
            if (!nonExclusiveLists.Any(g => g.Items.Any()))
            {
                return(null);
            }

            // If we do have items, then ask all the other (non exclusive providers) if they
            // want to augment the items.
            var usedProviders            = nonExclusiveLists.Select(g => g.Provider);
            var nonUsedProviders         = providers.Except(usedProviders);
            var nonUsedNonExclusiveLists = new List <CompletionContext>();

            foreach (var provider in nonUsedProviders)
            {
                var completionList = await GetContextAsync(provider, document, caretPosition, trigger, options, defaultItemSpan, cancellationToken).ConfigureAwait(false);

                if (completionList != null && !completionList.IsExclusive)
                {
                    nonUsedNonExclusiveLists.Add(completionList);
                }
            }

            var allProvidersAndLists = nonExclusiveLists.Concat(nonUsedNonExclusiveLists).ToList();

            if (allProvidersAndLists.Count == 0)
            {
                return(null);
            }

            // Providers are ordered, but we processed them in our own order.  Ensure that the
            // groups are properly ordered based on the original providers.
            allProvidersAndLists.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]);

            return(MergeAndPruneCompletionLists(allProvidersAndLists, defaultItemSpan, isExclusive: false));
        }
Beispiel #26
0
 protected virtual ImmutableArray <CompletionProvider> GetProviders(ImmutableHashSet <string> roles, CompletionTrigger trigger)
 {
     if (trigger.Kind == CompletionTriggerKind.Snippets)
     {
         return(GetProviders(roles).Where(p => p.IsSnippetProvider).ToImmutableArray());
     }
     else
     {
         return(GetProviders(roles));
     }
 }
Beispiel #27
0
 protected virtual ImmutableArray <CompletionProvider> GetProviders(
     ImmutableHashSet <string> roles, CompletionTrigger trigger)
 {
     return(GetProviders(roles));
 }
Beispiel #28
0
        public override async Task <CompletionList> GetCompletionsAsync(
            Document document,
            int caretPosition,
            CompletionTrigger trigger,
            ImmutableHashSet <string> roles,
            OptionSet options,
            CancellationToken cancellationToken)
        {
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var defaultItemSpan = this.GetDefaultCompletionListSpan(text, caretPosition);

            options = options ?? await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);

            var providers = GetFilteredProviders(roles, trigger, options);

            var completionProviderToIndex = GetCompletionProviderToIndex(providers);

            var triggeredProviders = ImmutableArray <CompletionProvider> .Empty;

            switch (trigger.Kind)
            {
            case CompletionTriggerKind.Insertion:
            case CompletionTriggerKind.Deletion:
                if (this.ShouldTriggerCompletion(text, caretPosition, trigger, roles, options))
                {
                    triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty();
                    if (triggeredProviders.Length == 0)
                    {
                        triggeredProviders = providers;
                    }
                }
                break;

            default:
                triggeredProviders = providers;
                break;
            }

            // Now, ask all the triggered providers, in parallel, to populate a completion context.
            // Note: we keep any context with items *or* with a suggested item.
            var triggeredCompletionContexts = await ComputeNonEmptyCompletionContextsAsync(
                document, caretPosition, trigger, options,
                defaultItemSpan, triggeredProviders,
                cancellationToken).ConfigureAwait(false);

            // If we didn't even get any back with items, then there's nothing to do.
            // i.e. if only got items back that had only suggestion items, then we don't
            // want to show any completion.
            if (!triggeredCompletionContexts.Any(cc => cc.Items.Count > 0))
            {
                return(null);
            }

            // All the contexts should be non-empty or have a suggestion item.
            Debug.Assert(triggeredCompletionContexts.All(HasAnyItems));

            // See if there was a completion context provided that was exclusive.  If so, then
            // that's all we'll return.
            var firstExclusiveContext = triggeredCompletionContexts.FirstOrDefault(t => t.IsExclusive);

            if (firstExclusiveContext != null)
            {
                return(MergeAndPruneCompletionLists(
                           SpecializedCollections.SingletonEnumerable(firstExclusiveContext),
                           defaultItemSpan,
                           isExclusive: true));
            }

            // Shouldn't be any exclusive completion contexts at this point.
            Debug.Assert(triggeredCompletionContexts.All(cc => !cc.IsExclusive));

            // Great!  We had some items.  Now we want to see if any of the other providers
            // would like to augment the completion list.  For example, we might trigger
            // enum-completion on space.  If enum completion results in any items, then
            // we'll want to augment the list with all the regular symbol completion items.
            var augmentingProviders = providers.Except(triggeredProviders).ToImmutableArray();

            var augmentingCompletionContexts = await ComputeNonEmptyCompletionContextsAsync(
                document, caretPosition, trigger, options, defaultItemSpan,
                augmentingProviders, cancellationToken).ConfigureAwait(false);

            var allContexts = triggeredCompletionContexts.Concat(augmentingCompletionContexts);

            Debug.Assert(allContexts.Length > 0);

            // Providers are ordered, but we processed them in our own order.  Ensure that the
            // groups are properly ordered based on the original providers.
            allContexts = allContexts.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]);

            return(MergeAndPruneCompletionLists(allContexts, defaultItemSpan, isExclusive: false));
        }
Beispiel #29
0
 private ImmutableArray <CompletionProvider> GetFilteredProviders(
     ImmutableHashSet <string> roles, CompletionTrigger trigger, OptionSet options)
 {
     return(FilterProviders(GetProviders(roles, trigger), trigger, options));
 }
Beispiel #30
0
 public static CompletionFilterReason GetFilterReason(this CompletionTrigger trigger)
 => trigger.Kind.GetFilterReason();
Beispiel #31
0
        public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, ImmutableHashSet <string> roles = null, OptionSet options = null)
        {
            options = options ?? _workspace.Options;
            if (!options.GetOption(CompletionOptions.TriggerOnTyping, this.Language))
            {
                return(false);
            }

            var providers = this.GetProviders(roles, CompletionTrigger.Default);

            return(providers.Any(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)));
        }
            private static bool MatchesFilterText(
                CompletionHelper helper, CompletionItem item,
                string filterText, CompletionTrigger trigger,
                CompletionFilterReason filterReason, ImmutableArray<string> recentItems)
            {
                // For the deletion we bake in the core logic for how matching should work.
                // This way deletion feels the same across all languages that opt into deletion 
                // as a completion trigger.

                // Specifically, to avoid being too aggressive when matching an item during 
                // completion, we require that the current filter text be a prefix of the 
                // item in the list.
                if (filterReason == CompletionFilterReason.BackspaceOrDelete &&
                    trigger.Kind == CompletionTriggerKind.Deletion)
                {
                    return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0;
                }

                return helper.MatchesFilterText(item, filterText, trigger, recentItems);
            }
Beispiel #33
0
 internal override bool ShouldTriggerCompletion(HostLanguageServices languageServices, SourceText text, int caretPosition, CompletionTrigger trigger, CompletionOptions options, OptionSet passThroughOptions)
 => ShouldTriggerCompletionImpl(text, caretPosition, trigger, options);
            /// <summary>
            /// Returns true if the completion item should be "soft" selected, or false if it should be "hard"
            /// selected.
            /// </summary>
            private static bool ShouldSoftSelectItem(CompletionItem item, string filterText, CompletionTrigger trigger)
            {
                // If all that has been typed is puntuation, then don't hard select anything.
                // It's possible the user is just typing language punctuation and selecting
                // anything in the list will interfere.  We only allow this if the filter text
                // exactly matches something in the list already. 
                if (filterText.Length > 0 && IsAllPunctuation(filterText) && filterText != item.DisplayText)
                {
                    return true;
                }

                // If the user hasn't actually typed anything, then don't hard select any item.
                // The only exception to this is if the completion provider has requested the
                // item be preselected.
                if (filterText.Length == 0)
                {
                    // Item didn't want to be hard selected with no filter text.
                    // So definitely soft select it.
                    if (item.Rules.SelectionBehavior != CompletionItemSelectionBehavior.HardSelection)
                    {
                        return true;
                    }

                    // Item did not ask to be preselected.  So definitely soft select it.
                    if (item.Rules.MatchPriority == MatchPriority.Default)
                    {
                        return true;
                    }
                }

                // The user typed something, or the item asked to be preselected.  In 
                // either case, don't soft select this.
                Debug.Assert(filterText.Length > 0 || item.Rules.MatchPriority != MatchPriority.Default);
                return false;
            }
Beispiel #35
0
        /// <summary>
        /// For backwards API compat only, should not be called.
        /// </summary>
        public sealed override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options)
        {
            Debug.Fail("For backwards API compat only, should not be called");

            // Publicly available options do not affect this API.
            return(ShouldTriggerCompletionImpl(text, caretPosition, trigger, CompletionOptions.Default));
        }
            static async Task <ImmutableArray <CompletionProvider> > GetAugmentingProviders(
                Document document, ImmutableArray <CompletionProvider> triggeredProviders, int caretPosition, CompletionTrigger trigger, CompletionOptions options, CancellationToken cancellationToken)
            {
                var additionalAugmentingProviders = ArrayBuilder <CompletionProvider> .GetInstance(triggeredProviders.Length);

                if (trigger.Kind == CompletionTriggerKind.Insertion)
                {
                    foreach (var provider in triggeredProviders)
                    {
                        if (!await provider.IsSyntacticTriggerCharacterAsync(document, caretPosition, trigger, options, cancellationToken).ConfigureAwait(false))
                        {
                            additionalAugmentingProviders.Add(provider);
                        }
                    }
                }

                return(additionalAugmentingProviders.ToImmutableAndFree());
            }
 protected virtual ImmutableArray<CompletionProvider> GetProviders(ImmutableHashSet<string> roles, CompletionTrigger trigger)
 {
     if (trigger.Kind == CompletionTriggerKind.Snippets)
     {
         return GetProviders(roles).Where(p => p.IsSnippetProvider).ToImmutableArray();
     }
     else
     {
         return GetProviders(roles);
     }
 }
        private protected async Task <CompletionList> GetCompletionsWithAvailabilityOfExpandedItemsAsync(
            Document document,
            int caretPosition,
            CompletionOptions options,
            OptionSet passThroughOptions,
            CompletionTrigger trigger,
            ImmutableHashSet <string>?roles,
            CancellationToken cancellationToken)
        {
            // We don't need SemanticModel here, just want to make sure it won't get GC'd before CompletionProviders are able to get it.
            (document, var semanticModel) = await GetDocumentWithFrozenPartialSemanticsAsync(document, cancellationToken).ConfigureAwait(false);

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var defaultItemSpan = GetDefaultCompletionListSpan(text, caretPosition);

            var providers = _providerManager.GetFilteredProviders(document.Project, roles, trigger, options);

            // Phase 1: Completion Providers decide if they are triggered based on textual analysis
            // Phase 2: Completion Providers use syntax to confirm they are triggered, or decide they are not actually triggered and should become an augmenting provider
            // Phase 3: Triggered Providers are asked for items
            // Phase 4: If any items were provided, all augmenting providers are asked for items
            // This allows a provider to be textually triggered but later decide to be an augmenting provider based on deeper syntactic analysis.

            var triggeredProviders = GetTriggeredProviders(document, providers, caretPosition, options, trigger, roles, text);

            var additionalAugmentingProviders = await GetAugmentingProviders(document, triggeredProviders, caretPosition, trigger, options, cancellationToken).ConfigureAwait(false);

            triggeredProviders = triggeredProviders.Except(additionalAugmentingProviders).ToImmutableArray();

            // Now, ask all the triggered providers, in parallel, to populate a completion context.
            // Note: we keep any context with items *or* with a suggested item.
            var triggeredContexts = await ComputeNonEmptyCompletionContextsAsync(
                document, caretPosition, trigger, options, defaultItemSpan, triggeredProviders, cancellationToken).ConfigureAwait(false);

            // Nothing to do if we didn't even get any regular items back (i.e. 0 items or suggestion item only.)
            if (!triggeredContexts.Any(cc => cc.Items.Count > 0))
            {
                return(CompletionList.Empty);
            }

            // See if there were completion contexts provided that were exclusive. If so, then
            // that's all we'll return.
            var exclusiveContexts = triggeredContexts.Where(t => t.IsExclusive).ToImmutableArray();

            if (!exclusiveContexts.IsEmpty)
            {
                return(MergeAndPruneCompletionLists(exclusiveContexts, defaultItemSpan, options, isExclusive: true));
            }

            // Great!  We had some items.  Now we want to see if any of the other providers
            // would like to augment the completion list.  For example, we might trigger
            // enum-completion on space.  If enum completion results in any items, then
            // we'll want to augment the list with all the regular symbol completion items.
            var augmentingProviders = providers.Except(triggeredProviders).ToImmutableArray();

            var augmentingContexts = await ComputeNonEmptyCompletionContextsAsync(
                document, caretPosition, trigger, options, defaultItemSpan, augmentingProviders, cancellationToken).ConfigureAwait(false);

            GC.KeepAlive(semanticModel);

            // Providers are ordered, but we processed them in our own order.  Ensure that the
            // groups are properly ordered based on the original providers.
            var completionProviderToIndex = GetCompletionProviderToIndex(providers);
            var allContexts = triggeredContexts.Concat(augmentingContexts)
                              .Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]);

            return(MergeAndPruneCompletionLists(allContexts, defaultItemSpan, options, isExclusive: false));

            ImmutableArray <CompletionProvider> GetTriggeredProviders(
                Document document, ConcatImmutableArray <CompletionProvider> providers, int caretPosition, CompletionOptions options, CompletionTrigger trigger, ImmutableHashSet <string>?roles, SourceText text)
            {
                switch (trigger.Kind)
                {
                case CompletionTriggerKind.Insertion:
                case CompletionTriggerKind.Deletion:

                    if (ShouldTriggerCompletion(document.Project, document.Project.LanguageServices, text, caretPosition, trigger, options, passThroughOptions, roles))
                    {
                        var triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(document.Project.LanguageServices, text, caretPosition, trigger, options, passThroughOptions)).ToImmutableArrayOrEmpty();

                        Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition, options));
                        return(triggeredProviders.IsEmpty ? providers.ToImmutableArray() : triggeredProviders);
                    }

                    return(ImmutableArray <CompletionProvider> .Empty);

                default:
                    return(providers.ToImmutableArray());
                }
            }
        public override async Task<CompletionList> GetCompletionsAsync(
            Document document,
            int caretPosition,
            CompletionTrigger trigger,
            ImmutableHashSet<string> roles,
            OptionSet options,
            CancellationToken cancellationToken)
        {
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
            var defaultItemSpan = this.GetDefaultItemSpan(text, caretPosition);

            options = options ?? document.Project.Solution.Workspace.Options;
            var providers = GetProviders(roles, trigger);

            var completionProviderToIndex = GetCompletionProviderToIndex(providers);
            var completionRules = this.GetRules();

            var triggeredProviders = ImmutableArray<CompletionProvider>.Empty;
            switch (trigger.Kind)
            {
                case CompletionTriggerKind.Insertion:
                case CompletionTriggerKind.Deletion:
                    if (this.ShouldTriggerCompletion(text, caretPosition, trigger, roles, options))
                    {
                        triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty();
                        if (triggeredProviders.Length == 0)
                        {
                            triggeredProviders = providers;
                        }
                    }
                    break;
                default:
                    triggeredProviders = providers;
                    break;
            }

            // Now, ask all the triggered providers if they can provide a group.
            var completionLists = new List<CompletionContext>();
            foreach (var provider in triggeredProviders)
            {
                var completionList = await GetProviderCompletionsAsync(provider, document, caretPosition, defaultItemSpan, trigger, options, cancellationToken).ConfigureAwait(false);
                if (completionList != null)
                {
                    completionLists.Add(completionList);
                }
            }

            // See if there was a group provided that was exclusive and had items in it.  If so, then
            // that's all we'll return.
            var firstExclusiveList = completionLists.FirstOrDefault(t => t.IsExclusive && t.Items.Any());

            if (firstExclusiveList != null)
            {
                return MergeAndPruneCompletionLists(SpecializedCollections.SingletonEnumerable(firstExclusiveList), defaultItemSpan);
            }

            // If no exclusive providers provided anything, then go through the remaining
            // triggered list and see if any provide items.
            var nonExclusiveLists = completionLists.Where(t => !t.IsExclusive).ToList();

            // If we still don't have any items, then we're definitely done.
            if (!nonExclusiveLists.Any(g => g.Items.Any()))
            {
                return null;
            }

            // If we do have items, then ask all the other (non exclusive providers) if they
            // want to augment the items.
            var usedProviders = nonExclusiveLists.Select(g => g.Provider);
            var nonUsedProviders = providers.Except(usedProviders);
            var nonUsedNonExclusiveLists = new List<CompletionContext>();
            foreach (var provider in nonUsedProviders)
            {
                var completionList = await GetProviderCompletionsAsync(provider, document, caretPosition, defaultItemSpan, trigger, options, cancellationToken).ConfigureAwait(false);
                if (completionList != null && !completionList.IsExclusive)
                {
                    nonUsedNonExclusiveLists.Add(completionList);
                }
            }

            var allProvidersAndLists = nonExclusiveLists.Concat(nonUsedNonExclusiveLists).ToList();
            if (allProvidersAndLists.Count == 0)
            {
                return null;
            }

            // Providers are ordered, but we processed them in our own order.  Ensure that the
            // groups are properly ordered based on the original providers.
            allProvidersAndLists.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]);

            return MergeAndPruneCompletionLists(allProvidersAndLists, defaultItemSpan);
        }
Beispiel #40
0
 /// <summary>
 /// Returns true if the completion item should be "soft" selected, or false if it should be "hard"
 /// selected.
 /// </summary>
 public virtual bool ShouldSoftSelectItem(CompletionItem item, string filterText, CompletionTrigger trigger)
 {
     return filterText.Length == 0 && !item.Rules.Preselect;
 }
 private static async Task<CompletionContext> GetProviderCompletionsAsync(
     CompletionProvider provider,
     Document document,
     int position,
     TextSpan defaultFilterSpan,
     CompletionTrigger triggerInfo,
     OptionSet options,
     CancellationToken cancellationToken)
 {
     var context = new CompletionContext(provider, document, position, defaultFilterSpan, triggerInfo, options, cancellationToken);
     await provider.ProvideCompletionsAsync(context).ConfigureAwait(false);
     return context;
 }
Beispiel #42
0
        public static Model CreateModel(
            Document triggerDocument,
            DisconnectedBufferGraph disconnectedBufferGraph,
            CompletionList originalList,
            CompletionItem selectedItem,
            bool isHardSelection,
            bool isUnique,
            bool useSuggestionMode,
            CompletionTrigger trigger,
            CompletionService completionService,
            Workspace workspace)
        {
            ImmutableArray<PresentationItem> totalItems;
            CompletionItem suggestionModeItem = originalList.SuggestionModeItem;
            PresentationItem suggestionModePresentationItem;
            PresentationItem defaultSuggestionModePresentationItem;

            // Get the set of actual filters used by all the completion items 
            // that are in the list.
            var actualFiltersSeen = new HashSet<CompletionItemFilter>();
            foreach (var item in originalList.Items)
            {
                foreach (var filter in CompletionItemFilter.AllFilters)
                {
                    if (filter.Matches(item))
                    {
                        actualFiltersSeen.Add(filter);
                    }
                }
            }

            // The set of filters we'll want to show the user are the filters that are actually
            // used by our completion items.  i.e. there's no reason to show the "field" filter
            // if none of completion items is actually a field.
            var actualItemFilters = CompletionItemFilter.AllFilters.Where(actualFiltersSeen.Contains)
                                                                   .ToImmutableArray();

            // By default we do not filter anything out.
            ImmutableDictionary<CompletionItemFilter, bool> filterState = null;

            if (completionService != null &&
                workspace != null &&
                workspace.Kind != WorkspaceKind.Interactive && // TODO (https://github.com/dotnet/roslyn/issues/5107): support in interactive
                workspace.Options.GetOption(InternalFeatureOnOffOptions.Snippets) &&
                trigger.Kind != CompletionTriggerKind.Snippets)
            {
                // In order to add snippet expansion notes to completion item descriptions, update
                // all of the provided CompletionItems to DescriptionModifyingCompletionItem which will proxy
                // requests to the original completion items and add the snippet expansion note to
                // the description if necessary. We won't do this if the list was triggered to show
                // snippet shortcuts.

                var totalItemsBuilder = ArrayBuilder<PresentationItem>.GetInstance();
                foreach (var item in originalList.Items)
                {
                    totalItemsBuilder.Add(new DescriptionModifyingPresentationItem(item, completionService));
                }

                totalItems = totalItemsBuilder.ToImmutableAndFree();
                defaultSuggestionModePresentationItem = new DescriptionModifyingPresentationItem(
                    CreateDefaultSuggestionModeItem(), completionService, isSuggestionModeItem: true);
                suggestionModePresentationItem = suggestionModeItem != null ? new DescriptionModifyingPresentationItem(suggestionModeItem, completionService, isSuggestionModeItem: true) : null;
            }
            else
            {
                totalItems = originalList.Items.Select(item => new SimplePresentationItem(item, completionService)).ToImmutableArray<PresentationItem>();
                defaultSuggestionModePresentationItem = new SimplePresentationItem(CreateDefaultSuggestionModeItem(), completionService, isSuggestionModeItem: true);
                suggestionModePresentationItem = suggestionModeItem != null ? new SimplePresentationItem(suggestionModeItem, completionService, isSuggestionModeItem: true) : null;
            }

            var selectedPresentationItem = totalItems.FirstOrDefault(it => it.Item == selectedItem);

            return new Model(
                triggerDocument,
                disconnectedBufferGraph,
                originalList,
                totalItems,
                totalItems,
                selectedPresentationItem,
                actualItemFilters,
                filterState,
                "",
                isHardSelection,
                isUnique,
                useSuggestionMode,
                suggestionModePresentationItem,
                defaultSuggestionModePresentationItem,
                trigger,
                GetDefaultTrackingSpanEnd(originalList.Span, disconnectedBufferGraph),
                originalList.Rules.DismissIfEmpty);
        }
        public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, ImmutableHashSet<string> roles = null, OptionSet options = null)
        {
            options = options ?? _workspace.Options;
            if (!options.GetOption(CompletionOptions.TriggerOnTyping, this.Language))
            {
                return false;
            }

            var providers = this.GetProviders(roles, CompletionTrigger.Default);
            return providers.Any(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options));
        }
            private static bool MatchesFilterText(
                CompletionHelper helper, CompletionItem item,
                string filterText, CompletionTrigger trigger,
                CompletionFilterReason filterReason, ImmutableArray<string> recentItems)
            {
                // For the deletion we bake in the core logic for how matching should work.
                // This way deletion feels the same across all languages that opt into deletion 
                // as a completion trigger.

                // Specifically, to avoid being too aggressive when matching an item during 
                // completion, we require that the current filter text be a prefix of the 
                // item in the list.
                if (filterReason == CompletionFilterReason.BackspaceOrDelete &&
                    trigger.Kind == CompletionTriggerKind.Deletion)
                {
                    return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0;
                }

                // If the user hasn't typed anything, and this item was preselected, or was in the
                // MRU list, then we definitely want to include it.
                if (filterText.Length == 0)
                {
                    if (item.Rules.MatchPriority > MatchPriority.Default)
                    {
                        return true;
                    }

                    if (!recentItems.IsDefault && GetRecentItemIndex(recentItems, item) < 0)
                    {
                        return true;
                    }
                }

                if (filterText.Length > 0 && IsAllDigits(filterText))
                {
                    // The user is just typing a number.  We never want this to match against
                    // anything we would put in a completion list.
                    return false;
                }

                return helper.MatchesFilterText(item, filterText, CultureInfo.CurrentCulture);
            }
Beispiel #45
0
        public static Model CreateModel(
            Document triggerDocument,
            DisconnectedBufferGraph disconnectedBufferGraph,
            CompletionList originalList,
            bool useSuggestionMode,
            CompletionTrigger trigger)
        {
            var selectedItem = originalList.Items.First();
            var isHardSelection = false;
            var isUnique = false;

            // Get the set of actual filters used by all the completion items 
            // that are in the list.
            var actualFiltersSeen = new HashSet<CompletionItemFilter>();
            foreach (var item in originalList.Items)
            {
                foreach (var filter in CompletionItemFilter.AllFilters)
                {
                    if (filter.Matches(item))
                    {
                        actualFiltersSeen.Add(filter);
                    }
                }
            }

            // The set of filters we'll want to show the user are the filters that are actually
            // used by our completion items.  i.e. there's no reason to show the "field" filter
            // if none of completion items is actually a field.
            var actualItemFilters = CompletionItemFilter.AllFilters.Where(actualFiltersSeen.Contains)
                                                                   .ToImmutableArray();

            // By default we do not filter anything out.
            ImmutableDictionary<CompletionItemFilter, bool> filterState = null;

            return new Model(
                triggerDocument,
                disconnectedBufferGraph,
                originalList,
                originalList.Items,
                selectedItem,
                actualItemFilters,
                filterState,
                "",
                isHardSelection,
                isUnique,
                useSuggestionMode,
                originalList.SuggestionModeItem,
                trigger,
                GetDefaultTrackingSpanEnd(originalList.Span, disconnectedBufferGraph),
                originalList.Rules.DismissIfEmpty);
        }
 /// <summary>
 /// For backwards API compat only, should not be called.
 /// </summary>
 public sealed override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options)
 {
     Debug.Fail("For backwards API compat only, should not be called");
     return ShouldTriggerCompletionImpl(text, caretPosition, trigger, CompletionOptions.From(options, Language));
 }
Beispiel #47
0
 /// <summary>
 /// Returns true if the character recently inserted or deleted in the text should trigger completion.
 /// </summary>
 /// <param name="text">The text that completion is occuring within.</param>
 /// <param name="caretPosition">The position of the caret after the triggering action.</param>
 /// <param name="trigger">The triggering action.</param>
 /// <param name="options">The set of options in effect.</param>
 public virtual bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options)
 {
     return(false);
 }
            private static bool IsBetterFilterMatch(
                CompletionHelper helper, CompletionItem item1, CompletionItem item2,
                string filterText, CompletionTrigger trigger,
                CompletionFilterReason filterReason, ImmutableArray<string> recentItems)
            {
                // For the deletion we bake in the core logic for how betterness should work.
                // This way deletion feels the same across all languages that opt into deletion 
                // as a completion trigger.
                if (filterReason == CompletionFilterReason.BackspaceOrDelete)
                {
                    var prefixLength1 = item1.FilterText.GetCaseInsensitivePrefixLength(filterText);
                    var prefixLength2 = item2.FilterText.GetCaseInsensitivePrefixLength(filterText);

                    // Prefer the item that matches a longer prefix of the filter text.
                    if (prefixLength1 > prefixLength2)
                    {
                        return true;
                    }

                    // If the lengths are the same, prefer the one with the higher match priority.
                    // But only if it's an item that would have been hard selected.  We don't want
                    // to aggressively select an item that was only going to be softly offered.
                    var item1Priority = item1.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection
                        ? item1.Rules.MatchPriority : MatchPriority.Default;
                    var item2Priority = item2.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection
                        ? item2.Rules.MatchPriority : MatchPriority.Default;

                    if (item1Priority > item2Priority)
                    {
                        return true;
                    }

                    return false;
                }

                return helper.IsBetterFilterMatch(item1, item2, filterText, trigger, recentItems);
            }
 internal Task <CompletionList> GetCompletionListAsync(
     CompletionService service,
     Document document, int position, CompletionTrigger triggerInfo, OptionSet options = null)
 {
     return(service.GetCompletionsAsync(document, position, triggerInfo, options: options));
 }
            private bool IsHardSelection(
                Model model,
                PresentationItem bestFilterMatch,
                ITextSnapshot textSnapshot,
                CompletionHelper completionHelper,
                CompletionTrigger trigger,
                CompletionFilterReason reason)
            {
                if (model.SuggestionModeItem != null)
                {
                    return bestFilterMatch != null && bestFilterMatch.Item.DisplayText == model.SuggestionModeItem.Item.DisplayText;
                }

                if (bestFilterMatch == null || model.UseSuggestionMode)
                {
                    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.GetViewBufferSpan(bestFilterMatch.Item.Span);
                var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null);

                var shouldSoftSelect = ShouldSoftSelectItem(bestFilterMatch.Item, fullFilterText, trigger);
                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 (!MatchesFilterText(completionHelper, bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems()))
                {
                    return false;
                }

                // There was either filter text, or this was a preselect match.  In either case, we
                // can hard select this.
                return true;
            }
Beispiel #51
0
        private protected async Task <(CompletionList completionList, bool expandItemsAvailable)> GetCompletionsWithAvailabilityOfExpandedItemsAsync(
            Document document,
            int caretPosition,
            CompletionTrigger trigger,
            ImmutableHashSet <string> roles,
            OptionSet options,
            CancellationToken cancellationToken)
        {
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var defaultItemSpan = GetDefaultCompletionListSpan(text, caretPosition);

            options ??= await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);

            var providers = GetFilteredProviders(roles, trigger, options);

            var completionProviderToIndex = GetCompletionProviderToIndex(providers);

            var triggeredProviders = ImmutableArray <CompletionProvider> .Empty;

            switch (trigger.Kind)
            {
            case CompletionTriggerKind.Insertion:
            case CompletionTriggerKind.Deletion:
                if (ShouldTriggerCompletion(text, caretPosition, trigger, roles, options))
                {
                    triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(text, caretPosition, trigger, options)).ToImmutableArrayOrEmpty();
                    Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition));
                    if (triggeredProviders.Length == 0)
                    {
                        triggeredProviders = providers;
                    }
                }
                break;

            default:
                triggeredProviders = providers;
                break;
            }

            // Phase 1: Completion Providers decide if they are triggered based on textual analysis
            // Phase 2: Completion Providers use syntax to confirm they are triggered, or decide they are not actually triggered and should become an augmenting provider
            // Phase 3: Triggered Providers are asked for items
            // Phase 4: If any items were provided, all augmenting providers are asked for items
            // This allows a provider to be textually triggered but later decide to be an augmenting provider based on deeper syntactic analysis.

            var additionalAugmentingProviders = new List <CompletionProvider>();

            foreach (var provider in triggeredProviders)
            {
                if (trigger.Kind == CompletionTriggerKind.Insertion)
                {
                    if (!await provider.IsSyntacticTriggerCharacterAsync(document, caretPosition, trigger, options, cancellationToken).ConfigureAwait(false))
                    {
                        additionalAugmentingProviders.Add(provider);
                    }
                }
            }

            triggeredProviders = triggeredProviders.Except(additionalAugmentingProviders).ToImmutableArray();

            // Now, ask all the triggered providers, in parallel, to populate a completion context.
            // Note: we keep any context with items *or* with a suggested item.
            var(triggeredCompletionContexts, expandItemsAvailableFromTriggeredProviders) = await ComputeNonEmptyCompletionContextsAsync(
                document, caretPosition, trigger, options,
                defaultItemSpan, triggeredProviders,
                cancellationToken).ConfigureAwait(false);

            // If we didn't even get any back with items, then there's nothing to do.
            // i.e. if only got items back that had only suggestion items, then we don't
            // want to show any completion.
            if (!triggeredCompletionContexts.Any(cc => cc.Items.Count > 0))
            {
                return(null, expandItemsAvailableFromTriggeredProviders);
            }

            // All the contexts should be non-empty or have a suggestion item.
            Debug.Assert(triggeredCompletionContexts.All(HasAnyItems));

            // See if there were completion contexts provided that were exclusive. If so, then
            // that's all we'll return.
            var exclusiveContexts = triggeredCompletionContexts.Where(t => t.IsExclusive);

            if (exclusiveContexts.Any())
            {
                return(MergeAndPruneCompletionLists(exclusiveContexts, defaultItemSpan, isExclusive: true),
                       expandItemsAvailableFromTriggeredProviders);
            }

            // Shouldn't be any exclusive completion contexts at this point.
            Debug.Assert(triggeredCompletionContexts.All(cc => !cc.IsExclusive));

            // Great!  We had some items.  Now we want to see if any of the other providers
            // would like to augment the completion list.  For example, we might trigger
            // enum-completion on space.  If enum completion results in any items, then
            // we'll want to augment the list with all the regular symbol completion items.
            var augmentingProviders = providers.Except(triggeredProviders).ToImmutableArray();

            var(augmentingCompletionContexts, expandItemsAvailableFromAugmentingProviders) = await ComputeNonEmptyCompletionContextsAsync(
                document, caretPosition, trigger, options, defaultItemSpan,
                augmentingProviders, cancellationToken).ConfigureAwait(false);

            var allContexts = triggeredCompletionContexts.Concat(augmentingCompletionContexts);

            Debug.Assert(allContexts.Length > 0);

            // Providers are ordered, but we processed them in our own order.  Ensure that the
            // groups are properly ordered based on the original providers.
            allContexts = allContexts.Sort((p1, p2) => completionProviderToIndex[p1.Provider] - completionProviderToIndex[p2.Provider]);

            return(MergeAndPruneCompletionLists(allContexts, defaultItemSpan, isExclusive: false),
                   (expandItemsAvailableFromTriggeredProviders || expandItemsAvailableFromAugmentingProviders));
        }
        protected override async Task<CompletionItem> GetSuggestionModeItemAsync(
            Document document, int position, TextSpan itemSpan, CompletionTrigger trigger, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (trigger.Kind == CompletionTriggerKind.Insertion)
            {
                var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                var token = tree
                    .FindTokenOnLeftOfPosition(position, cancellationToken)
                    .GetPreviousTokenIfTouchingWord(position);

                if (token.Kind() == SyntaxKind.None)
                {
                    return null;
                }

                var semanticModel = await document.GetSemanticModelForNodeAsync(token.Parent, cancellationToken).ConfigureAwait(false);
                var typeInferrer = document.GetLanguageService<ITypeInferenceService>();
                if (IsLambdaExpression(semanticModel, position, token, typeInferrer, cancellationToken))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.lambda_expression, CSharpFeaturesResources.Autoselect_disabled_due_to_potential_lambda_declaration);
                }
                else if (IsAnonymousObjectCreation(token))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.member_name, CSharpFeaturesResources.Autoselect_disabled_due_to_possible_explicitly_named_anonymous_type_member_creation);
                }
                else if (token.IsPreProcessorExpressionContext())
                {
                    return CreateEmptySuggestionModeItem();
                }
                else if (IsImplicitArrayCreation(semanticModel, token, position, typeInferrer, cancellationToken))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.implicit_array_creation, CSharpFeaturesResources.Autoselect_disabled_due_to_potential_implicit_array_creation);
                }
                else if (token.IsKindOrHasMatchingText(SyntaxKind.FromKeyword) || token.IsKindOrHasMatchingText(SyntaxKind.JoinKeyword))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.range_variable, CSharpFeaturesResources.Autoselect_disabled_due_to_potential_range_variable_declaration);
                }
                else if (tree.IsNamespaceDeclarationNameContext(position, cancellationToken))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.namespace_name, CSharpFeaturesResources.Autoselect_disabled_due_to_namespace_declaration);
                }
                else if (tree.IsPartialTypeDeclarationNameContext(position, cancellationToken, out var typeDeclaration))
                {
                    switch (typeDeclaration.Keyword.Kind())
                    {
                        case SyntaxKind.ClassKeyword:
                            return CreateSuggestionModeItem(CSharpFeaturesResources.class_name, CSharpFeaturesResources.Autoselect_disabled_due_to_type_declaration);

                        case SyntaxKind.StructKeyword:
                            return CreateSuggestionModeItem(CSharpFeaturesResources.struct_name, CSharpFeaturesResources.Autoselect_disabled_due_to_type_declaration);

                        case SyntaxKind.InterfaceKeyword:
                            return CreateSuggestionModeItem(CSharpFeaturesResources.interface_name, CSharpFeaturesResources.Autoselect_disabled_due_to_type_declaration);
                    }
                }
                else if (tree.IsPossibleDeconstructionDesignation(position, cancellationToken))
                {
                    return CreateSuggestionModeItem(CSharpFeaturesResources.designation_name,
                        CSharpFeaturesResources.Autoselect_disabled_due_to_possible_deconstruction_declaration);
                }
            }

            return null;
        }
Beispiel #53
0
 private bool ShouldTriggerCompletionImpl(SourceText text, int caretPosition, CompletionTrigger trigger, in CompletionOptions options)