| File: EmbeddedLanguages\RegularExpressions\LanguageServices\RegexBraceMatcher.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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. using System; using System.Collections.Immutable; using System.Composition; using System.Threading; using Microsoft.CodeAnalysis.BraceMatching; using Microsoft.CodeAnalysis.EmbeddedLanguages; using Microsoft.CodeAnalysis.EmbeddedLanguages.Common; using Microsoft.CodeAnalysis.EmbeddedLanguages.RegularExpressions; using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Features.EmbeddedLanguages.RegularExpressions.LanguageServices; using RegexToken = EmbeddedSyntaxToken<RegexKind>; using RegexTrivia = EmbeddedSyntaxTrivia<RegexKind>; /// <summary> /// Brace matching impl for embedded regex strings. /// </summary> [ExportEmbeddedLanguageBraceMatcher( PredefinedEmbeddedLanguageNames.Regex, [LanguageNames.CSharp, LanguageNames.VisualBasic], supportsUnannotatedAPIs: true, "Regex", "Regexp"), Shared] internal sealed class RegexBraceMatcher : IEmbeddedLanguageBraceMatcher { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RegexBraceMatcher() { } public BraceMatchingResult? FindBraces( Project project, SemanticModel semanticModel, SyntaxToken token, int position, BraceMatchingOptions options, CancellationToken cancellationToken) { if (!options.HighlightingOptions.HighlightRelatedRegexComponentsUnderCursor) return null; var info = project.GetRequiredLanguageService<IEmbeddedLanguagesProvider>().EmbeddedLanguageInfo; var detector = RegexLanguageDetector.GetOrCreate(semanticModel.Compilation, info); var tree = detector.TryParseString(token, semanticModel, cancellationToken); return tree == null ? null : GetMatchingBraces(tree, position); } private static BraceMatchingResult? GetMatchingBraces(RegexTree tree, int position) { var virtualChar = tree.Text.Find(position); if (virtualChar == null) return null; var ch = virtualChar.Value; return ch.Value switch { '(' or ')' => FindGroupingBraces(tree, ch) ?? FindCommentBraces(tree, ch), '[' or ']' => FindCharacterClassBraces(tree, ch), _ => null, }; } private static BraceMatchingResult? CreateResult(RegexToken open, RegexToken close) => open.IsMissing || close.IsMissing ? null : new BraceMatchingResult(open.VirtualChars[0].Span, close.VirtualChars[0].Span); private static BraceMatchingResult? FindCommentBraces(RegexTree tree, VirtualChar ch) { var trivia = FindTrivia(tree.Root, ch); if (trivia?.Kind != RegexKind.CommentTrivia) return null; var firstChar = trivia.Value.VirtualChars[0]; var lastChar = trivia.Value.VirtualChars[^1]; return firstChar != '(' || lastChar != ')' ? null : new BraceMatchingResult(firstChar.Span, lastChar.Span); } private static BraceMatchingResult? FindGroupingBraces(RegexTree tree, VirtualChar ch) { var node = FindGroupingNode(tree.Root, ch); return node == null ? null : CreateResult(node.OpenParenToken, node.CloseParenToken); } private static BraceMatchingResult? FindCharacterClassBraces(RegexTree tree, VirtualChar ch) { var node = FindCharacterClassNode(tree.Root, ch); return node == null ? null : CreateResult(node.OpenBracketToken, node.CloseBracketToken); } private static RegexGroupingNode? FindGroupingNode(RegexNode node, VirtualChar ch) => FindNode<RegexGroupingNode>(node, ch, (grouping, c) => grouping.OpenParenToken.VirtualChars.Contains(c) || grouping.CloseParenToken.VirtualChars.Contains(c)); private static RegexBaseCharacterClassNode? FindCharacterClassNode(RegexNode node, VirtualChar ch) => FindNode<RegexBaseCharacterClassNode>(node, ch, (grouping, c) => grouping.OpenBracketToken.VirtualChars.Contains(c) || grouping.CloseBracketToken.VirtualChars.Contains(c)); private static TNode? FindNode<TNode>(RegexNode node, VirtualChar ch, Func<TNode, VirtualChar, bool> predicate) where TNode : RegexNode { if (node is TNode nodeMatch && predicate(nodeMatch, ch)) return nodeMatch; foreach (var child in node) { if (child.IsNode) { var result = FindNode(child.Node, ch, predicate); if (result != null) return result; } } return null; } private static RegexTrivia? FindTrivia(RegexNode node, VirtualChar ch) { foreach (var child in node) { if (child.IsNode) { var result = FindTrivia(child.Node, ch); if (result != null) return result; } else { var token = child.Token; var trivia = TryGetTrivia(token.LeadingTrivia, ch) ?? TryGetTrivia(token.TrailingTrivia, ch); if (trivia != null) return trivia; } } return null; } private static RegexTrivia? TryGetTrivia(ImmutableArray<RegexTrivia> triviaList, VirtualChar ch) { foreach (var trivia in triviaList) { if (trivia.VirtualChars.Contains(ch)) return trivia; } return null; } }