Helpers.cs 7.5 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
4

5
using System;
6 7
using Microsoft.CodeAnalysis.Completion;
using Microsoft.VisualStudio.Text;
G
Gen Lu 已提交
8 9
using EditorAsyncCompletion = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using EditorAsyncCompletionData = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
I
Ivan Basov 已提交
10
using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem;
G
Gen Lu 已提交
11
using RoslynTrigger = Microsoft.CodeAnalysis.Completion.CompletionTrigger;
I
Ivan Basov 已提交
12
using VSCompletionItem = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem;
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion
{
    internal static class Helpers
    {
        /// <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>
G
Gen Lu 已提交
28
        internal static RoslynTrigger GetRoslynTrigger(EditorAsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation)
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
        {
            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);
            }
        }

G
Gen Lu 已提交
53
        internal static CompletionTriggerKind GetRoslynTriggerKind(EditorAsyncCompletionData.CompletionTrigger trigger)
54 55 56
        {
            switch (trigger.Reason)
            {
G
Gen Lu 已提交
57
                case EditorAsyncCompletionData.CompletionTriggerReason.InvokeAndCommitIfUnique:
58
                    return CompletionTriggerKind.InvokeAndCommitIfUnique;
G
Gen Lu 已提交
59
                case EditorAsyncCompletionData.CompletionTriggerReason.Insertion:
60
                    return CompletionTriggerKind.Insertion;
G
Gen Lu 已提交
61 62
                case EditorAsyncCompletionData.CompletionTriggerReason.Deletion:
                case EditorAsyncCompletionData.CompletionTriggerReason.Backspace:
63
                    return CompletionTriggerKind.Deletion;
G
Gen Lu 已提交
64
                case EditorAsyncCompletionData.CompletionTriggerReason.SnippetsMode:
65
                    return CompletionTriggerKind.Snippets;
66
                default:
67
                    return CompletionTriggerKind.Invoke;
68 69 70
            }
        }

G
Gen Lu 已提交
71
        internal static CompletionFilterReason GetFilterReason(EditorAsyncCompletionData.CompletionTrigger trigger)
72 73 74
        {
            switch (trigger.Reason)
            {
G
Gen Lu 已提交
75
                case EditorAsyncCompletionData.CompletionTriggerReason.Insertion:
76
                    return CompletionFilterReason.Insertion;
G
Gen Lu 已提交
77 78
                case EditorAsyncCompletionData.CompletionTriggerReason.Deletion:
                case EditorAsyncCompletionData.CompletionTriggerReason.Backspace:
79 80 81 82 83 84
                    return CompletionFilterReason.Deletion;
                default:
                    return CompletionFilterReason.Other;
            }
        }

I
Ivan Basov 已提交
85
        internal static bool IsFilterCharacter(RoslynCompletionItem item, char ch, string textTypedSoFar)
86
        {
87 88 89 90 91 92
            // Exclude standard commit character upfront because TextTypedSoFarMatchesItem can miss them on non-Windows platforms.
            if (IsStandardCommitCharacter(ch))
            {
                return false;
            }

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
            // First see if the item has any specific filter rules it wants followed.
            foreach (var rule in item.Rules.FilterCharacterRules)
            {
                switch (rule.Kind)
                {
                    case CharacterSetModificationKind.Add:
                        if (rule.Characters.Contains(ch))
                        {
                            return true;
                        }
                        continue;

                    case CharacterSetModificationKind.Remove:
                        if (rule.Characters.Contains(ch))
                        {
                            return false;
                        }
                        continue;

                    case CharacterSetModificationKind.Replace:
                        return rule.Characters.Contains(ch);
                }
            }

            // general rule: if the filtering text exactly matches the start of the item then it must be a filter character
118
            if (TextTypedSoFarMatchesItem(item, textTypedSoFar))
119 120 121 122 123 124
            {
                return true;
            }

            return false;
        }
G
Gen Lu 已提交
125

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
        internal static bool TextTypedSoFarMatchesItem(RoslynCompletionItem item, string textTypedSoFar)
        {
            if (textTypedSoFar.Length > 0)
            {
                // Note that StartsWith ignores \0 at the end of textTypedSoFar on VS Mac and Mono.
                return item.DisplayText.StartsWith(textTypedSoFar, StringComparison.CurrentCultureIgnoreCase) ||
                       item.FilterText.StartsWith(textTypedSoFar, StringComparison.CurrentCultureIgnoreCase);
            }

            return false;
        }

        // Tab, Enter and Null (call invoke commit) are always commit characters. 
        internal static bool IsStandardCommitCharacter(char c)
            => c == '\t' || c == '\n' || c == '\0';

G
Gen Lu 已提交
142
        internal static bool TryGetInitialTriggerLocation(EditorAsyncCompletion.IAsyncCompletionSession session, out SnapshotPoint initialTriggerLocation)
143
            => session.Properties.TryGetProperty(CompletionSource.TriggerLocation, out initialTriggerLocation);
G
Gen Lu 已提交
144

I
Ivan Basov 已提交
145
        // This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items.
N
nnpcYvIVl 已提交
146
        // We expect that Editor will introduce this support and we will get rid of relying on the "★" then.
147 148
        // We check both the display text and the display text prefix to account for IntelliCode item providers
        // that may be using the prefix to include the ★.
I
Ivan Basov 已提交
149
        internal static bool IsPreferredItem(this RoslynCompletionItem completionItem)
150
            => completionItem.DisplayText.StartsWith("★") || completionItem.DisplayTextPrefix.StartsWith("★");
I
Ivan Basov 已提交
151

I
Ivan Basov 已提交
152
        // This is a temporarily method to support preference of IntelliCode items comparing to non-IntelliCode items.
N
nnpcYvIVl 已提交
153
        // We expect that Editor will introduce this support and we will get rid of relying on the "★" then.
I
Ivan Basov 已提交
154 155
        internal static bool IsPreferredItem(this VSCompletionItem completionItem)
            => completionItem.DisplayText.StartsWith("★");
156 157
    }
}