| File: EmbeddedLanguages\RegularExpressions\LanguageServices\RegexDocumentHighlighter.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.DocumentHighlighting; 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; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Features.EmbeddedLanguages.RegularExpressions.LanguageServices; using RegexToken = EmbeddedSyntaxToken<RegexKind>; [ExportEmbeddedLanguageDocumentHighlighter( PredefinedEmbeddedLanguageNames.Regex, [LanguageNames.CSharp, LanguageNames.VisualBasic], supportsUnannotatedAPIs: true, "Regex", "Regexp"), Shared] internal sealed class RegexDocumentHighlighter : IEmbeddedLanguageDocumentHighlighter { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RegexDocumentHighlighter() { } public ImmutableArray<DocumentHighlights> GetDocumentHighlights( Document document, SemanticModel semanticModel, SyntaxToken token, int position, HighlightingOptions options, CancellationToken cancellationToken) { if (!options.HighlightRelatedRegexComponentsUnderCursor) return default; var info = document.GetRequiredLanguageService<IEmbeddedLanguagesProvider>().EmbeddedLanguageInfo; var detector = RegexLanguageDetector.GetOrCreate(semanticModel.Compilation, info); var tree = detector.TryParseString(token, semanticModel, cancellationToken); return tree == null ? default : [new DocumentHighlights(document, GetHighlights(tree, position))]; } private static ImmutableArray<HighlightSpan> GetHighlights(RegexTree tree, int positionInDocument) { var referencesOnTheRight = GetReferences(tree, positionInDocument); if (!referencesOnTheRight.IsEmpty) { return referencesOnTheRight; } if (positionInDocument == 0) { return default; } // Nothing was on the right of the caret. Return anything we were able to find on // the left of the caret. var referencesOnTheLeft = GetReferences(tree, positionInDocument - 1); return referencesOnTheLeft; } private static ImmutableArray<HighlightSpan> GetReferences(RegexTree tree, int position) { var virtualChar = tree.Text.Find(position); if (virtualChar == null) return []; var ch = virtualChar.Value; return FindReferenceHighlights(tree, ch); } private static ImmutableArray<HighlightSpan> FindReferenceHighlights(RegexTree tree, VirtualChar ch) { var node = FindReferenceNode(tree.Root, ch); if (node == null) { return []; } var captureToken = GetCaptureToken(node); if (captureToken.Kind == RegexKind.NumberToken) { var val = (int)captureToken.Value; if (tree.CaptureNumbersToSpan.TryGetValue(val, out var captureSpan)) { return CreateHighlights(node, captureSpan); } } else { var val = (string)captureToken.Value; if (tree.CaptureNamesToSpan.TryGetValue(val, out var captureSpan)) { return CreateHighlights(node, captureSpan); } } return []; } private static ImmutableArray<HighlightSpan> CreateHighlights( RegexEscapeNode node, TextSpan captureSpan) { return [CreateHighlightSpan(node.GetSpan()), CreateHighlightSpan(captureSpan)]; } private static HighlightSpan CreateHighlightSpan(TextSpan textSpan) => new(textSpan, HighlightSpanKind.None); private static RegexToken GetCaptureToken(RegexEscapeNode node) => node switch { RegexBackreferenceEscapeNode backReference => backReference.NumberToken, RegexCaptureEscapeNode captureEscape => captureEscape.CaptureToken, RegexKCaptureEscapeNode kCaptureEscape => kCaptureEscape.CaptureToken, _ => throw new InvalidOperationException(), }; private static RegexEscapeNode? FindReferenceNode(RegexNode node, VirtualChar virtualChar) { if (node.Kind is RegexKind.BackreferenceEscape or RegexKind.CaptureEscape or RegexKind.KCaptureEscape) { if (node.Contains(virtualChar)) { return (RegexEscapeNode)node; } } foreach (var child in node) { if (child.IsNode) { var result = FindReferenceNode(child.Node, virtualChar); if (result != null) { return result; } } } return null; } }