| File: EmbeddedLanguages\Json\LanguageServices\JsonBraceMatcher.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.Composition; using System.Threading; using Microsoft.CodeAnalysis.BraceMatching; using Microsoft.CodeAnalysis.EmbeddedLanguages; using Microsoft.CodeAnalysis.EmbeddedLanguages.Common; using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Features.EmbeddedLanguages.Json.LanguageServices; using JsonToken = EmbeddedSyntaxToken<JsonKind>; /// <summary> /// Brace matcher impl for embedded json strings. /// </summary> [ExportEmbeddedLanguageBraceMatcher( PredefinedEmbeddedLanguageNames.Json, [LanguageNames.CSharp, LanguageNames.VisualBasic], supportsUnannotatedAPIs: true, "Json"), Shared] internal sealed class JsonBraceMatcher : IEmbeddedLanguageBraceMatcher { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public JsonBraceMatcher() { } public BraceMatchingResult? FindBraces( Project project, SemanticModel semanticModel, SyntaxToken token, int position, BraceMatchingOptions options, CancellationToken cancellationToken) { if (!options.HighlightingOptions.HighlightRelatedJsonComponentsUnderCursor) return null; var info = project.GetRequiredLanguageService<IEmbeddedLanguagesProvider>().EmbeddedLanguageInfo; var detector = JsonLanguageDetector.GetOrCreate(semanticModel.Compilation, info); // We do support brace matching in strings that look very likely to be json, even if we aren't 100% certain // if it truly is json. var tree = detector.TryParseString(token, semanticModel, includeProbableStrings: true, cancellationToken); if (tree == null) return null; return GetMatchingBraces(tree, position); } private static BraceMatchingResult? GetMatchingBraces(JsonTree tree, int position) { var virtualChar = tree.Text.Find(position); if (virtualChar == null) return null; var ch = virtualChar.Value; return ch.Value is '{' or '[' or '(' or '}' or ']' or ')' ? FindBraceHighlights(tree, ch) : null; } private static BraceMatchingResult? FindBraceHighlights(JsonTree tree, VirtualChar ch) => FindBraceMatchingResult(tree.Root, ch); private static BraceMatchingResult? FindBraceMatchingResult(JsonNode node, VirtualChar ch) { var fullSpan = node.GetFullSpan(); if (fullSpan == null) return null; if (!fullSpan.Value.Contains(ch.Span.Start)) return null; switch (node) { case JsonArrayNode array when Matches(array.OpenBracketToken, array.CloseBracketToken, ch): return Create(array.OpenBracketToken, array.CloseBracketToken); case JsonObjectNode obj when Matches(obj.OpenBraceToken, obj.CloseBraceToken, ch): return Create(obj.OpenBraceToken, obj.CloseBraceToken); case JsonConstructorNode cons when Matches(cons.OpenParenToken, cons.CloseParenToken, ch): return Create(cons.OpenParenToken, cons.CloseParenToken); } foreach (var child in node) { if (child.IsNode) { var result = FindBraceMatchingResult(child.Node, ch); if (result != null) return result; } } return null; } private static BraceMatchingResult? Create(JsonToken open, JsonToken close) => open.IsMissing || close.IsMissing ? null : new BraceMatchingResult(open.GetSpan(), close.GetSpan()); private static bool Matches(JsonToken openToken, JsonToken closeToken, VirtualChar ch) => openToken.VirtualChars.Contains(ch) || closeToken.VirtualChars.Contains(ch); }