/// <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); } }
/// <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); } } }
/// <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)); } } }
/// <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)); } }
/// <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); }
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); }
/// <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; }
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; }
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(); }
/// <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; }
/// <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; }
/// <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; }
/// <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; }
/// <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)); }
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)); } }
protected virtual ImmutableArray <CompletionProvider> GetProviders( ImmutableHashSet <string> roles, CompletionTrigger trigger) { return(GetProviders(roles)); }
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)); }
private ImmutableArray <CompletionProvider> GetFilteredProviders( ImmutableHashSet <string> roles, CompletionTrigger trigger, OptionSet options) { return(FilterProviders(GetProviders(roles, trigger), trigger, options)); }
public static CompletionFilterReason GetFilterReason(this CompletionTrigger trigger) => trigger.Kind.GetFilterReason();
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); }
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; }
/// <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); }
/// <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; }
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); }
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)); }
/// <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; }
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; }
private bool ShouldTriggerCompletionImpl(SourceText text, int caretPosition, CompletionTrigger trigger, in CompletionOptions options)