diff --git a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs index ea660a2f455e60b2b6e3a83c2bd99716b74e31b0..d101f4e12daddf98eb34eaab9debf44898208cb9 100644 --- a/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs +++ b/src/EditorFeatures/Core/Extensibility/Completion/CompletionHelper.cs @@ -54,11 +54,6 @@ public IReadOnlyList GetHighlightedSpans(CompletionItem completionItem return match?.MatchedSpans; } - /// - /// If true then a [TAB] after a question mark brings up completion. - /// - public virtual bool QuestionTabInvokesSnippetCompletion => false; - /// /// 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 diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs index 256787e7bbb1a62b52404165b6d80d2abeaacd6c..7bb4adc39c4ba9c8ad05e58a54aea2553d2c1a2d 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/Completion/Controller_TabKey.cs @@ -25,13 +25,10 @@ void ICommandHandler.ExecuteCommand(TabKeyCommandArgs args, A if (sessionOpt == null) { - // The user may be trying to invoke snippets in VB - var helper = GetCompletionHelper(); + // The user may be trying to invoke snippets through question-tab var completionService = GetCompletionService(); - if (helper != null && - completionService != null && - helper.QuestionTabInvokesSnippetCompletion && + if (completionService != null && TryInvokeSnippetCompletion(args, completionService)) { // We've taken care of the tab. Don't send it to the buffer. @@ -67,60 +64,91 @@ private bool TryInvokeSnippetCompletion(TabKeyCommandArgs args, CompletionServic var text = subjectBuffer.AsTextContainer().CurrentText; - // Delete the ? and invoke completion + // If the user types "" + // then the editor takes over and shows the normal *full* snippet picker UI. + // i.e. the picker with all the folders and snippet organization. + // + // However, if the user instead has something like + // + // "" + // + // Then we take over and we show a completion list with all snippets in it + // in a flat list. This enables simple browsing and filtering of all items + // based on what the user typed so far. + // + // If we detect this pattern, then we delete the previous character (the + // question mark) and we don't send the tab through to the editor. In + // essence, the acts as the trigger, and we act as if that + // text never makes it into the buffer. Workspace workspace = null; - if (Workspace.TryGetWorkspace(subjectBuffer.AsTextContainer(), out workspace)) + if (!Workspace.TryGetWorkspace(subjectBuffer.AsTextContainer(), out workspace)) { - var documentId = workspace.GetDocumentIdInCurrentContext(subjectBuffer.AsTextContainer()); - if (documentId != null) - { - var document = workspace.CurrentSolution.GetDocument(documentId); - if (document != null) - { - var syntaxFacts = document.GetLanguageService(); - - if (caretPoint >= 2 && text[caretPoint - 1] == '?' && QuestionMarkIsPrecededByIdentifierAndWhitespace(text, caretPoint - 2, syntaxFacts)) - { - var textChange = new TextChange(TextSpan.FromBounds(caretPoint - 1, caretPoint), string.Empty); - workspace.ApplyTextChanges(documentId, textChange, CancellationToken.None); - this.StartNewModelComputation(completionService, new CompletionTrigger(CompletionTriggerKind.Snippets), filterItems: false); - return true; - } - } - } + return false; + } + + var documentId = workspace.GetDocumentIdInCurrentContext(subjectBuffer.AsTextContainer()); + if (documentId == null) + { + return false; + } + + var document = workspace.CurrentSolution.GetDocument(documentId); + if (document == null) + { + return false; + } + + var rules = GetCompletionService().GetRules(); + if (rules.SnippetsRule != SnippetsRule.IncludeAfterTypingIdentifierQuestionTab) + { + return false; } - return false; + var syntaxFactsOpt = document.GetLanguageService(); + if (syntaxFactsOpt == null || + caretPoint < 2 || + text[caretPoint - 1] != '?' || + !QuestionMarkIsPrecededByIdentifierAndWhitespace(text, caretPoint - 1, syntaxFactsOpt)) + { + return false; + } + + // Because is actually a command to bring up snippets, + // we delete the last that was typed. + var textChange = new TextChange(TextSpan.FromBounds(caretPoint - 1, caretPoint), string.Empty); + workspace.ApplyTextChanges(documentId, textChange, CancellationToken.None); + this.StartNewModelComputation(completionService, new CompletionTrigger(CompletionTriggerKind.Snippets), filterItems: false); + return true; } - private bool QuestionMarkIsPrecededByIdentifierAndWhitespace(SourceText text, int p, ISyntaxFactsService syntaxFacts) + private bool QuestionMarkIsPrecededByIdentifierAndWhitespace( + SourceText text, int questionPosition, ISyntaxFactsService syntaxFacts) { - int start = text.Lines.GetLineFromPosition(p).Start; - bool seenIdentifier = false; + var startOfLine = text.Lines.GetLineFromPosition(questionPosition).Start; - while (p >= start) + // First, skip all the whitespace. + var current = startOfLine; + while (current < questionPosition && char.IsWhiteSpace(text[current])) { - if (!(syntaxFacts.IsIdentifierStartCharacter(text[p]) || syntaxFacts.IsIdentifierPartCharacter(text[p]))) - { - break; - } - - seenIdentifier = true; - p--; + current++; } - while (p >= start) + if (current < questionPosition && syntaxFacts.IsIdentifierStartCharacter(text[current])) { - if (!char.IsWhiteSpace(text[p])) - { - break; - } + current++; + } + else + { + return false; + } - p--; + while (current < questionPosition && syntaxFacts.IsIdentifierPartCharacter(text[current])) + { + current++; } - return seenIdentifier && p <= start; + return current == questionPosition; } private void CommitOnTab(out bool committed) diff --git a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb index 595b46077d979573225832c4997a3011e494e2ef..7cce24b2c229f7649296a7708bbbdefee8bd47d4 100644 --- a/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb +++ b/src/EditorFeatures/VisualBasic/Completion/VisualBasicCompletionHelper.vb @@ -35,12 +35,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Completion MyBase.New(isCaseSensitive:=False) End Sub - Public Overrides ReadOnly Property QuestionTabInvokesSnippetCompletion As Boolean - Get - Return True - End Get - End Property - Public Overrides Function MatchesFilterText(item As CompletionItem, filterText As String, trigger As CompletionTrigger, filterReason As CompletionFilterReason, Optional recentItems As ImmutableArray(Of String) = Nothing) As Boolean ' If this Is a session started on backspace, we use a much looser prefix match check ' to see if an item matches diff --git a/src/Features/CSharp/Portable/Completion/CSharpCompletionOptions.cs b/src/Features/CSharp/Portable/Completion/CSharpCompletionOptions.cs index 07e95bf3feb339345dbdfea4b5f1939b8428edb2..fd102ea7cc3223142b99482812a21601802a0321 100644 --- a/src/Features/CSharp/Portable/Completion/CSharpCompletionOptions.cs +++ b/src/Features/CSharp/Portable/Completion/CSharpCompletionOptions.cs @@ -12,6 +12,7 @@ internal static class CSharpCompletionOptions [Obsolete("This option is superceded by CompletionOptions.EnterKeyBehavior")] public static readonly Option AddNewLineOnEnterAfterFullyTypedWord = new Option(FeatureName, "Add New Line On Enter After Fully Typed Word", defaultValue: false); + [Obsolete("This option is superceded by CompletionOptions.SnippetsBehavior")] public static readonly Option IncludeSnippets = new Option(FeatureName, "Include Code Snippets", defaultValue: true); } } diff --git a/src/Features/CSharp/Portable/Completion/CSharpCompletionOptionsProvider.cs b/src/Features/CSharp/Portable/Completion/CSharpCompletionOptionsProvider.cs index 73864cdeb617d98bd35cc967ebaf77c956e43564..ae4eae296382e06375552eef4d7018a3bbfdd2f3 100644 --- a/src/Features/CSharp/Portable/Completion/CSharpCompletionOptionsProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CSharpCompletionOptionsProvider.cs @@ -5,16 +5,14 @@ using System.Composition; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Options.Providers; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Completion { [ExportOptionProvider, Shared] internal class CSharpCompletionOptionsProvider : IOptionProvider { - private readonly IEnumerable _options = new List - { - CSharpCompletionOptions.IncludeSnippets, - }.ToImmutableArray(); + private readonly IEnumerable _options = SpecializedCollections.EmptyEnumerable(); public IEnumerable GetOptions() { diff --git a/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs b/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs index 2844ef145b3ddd712ba5a650eb5f51f5985d16e3..2aa5c3c85e4e5c3373c89e3e11f721bee197b37e 100644 --- a/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs +++ b/src/Features/CSharp/Portable/Completion/CSharpCompletionService.cs @@ -78,17 +78,24 @@ public override CompletionRules GetRules() { var options = _workspace.Options; - var rule = options.GetOption(CompletionOptions.EnterKeyBehavior, LanguageNames.CSharp); + var enterRule = options.GetOption(CompletionOptions.EnterKeyBehavior, LanguageNames.CSharp); + var snippetRule = options.GetOption(CompletionOptions.SnippetsBehavior, LanguageNames.CSharp); // Although EnterKeyBehavior is a per-language setting, the meaning of an unset setting (Default) differs between C# and VB // In C# the default means Never to maintain previous behavior - if (rule == EnterKeyRule.Default) + if (enterRule == EnterKeyRule.Default) { - rule = EnterKeyRule.Never; + enterRule = EnterKeyRule.Never; + } + + if (snippetRule == SnippetsRule.Default) + { + snippetRule = SnippetsRule.AlwaysInclude; } // use interlocked + stored rules to reduce # of times this gets created when option is different than default - var newRules = _latestRules.WithDefaultEnterKeyRule(rule); + var newRules = _latestRules.WithDefaultEnterKeyRule(enterRule) + .WithSnippetsRule(snippetRule); Interlocked.Exchange(ref _latestRules, newRules); diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs index 3c60a9c6f97e14e3afddeb6f7cbd2d4f57973949..1c67794d0d1e6163e153eae7c24e294b72165149 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SnippetCompletionProvider.cs @@ -24,10 +24,7 @@ internal sealed class SnippetCompletionProvider : CommonCompletionProvider // If null, the document's language service will be used. private readonly ISnippetInfoService _snippetInfoService; - internal override bool IsSnippetProvider - { - get { return true; } - } + internal override bool IsSnippetProvider => true; public SnippetCompletionProvider(ISnippetInfoService snippetInfoService = null) { @@ -36,11 +33,6 @@ public SnippetCompletionProvider(ISnippetInfoService snippetInfoService = null) internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options) { - if (!options.GetOption(CSharpCompletionOptions.IncludeSnippets)) - { - return false; - } - return CompletionUtilities.IsTriggerCharacter(text, characterPosition, options); } @@ -62,11 +54,6 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) return; } - if (!options.GetOption(CSharpCompletionOptions.IncludeSnippets)) - { - return; - } - var snippetCompletionItems = await document.GetUnionItemsFromDocumentAndLinkedDocumentsAsync( UnionCompletionItemComparer.Instance, (d, c) => GetSnippetsForDocumentAsync(d, position, context.DefaultItemSpan, workspace, c), diff --git a/src/Features/Core/Portable/Completion/CompletionOptions.cs b/src/Features/Core/Portable/Completion/CompletionOptions.cs index 907da69a6914a808dc9502f1b1a09e829013f272..4ce19eae6fb3376d63c840a53490e97b40ff9a0e 100644 --- a/src/Features/Core/Portable/Completion/CompletionOptions.cs +++ b/src/Features/Core/Portable/Completion/CompletionOptions.cs @@ -10,12 +10,13 @@ internal static class CompletionOptions internal const string FeatureName = "Completion"; public static readonly PerLanguageOption HideAdvancedMembers = new PerLanguageOption(FeatureName, "HideAdvancedMembers", defaultValue: false); - public static readonly PerLanguageOption IncludeKeywords = new PerLanguageOption(FeatureName, "IncludeKeywords", defaultValue: true); public static readonly PerLanguageOption TriggerOnTyping = new PerLanguageOption(FeatureName, "TriggerOnTyping", defaultValue: true); public static readonly PerLanguageOption TriggerOnTypingLetters = new PerLanguageOption(FeatureName, "TriggerOnTypingLetters", defaultValue: true); public static readonly PerLanguageOption EnterKeyBehavior = new PerLanguageOption(FeatureName, nameof(EnterKeyBehavior), defaultValue: EnterKeyRule.Default); + public static readonly PerLanguageOption SnippetsBehavior = + new PerLanguageOption(FeatureName, nameof(SnippetsBehavior), defaultValue: SnippetsRule.Default); // Dev15 options public static readonly PerLanguageOption ShowCompletionItemFilters = new PerLanguageOption(FeatureName, nameof(ShowCompletionItemFilters), defaultValue: false); diff --git a/src/Features/Core/Portable/Completion/CompletionOptionsProvider.cs b/src/Features/Core/Portable/Completion/CompletionOptionsProvider.cs index 7a084879499284c5ddcdb192e2db08e2a96cea95..5921f48f18074e48c9dbba0253ecb7712f986a23 100644 --- a/src/Features/Core/Portable/Completion/CompletionOptionsProvider.cs +++ b/src/Features/Core/Portable/Completion/CompletionOptionsProvider.cs @@ -13,12 +13,12 @@ internal class CompletionOptionsProvider : IOptionProvider { private readonly IEnumerable _options = ImmutableArray.Create( CompletionOptions.HideAdvancedMembers, - CompletionOptions.IncludeKeywords, CompletionOptions.TriggerOnTyping, CompletionOptions.TriggerOnTypingLetters, CompletionOptions.ShowCompletionItemFilters, CompletionOptions.HighlightMatchingPortionsOfCompletionListItems, - CompletionOptions.EnterKeyBehavior); + CompletionOptions.EnterKeyBehavior, + CompletionOptions.SnippetsBehavior); public IEnumerable GetOptions() => _options; } diff --git a/src/Features/Core/Portable/Completion/CompletionRules.cs b/src/Features/Core/Portable/Completion/CompletionRules.cs index 907c0b5c5ec637587bb2b024b5feb9c773211d9a..8d3b982ab8e170fb6a747e8e91e232a900b79e55 100644 --- a/src/Features/Core/Portable/Completion/CompletionRules.cs +++ b/src/Features/Core/Portable/Completion/CompletionRules.cs @@ -33,16 +33,23 @@ public sealed class CompletionRules /// public EnterKeyRule DefaultEnterKeyRule { get; } + /// + /// The rule determing how snippets work. + /// + public SnippetsRule SnippetsRule { get; } + private CompletionRules( bool dismissIfEmpty, bool dismissIfLastCharacterDeleted, ImmutableArray defaultCommitCharacters, - EnterKeyRule defaultEnterKeyRule) + EnterKeyRule defaultEnterKeyRule, + SnippetsRule snippetsRule) { this.DismissIfEmpty = dismissIfEmpty; this.DismissIfLastCharacterDeleted = dismissIfLastCharacterDeleted; this.DefaultCommitCharacters = defaultCommitCharacters.IsDefault ? ImmutableArray.Empty : defaultCommitCharacters; this.DefaultEnterKeyRule = defaultEnterKeyRule; + this.SnippetsRule = snippetsRule; } /// @@ -52,35 +59,57 @@ public sealed class CompletionRules /// True if the list should be dismissed when the user deletes the last character in the span. /// The default set of typed characters that cause the selected item to be committed. /// The default rule that determines if the enter key is passed through to the editor after the selected item has been committed. - /// + public static CompletionRules Create( + bool dismissIfEmpty, + bool dismissIfLastCharacterDeleted, + ImmutableArray defaultCommitCharacters, + EnterKeyRule defaultEnterKeyRule) + { + return Create(dismissIfEmpty, dismissIfLastCharacterDeleted, defaultCommitCharacters, + defaultEnterKeyRule, SnippetsRule.Default); + } + + /// + /// Creates a new instance. + /// + /// True if the completion list should be dismissed if the user's typing causes it to filter and display no items. + /// True if the list should be dismissed when the user deletes the last character in the span. + /// The default set of typed characters that cause the selected item to be committed. + /// The default rule that determines if the enter key is passed through to the editor after the selected item has been committed. + /// The rule that controls snippets behavior. public static CompletionRules Create( bool dismissIfEmpty = false, bool dismissIfLastCharacterDeleted = false, ImmutableArray defaultCommitCharacters = default(ImmutableArray), - EnterKeyRule defaultEnterKeyRule = EnterKeyRule.Default) + EnterKeyRule defaultEnterKeyRule = EnterKeyRule.Default, + SnippetsRule snippetsRule = SnippetsRule.Default) { return new CompletionRules( dismissIfEmpty: dismissIfEmpty, dismissIfLastCharacterDeleted: dismissIfLastCharacterDeleted, defaultCommitCharacters: defaultCommitCharacters, - defaultEnterKeyRule: defaultEnterKeyRule); + defaultEnterKeyRule: defaultEnterKeyRule, + snippetsRule: snippetsRule); } private CompletionRules With( Optional dismissIfEmpty = default(Optional), Optional dismissIfLastCharacterDeleted = default(Optional), Optional> defaultCommitCharacters = default(Optional>), - Optional defaultEnterKeyRule = default(Optional)) + Optional defaultEnterKeyRule = default(Optional), + Optional snippetsRule = default(Optional)) { var newDismissIfEmpty = dismissIfEmpty.HasValue ? dismissIfEmpty.Value : this.DismissIfEmpty; var newDismissIfLastCharacterDeleted = dismissIfLastCharacterDeleted.HasValue ? dismissIfLastCharacterDeleted.Value : this.DismissIfLastCharacterDeleted; var newDefaultCommitCharacters = defaultCommitCharacters.HasValue ? defaultCommitCharacters.Value : this.DefaultCommitCharacters; var newDefaultEnterKeyRule = defaultEnterKeyRule.HasValue ? defaultEnterKeyRule.Value : this.DefaultEnterKeyRule; + var newSnippetsRule = snippetsRule.HasValue ? snippetsRule.Value : this.SnippetsRule; - if (newDismissIfEmpty == this.DismissIfEmpty - && newDismissIfLastCharacterDeleted == this.DismissIfLastCharacterDeleted - && newDefaultCommitCharacters == this.DefaultCommitCharacters - && newDefaultEnterKeyRule == this.DefaultEnterKeyRule) + if (newDismissIfEmpty == this.DismissIfEmpty && + newDismissIfLastCharacterDeleted == this.DismissIfLastCharacterDeleted && + newDefaultCommitCharacters == this.DefaultCommitCharacters && + newDefaultEnterKeyRule == this.DefaultEnterKeyRule && + newSnippetsRule == this.SnippetsRule) { return this; } @@ -90,7 +119,8 @@ public sealed class CompletionRules newDismissIfEmpty, newDismissIfLastCharacterDeleted, newDefaultCommitCharacters, - newDefaultEnterKeyRule); + newDefaultEnterKeyRule, + newSnippetsRule); } } @@ -126,12 +156,18 @@ public CompletionRules WithDefaultEnterKeyRule(EnterKeyRule defaultEnterKeyRule) return With(defaultEnterKeyRule: defaultEnterKeyRule); } - private static readonly ImmutableArray s_defaultCommitKeys = new[] - { + /// + /// Creates a copy of the this with the property changed. + /// + public CompletionRules WithSnippetsRule(SnippetsRule snippetsRule) + { + return With(snippetsRule: snippetsRule); + } + + private static readonly ImmutableArray s_defaultCommitKeys = ImmutableArray.Create( ' ', '{', '}', '[', ']', '(', ')', '.', ',', ':', ';', '+', '-', '*', '/', '%', '&', '|', '^', '!', - '~', '=', '<', '>', '?', '@', '#', '\'', '\"', '\\' - }.ToImmutableArray(); + '~', '=', '<', '>', '?', '@', '#', '\'', '\"', '\\'); /// /// The default if none is otherwise specified. @@ -141,6 +177,7 @@ public CompletionRules WithDefaultEnterKeyRule(EnterKeyRule defaultEnterKeyRule) dismissIfEmpty: false, dismissIfLastCharacterDeleted: false, defaultCommitCharacters: s_defaultCommitKeys, - defaultEnterKeyRule: EnterKeyRule.Never); + defaultEnterKeyRule: EnterKeyRule.Never, + snippetsRule: SnippetsRule.NeverInclude); } } diff --git a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs index c4d48e527a2a360ac3ef6ae473f62d521f3ea36d..51d95fdf5f141d224341142c735444da421bdb8b 100644 --- a/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs +++ b/src/Features/Core/Portable/Completion/CompletionServiceWithProviders.cs @@ -137,16 +137,32 @@ protected ImmutableArray GetProviders(ImmutableHashSet GetProviders(ImmutableHashSet roles, CompletionTrigger trigger) + protected virtual ImmutableArray GetProviders( + ImmutableHashSet roles, CompletionTrigger trigger) { - if (trigger.Kind == CompletionTriggerKind.Snippets) + var snippetsRule = this.GetRules().SnippetsRule; + + if (snippetsRule == SnippetsRule.NeverInclude) { - return GetProviders(roles).Where(p => p.IsSnippetProvider).ToImmutableArray(); + return GetProviders(roles).Where(p => !p.IsSnippetProvider).ToImmutableArray(); } - else + else if (snippetsRule == SnippetsRule.AlwaysInclude) { return GetProviders(roles); } + else if (snippetsRule == SnippetsRule.IncludeAfterTypingIdentifierQuestionTab) + { + if (trigger.Kind == CompletionTriggerKind.Snippets) + { + return GetProviders(roles).Where(p => p.IsSnippetProvider).ToImmutableArray(); + } + else + { + return GetProviders(roles).Where(p => !p.IsSnippetProvider).ToImmutableArray(); + } + } + + return ImmutableArray.Empty; } internal protected CompletionProvider GetProvider(CompletionItem item) diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs index b79c3678dee72a4a4cc6eaeabffeacc9946a04d4..780c22c9040ad4218499c8fabbce9f78dabb26e0 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractKeywordCompletionProvider.cs @@ -47,11 +47,6 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) var options = context.Options; var cancellationToken = context.CancellationToken; - if (!options.GetOption(CompletionOptions.IncludeKeywords, document.Project.Language)) - { - return; - } - using (Logger.LogBlock(FunctionId.Completion_KeywordCompletionProvider_GetItemsWorker, cancellationToken)) { var keywords = await document.GetUnionItemsFromDocumentAndLinkedDocumentsAsync( diff --git a/src/Features/Core/Portable/Completion/SnippetsRule.cs b/src/Features/Core/Portable/Completion/SnippetsRule.cs new file mode 100644 index 0000000000000000000000000000000000000000..bb1ac17507757f25840eac653851831d52828520 --- /dev/null +++ b/src/Features/Core/Portable/Completion/SnippetsRule.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Completion +{ + public enum SnippetsRule + { + /// + /// Snippet triggering follows the default rules of the language. + /// + Default = 0, + + /// + /// Snippets are never included in the completion list + /// + NeverInclude = 1, + + /// + /// Snippets are always included in the completion list. + /// + AlwaysInclude = 2, + + /// + /// Snippets are included if the user types: id?<tab> + /// + IncludeAfterTypingIdentifierQuestionTab = 3, + } +} \ No newline at end of file diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 00264b1096df26f868be0372c3872bb49589f094..2168cc620dc94ba54bf9b3b70cf30daff27d688c 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -200,6 +200,7 @@ + diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index 236098bafda455eb7181ab22fa2b728b6e8fd773..a68f2cb16b6faf99e5bb2bd364153ea54fa644ab 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -80,10 +80,12 @@ Microsoft.CodeAnalysis.Completion.CompletionRules.DefaultCommitCharacters.get -> Microsoft.CodeAnalysis.Completion.CompletionRules.DefaultEnterKeyRule.get -> Microsoft.CodeAnalysis.Completion.EnterKeyRule Microsoft.CodeAnalysis.Completion.CompletionRules.DismissIfEmpty.get -> bool Microsoft.CodeAnalysis.Completion.CompletionRules.DismissIfLastCharacterDeleted.get -> bool +Microsoft.CodeAnalysis.Completion.CompletionRules.SnippetsRule.get -> Microsoft.CodeAnalysis.Completion.SnippetsRule Microsoft.CodeAnalysis.Completion.CompletionRules.WithDefaultCommitCharacters(System.Collections.Immutable.ImmutableArray defaultCommitCharacters) -> Microsoft.CodeAnalysis.Completion.CompletionRules Microsoft.CodeAnalysis.Completion.CompletionRules.WithDefaultEnterKeyRule(Microsoft.CodeAnalysis.Completion.EnterKeyRule defaultEnterKeyRule) -> Microsoft.CodeAnalysis.Completion.CompletionRules Microsoft.CodeAnalysis.Completion.CompletionRules.WithDismissIfEmpty(bool dismissIfEmpty) -> Microsoft.CodeAnalysis.Completion.CompletionRules Microsoft.CodeAnalysis.Completion.CompletionRules.WithDismissIfLastCharacterDeleted(bool dismissIfLastCharacterDeleted) -> Microsoft.CodeAnalysis.Completion.CompletionRules +Microsoft.CodeAnalysis.Completion.CompletionRules.WithSnippetsRule(Microsoft.CodeAnalysis.Completion.SnippetsRule snippetsRule) -> Microsoft.CodeAnalysis.Completion.CompletionRules Microsoft.CodeAnalysis.Completion.CompletionService Microsoft.CodeAnalysis.Completion.CompletionService.CompletionService() -> void Microsoft.CodeAnalysis.Completion.CompletionServiceWithProviders @@ -111,6 +113,11 @@ Microsoft.CodeAnalysis.Completion.ExportCompletionProviderAttribute.Name.get -> Microsoft.CodeAnalysis.Completion.ExportCompletionProviderAttribute.Roles.get -> string[] Microsoft.CodeAnalysis.Completion.ExportCompletionProviderAttribute.Roles.set -> void Microsoft.CodeAnalysis.Completion.MatchPriority +Microsoft.CodeAnalysis.Completion.SnippetsRule +Microsoft.CodeAnalysis.Completion.SnippetsRule.AlwaysInclude = 2 -> Microsoft.CodeAnalysis.Completion.SnippetsRule +Microsoft.CodeAnalysis.Completion.SnippetsRule.Default = 0 -> Microsoft.CodeAnalysis.Completion.SnippetsRule +Microsoft.CodeAnalysis.Completion.SnippetsRule.IncludeAfterTypingIdentifierQuestionTab = 3 -> Microsoft.CodeAnalysis.Completion.SnippetsRule +Microsoft.CodeAnalysis.Completion.SnippetsRule.NeverInclude = 1 -> Microsoft.CodeAnalysis.Completion.SnippetsRule Microsoft.CodeAnalysis.TaggedText Microsoft.CodeAnalysis.TaggedText.Tag.get -> string Microsoft.CodeAnalysis.TaggedText.TaggedText(string tag, string text) -> void @@ -198,7 +205,8 @@ static Microsoft.CodeAnalysis.Completion.CompletionItemRules.Create(System.Colle static Microsoft.CodeAnalysis.Completion.CompletionItemRules.Create(System.Collections.Immutable.ImmutableArray filterCharacterRules, System.Collections.Immutable.ImmutableArray commitCharacterRules, Microsoft.CodeAnalysis.Completion.EnterKeyRule enterKeyRule, bool formatOnCommit, int? matchPriority) -> Microsoft.CodeAnalysis.Completion.CompletionItemRules static Microsoft.CodeAnalysis.Completion.CompletionItemRules.Default -> Microsoft.CodeAnalysis.Completion.CompletionItemRules static Microsoft.CodeAnalysis.Completion.CompletionList.Create(Microsoft.CodeAnalysis.Text.TextSpan defaultSpan, System.Collections.Immutable.ImmutableArray items, Microsoft.CodeAnalysis.Completion.CompletionRules rules = null, Microsoft.CodeAnalysis.Completion.CompletionItem suggestionModeItem = null) -> Microsoft.CodeAnalysis.Completion.CompletionList -static Microsoft.CodeAnalysis.Completion.CompletionRules.Create(bool dismissIfEmpty = false, bool dismissIfLastCharacterDeleted = false, System.Collections.Immutable.ImmutableArray defaultCommitCharacters = default(System.Collections.Immutable.ImmutableArray), Microsoft.CodeAnalysis.Completion.EnterKeyRule defaultEnterKeyRule = Microsoft.CodeAnalysis.Completion.EnterKeyRule.Default) -> Microsoft.CodeAnalysis.Completion.CompletionRules +static Microsoft.CodeAnalysis.Completion.CompletionRules.Create(bool dismissIfEmpty = false, bool dismissIfLastCharacterDeleted = false, System.Collections.Immutable.ImmutableArray defaultCommitCharacters = default(System.Collections.Immutable.ImmutableArray), Microsoft.CodeAnalysis.Completion.EnterKeyRule defaultEnterKeyRule = Microsoft.CodeAnalysis.Completion.EnterKeyRule.Default, Microsoft.CodeAnalysis.Completion.SnippetsRule snippetsRule = Microsoft.CodeAnalysis.Completion.SnippetsRule.Default) -> Microsoft.CodeAnalysis.Completion.CompletionRules +static Microsoft.CodeAnalysis.Completion.CompletionRules.Create(bool dismissIfEmpty, bool dismissIfLastCharacterDeleted, System.Collections.Immutable.ImmutableArray defaultCommitCharacters, Microsoft.CodeAnalysis.Completion.EnterKeyRule defaultEnterKeyRule) -> Microsoft.CodeAnalysis.Completion.CompletionRules static Microsoft.CodeAnalysis.Completion.CompletionService.GetService(Microsoft.CodeAnalysis.Document document) -> Microsoft.CodeAnalysis.Completion.CompletionService static Microsoft.CodeAnalysis.Completion.CompletionTrigger.CreateDeletionTrigger(char deletedCharacter) -> Microsoft.CodeAnalysis.Completion.CompletionTrigger static Microsoft.CodeAnalysis.Completion.CompletionTrigger.CreateInsertionTrigger(char insertedCharacter) -> Microsoft.CodeAnalysis.Completion.CompletionTrigger diff --git a/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb b/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb index 1fa284df9493466ffd238ab4a516b03f8cc1d1c8..136e7db186b3384f38498a1503802a840ec089fc 100644 --- a/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb +++ b/src/Features/VisualBasic/Portable/Completion/VisualBasicCompletionService.vb @@ -58,23 +58,29 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion End Property Private _latestRules As CompletionRules = CompletionRules.Create( - dismissIfEmpty:=True, - dismissIfLastCharacterDeleted:=True, - defaultCommitCharacters:=CompletionRules.Default.DefaultCommitCharacters, - defaultEnterKeyRule:=EnterKeyRule.Always) + dismissIfEmpty:=True, + dismissIfLastCharacterDeleted:=True, + defaultCommitCharacters:=CompletionRules.Default.DefaultCommitCharacters, + defaultEnterKeyRule:=EnterKeyRule.Always) Public Overrides Function GetRules() As CompletionRules Dim options = _workspace.Options ' Although EnterKeyBehavior is a per-language setting, the meaning of an unset setting (Default) differs between C# And VB ' In VB the default means Always to maintain previous behavior - Dim rule = options.GetOption(CompletionOptions.EnterKeyBehavior, LanguageNames.VisualBasic) + Dim enterRule = options.GetOption(CompletionOptions.EnterKeyBehavior, LanguageNames.VisualBasic) + Dim snippetsRule = options.GetOption(CompletionOptions.SnippetsBehavior, LanguageNames.VisualBasic) - If rule = EnterKeyRule.Default Then - rule = EnterKeyRule.Always + If enterRule = EnterKeyRule.Default Then + enterRule = EnterKeyRule.Always End If - Dim newRules = _latestRules.WithDefaultEnterKeyRule(rule) + If snippetsRule = SnippetsRule.Default Then + snippetsRule = SnippetsRule.IncludeAfterTypingIdentifierQuestionTab + End If + + Dim newRules = _latestRules.WithDefaultEnterKeyRule(enterRule). + WithSnippetsRule(snippetsRule) Interlocked.Exchange(_latestRules, newRules) @@ -86,18 +92,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion Return _completionProviders End Function - Protected Overrides Function GetProviders(roles As ImmutableHashSet(Of String), trigger As CompletionTrigger) As ImmutableArray(Of CompletionProvider) - Dim _providers = MyBase.GetProviders(roles) - - If trigger.Kind = CompletionTriggerKind.Snippets Then - _providers = _providers.Where(Function(p) p.IsSnippetProvider).ToImmutableArray() - Else - _providers = _providers.Where(Function(p) Not p.IsSnippetProvider).ToImmutableArray() - End If - - Return _providers - End Function - Protected Overrides Function GetBetterItem(item As CompletionItem, existingItem As CompletionItem) As CompletionItem ' If one Is a keyword, And the other Is some other item that inserts the same text as the keyword, ' keep the keyword (VB only) diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs index e124ac3be1a9144fe0fbac45e29171524aaeae8e..50681654ab02ed5405fc1f4ad4ee47069411cb4e 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs @@ -402,6 +402,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to Always include snippets. + /// + internal static string Option_Always_include_snippets { + get { + return ResourceManager.GetString("Option_Always_include_snippets", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Show completion list after a character is typed. /// @@ -519,6 +528,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to Include snippets when ?-Tab is typed after an identifier. + /// + internal static string Option_Include_snippets_when_question_Tab_is_typed_after_an_identifier { + get { + return ResourceManager.GetString("Option_Include_snippets_when_question_Tab_is_typed_after_an_identifier", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Insert * at the start of new lines when writing /* */ comments. /// @@ -537,6 +555,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to Never include snippets. + /// + internal static string Option_Never_include_snippets { + get { + return ResourceManager.GetString("Option_Never_include_snippets", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Only add new line on enter after end of fully typed word. /// @@ -852,6 +879,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to Snippets behavior. + /// + internal static string Snippets_behavior { + get { + return ResourceManager.GetString("Snippets_behavior", resourceCulture); + } + } + /// /// Looks up a localized string similar to Insert space after cast. /// diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index 2886264b8e5167a634379a5e6bd7300d2032c69d..d3ecdecbd3cbdd4af0105cb45b15be5ac73664a4 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -468,10 +468,22 @@ _Only add new line on enter after end of fully typed word - + _Always add new line on enter - + _Never add new line on enter + + Always include snippets + + + Include snippets when ?-Tab is typed after an identifier + + + Never include snippets + + + Snippets behavior + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs index f9cbf69976dfa91c981dd9543604bc88d6680e74..1ee5377a5a7bfabf1888129efdc7e9f33f46a942 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs @@ -208,6 +208,12 @@ public int EnterKeyBehavior set { SetOption(CompletionOptions.EnterKeyBehavior, (EnterKeyRule)value); } } + public int SnippetsBehavior + { + get { return (int)GetOption(CompletionOptions.SnippetsBehavior); } + set { SetOption(CompletionOptions.SnippetsBehavior, (SnippetsRule)value); } + } + public int NewLines_AnonymousTypeInitializer_EachMember { get { return GetBooleanOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes); } @@ -318,14 +324,14 @@ public int RenameTrackingPreview public int ShowKeywords { - get { return GetBooleanOption(CompletionOptions.IncludeKeywords); } - set { SetBooleanOption(CompletionOptions.IncludeKeywords, value); } + get { return 0; } + set { } } public int ShowSnippets { - get { return GetBooleanOption(CSharpCompletionOptions.IncludeSnippets); } - set { SetBooleanOption(CSharpCompletionOptions.IncludeSnippets, value); } + get { return this.SnippetsBehavior; } + set { this.SnippetsBehavior = value; } } public int SortUsings_PlaceSystemFirst diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs index 72ed0fac23cbe43224361ba7ce9096764aa3a6db..31a04ff4964b2a8a15948dcb396ce0a26e2966f2 100644 --- a/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpSettingsManagerOptionSerializer.cs @@ -80,7 +80,6 @@ private bool ShouldIncludeOnOffOption(FieldInfo fieldInfo) result.AddRange(new[] { - new KeyValuePair(GetStorageKeyForOption(CompletionOptions.IncludeKeywords), CompletionOptions.IncludeKeywords), new KeyValuePair(GetStorageKeyForOption(CompletionOptions.TriggerOnTypingLetters), CompletionOptions.TriggerOnTypingLetters), new KeyValuePair(GetStorageKeyForOption(CompletionOptions.ShowCompletionItemFilters), CompletionOptions.ShowCompletionItemFilters), new KeyValuePair(GetStorageKeyForOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems), CompletionOptions.HighlightMatchingPortionsOfCompletionListItems), @@ -144,11 +143,11 @@ protected override bool SupportsOption(IOption option, string languageName) } else if (languageName == LanguageNames.CSharp) { - if (option == CompletionOptions.IncludeKeywords || - option == CompletionOptions.TriggerOnTypingLetters || + if (option == CompletionOptions.TriggerOnTypingLetters || option == CompletionOptions.ShowCompletionItemFilters || option == CompletionOptions.HighlightMatchingPortionsOfCompletionListItems || option == CompletionOptions.EnterKeyBehavior || + option == CompletionOptions.SnippetsBehavior || option.Feature == SimplificationOptions.PerLanguageFeatureName || option.Feature == ExtractMethodOptions.FeatureName || option.Feature == ServiceFeatureOnOffOptions.OptionName || @@ -272,6 +271,11 @@ public override bool TryFetch(OptionKey optionKey, out object value) return FetchEnterKeyBehavior(optionKey, out value); } + if (optionKey.Option == CompletionOptions.SnippetsBehavior) + { + return FetchSnippetsBehavior(optionKey, out value); + } + return base.TryFetch(optionKey, out value); } @@ -281,6 +285,45 @@ private bool FetchStyleBool(string settingName, out object value) return FetchStyleOption(typeStyleValue, out value); } + /// + /// The EnterKeyBehavior option (formerly AddNewLineOnEnterAfterFullyTypedWord) used to only exist in C# and as a boolean. + /// We need to maintain the meaning of the serialized legacy setting. + /// + private bool FetchSnippetsBehavior(OptionKey optionKey, out object value) + { + if (!base.TryFetch(optionKey, out value)) + { + return false; + } + + if (!value.Equals(SnippetsRule.Default)) + { + return true; + } + + // if the SnippetsBehavior setting cannot be loaded, then attempt to load and upgrade the legacy setting + +#pragma warning disable CS0618 // IncludeSnippets is obsolete + if (base.TryFetch(CSharpCompletionOptions.IncludeSnippets, out value)) +#pragma warning restore CS0618 + { + if ((bool)value) + { + value = SnippetsRule.AlwaysInclude; + } + else + { + value = SnippetsRule.NeverInclude; + } + + return true; + } + + value = SnippetsRule.AlwaysInclude; + return true; + } + + /// /// The EnterKeyBehavior option (formerly AddNewLineOnEnterAfterFullyTypedWord) used to only exist in C# and as a boolean. /// We need to maintain the meaning of the serialized legacy setting. diff --git a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml index fef4bfc5e01c6b4e9be9ef3baf42267bb2c32096..bf449e6dbbe969e4b5b95b2971ac85a138f1adfe 100644 --- a/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/IntelliSenseOptionPageControl.xaml @@ -17,17 +17,7 @@ - - - - + Content="{x:Static local:IntelliSenseOptionPageStrings.Option_BringUpOnIdentifier}" /> +