提交 2212d015 编写于 作者: C CyrusNajmabadi

Better codify brace matching invariants.

上级 7cf515cf
......@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
......@@ -61,25 +62,26 @@ internal async Task ProduceTagsAsync(TaggerContext<BraceHighlightTag> context, D
{
using (Logger.LogBlock(FunctionId.Tagger_BraceHighlighting_TagProducer_ProduceTags, context.CancellationToken))
{
await ProduceTagsForBracesAsync(context, document, snapshot, position, rightBrace: false).ConfigureAwait(false);
await ProduceTagsForBracesAsync(context, document, snapshot, position - 1, rightBrace: true).ConfigureAwait(false);
if (position >= 0 && position <= snapshot.Length)
{
var (bracesLeftOfPosition, bracesRightOfPosition) = await _braceMatcherService.GetAllMatchingBracesAsync(
document, position, context.CancellationToken).ConfigureAwait(false);
AddBraces(context, snapshot, bracesLeftOfPosition);
AddBraces(context, snapshot, bracesRightOfPosition);
}
}
}
private async Task ProduceTagsForBracesAsync(TaggerContext<BraceHighlightTag> context, Document document, ITextSnapshot snapshot, int position, bool rightBrace)
private void AddBraces(
TaggerContext<BraceHighlightTag> context,
ITextSnapshot snapshot,
BraceMatchingResult? braces)
{
if (position >= 0 && position <= snapshot.Length)
if (braces.HasValue)
{
var braces = await _braceMatcherService.GetMatchingBracesAsync(document, position, context.CancellationToken).ConfigureAwait(false);
if (braces.HasValue)
{
if ((!rightBrace && braces.Value.LeftSpan.Start == position) ||
(rightBrace && braces.Value.RightSpan.Start == position))
{
context.AddTag(snapshot.GetTagSpan(braces.Value.LeftSpan.ToSpan(), BraceHighlightTag.StartTag));
context.AddTag(snapshot.GetTagSpan(braces.Value.RightSpan.ToSpan(), BraceHighlightTag.EndTag));
}
}
context.AddTag(snapshot.GetTagSpan(braces.Value.LeftSpan.ToSpan(), BraceHighlightTag.StartTag));
context.AddTag(snapshot.GetTagSpan(braces.Value.RightSpan.ToSpan(), BraceHighlightTag.EndTag));
}
}
}
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
......@@ -13,14 +14,13 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.BraceMatching
[Export(typeof(IBraceMatchingService))]
internal class BraceMatchingService : IBraceMatchingService
{
private readonly List<Lazy<IBraceMatcher, LanguageMetadata>> _braceMatchers;
private readonly ImmutableArray<Lazy<IBraceMatcher, LanguageMetadata>> _braceMatchers;
[ImportingConstructor]
public BraceMatchingService(
[ImportMany] IEnumerable<Lazy<IBraceMatcher, LanguageMetadata>> braceMatchers)
{
////braceMatchers.RealizeImports();
_braceMatchers = braceMatchers.ToList();
_braceMatchers = braceMatchers.ToImmutableArray();
}
public async Task<BraceMatchingResult?> GetMatchingBracesAsync(Document document, int position, CancellationToken cancellationToken)
......
......@@ -8,50 +8,92 @@ namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions
{
internal static class IBraceMatchingServiceExtensions
{
public static async Task<TextSpan?> FindMatchingSpanAsync(
/// <summary>
/// Given code like ()^() (where ^ is the caret position), returns the two pairs of
/// matching braces on the left and the right of the position. Note: a brace matching
/// pair is only returned if the position is on the left-side of hte start brace, or the
/// right side of end brace. So, for example, if you have (^()), then only the inner
/// braces are returned as the position is not on the right-side of the outer braces.
///
/// This function also works for multi-character braces i.e. ([ ]) In this case,
/// the rule is that the position has to be on the left side of the start brace, or
/// inside the start brace (but not at the end). So, ^([ ]) will return this
/// as a brace match, as will (^[ ]). But ([^ ]) will not.
///
/// The same goes for the braces on the the left of the caret. i.e.: ([ ])^
/// will return the braces on the left, as will ([ ]^). But ([ ^]) will not.
/// </summary>
public static async Task<(BraceMatchingResult? leftOfPosition, BraceMatchingResult? rightOfPosition)> GetAllMatchingBracesAsync(
this IBraceMatchingService service,
Document document,
int position,
CancellationToken cancellationToken)
{
// These are the matching spans when checking the token to the right of the position.
var braces1 = await service.GetMatchingBracesAsync(document, position, cancellationToken).ConfigureAwait(false);
// These are the matching spans when checking the token to the left of the position.
BraceMatchingResult? braces2 = null;
var rightOfPosition = await service.GetMatchingBracesAsync(document, position, cancellationToken).ConfigureAwait(false);
// Ensure caret is valid at left of position.
if (position > 0)
{
braces2 = await service.GetMatchingBracesAsync(document, position - 1, cancellationToken).ConfigureAwait(false);
}
// Favor matches where the position is on the outside boundary of the braces. i.e. if we
// have: {^()}
// The braces to the right of the position should only be added if the position is
// actually within the span of the start brace. Note that this is what we want for
// single character braces as well as multi char braces. i.e. if the user has:
//
// then this would return the () not the {}
if (braces1.HasValue && position >= braces1.Value.LeftSpan.Start && position < braces1.Value.LeftSpan.End)
{
// ^{ } -- return right span
return braces1.Value.RightSpan;
}
else if (braces2.HasValue && position > braces2.Value.RightSpan.Start && position <= braces2.Value.RightSpan.End)
// ^{ } // then { and } are matching braces.
// {^ } // then { and } are not matching braces.
//
// ^<@ @> // then <@ and @> are matching braces.
// <^@ @> // then <@ and @> are matching braces.
// <@^ @> // then <@ and @> are not matching braces.
if (rightOfPosition.HasValue &&
!rightOfPosition.Value.LeftSpan.Contains(position))
{
// { }^ -- return left span
return braces2.Value.LeftSpan;
// Not a valid match.
rightOfPosition = null;
}
else if (braces2.HasValue && position > braces2.Value.LeftSpan.Start && position <= braces2.Value.LeftSpan.End)
if (position == 0)
{
// {^ } -- return right span
return braces2.Value.RightSpan;
// We're at the start of the document, can't find braces to the left of the position.
return (leftOfPosition: null, rightOfPosition);
}
else if (braces1.HasValue && position >= braces1.Value.RightSpan.Start && position < braces1.Value.RightSpan.End)
// See if we're touching the end of some construct. i.e.:
//
// { }^
// <@ @>^
// <@ @^>
//
// But not
//
// { ^}
// <@ ^@>
var leftOfPosition = await service.GetMatchingBracesAsync(document, position - 1, cancellationToken).ConfigureAwait(false);
if (leftOfPosition.HasValue &&
position <= leftOfPosition.Value.RightSpan.End &&
position > leftOfPosition.Value.RightSpan.Start)
{
// { ^} -- return left span
return braces1.Value.LeftSpan;
// Found a valid pair on the left of us.
return (leftOfPosition, rightOfPosition);
}
// No valid pair of braces on the left of us.
return (leftOfPosition: null, rightOfPosition);
}
public static async Task<TextSpan?> FindMatchingSpanAsync(
this IBraceMatchingService service,
Document document,
int position,
CancellationToken cancellationToken)
{
var (bracesLeftOfPosition, bracesRightOfPosition) = await service.GetAllMatchingBracesAsync(
document, position, cancellationToken).ConfigureAwait(false);
return null;
// Favor matches where the position is on the outside boundary of the braces. i.e. if we
// have: {}^()
//
// then this would return the () not the {}
return bracesRightOfPosition?.RightSpan ?? bracesLeftOfPosition?.LeftSpan;
}
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册