/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/LanguageServer/Protocol/Handler/Breakpoints/ValidateBreakableRangeHandler.cs
118 строк
6 KB
Cyrus Najmabadi
Use file scoped namespaces. (#77854)
27 мар 2025, 09:55
Не верифицирован
27 мар 2025, 09:55
98d41b8
Код
Авторство
О чём код?
// 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.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler; [ExportCSharpVisualBasicStatelessLspService(typeof(ValidateBreakableRangeHandler)), Shared] [Method(LSP.VSInternalMethods.TextDocumentValidateBreakableRangeName)] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class ValidateBreakableRangeHandler() : ILspServiceDocumentRequestHandler<VSInternalValidateBreakableRangeParams, LSP.Range?> { public bool MutatesSolutionState => false; public bool RequiresLSPSolution => true; public TextDocumentIdentifier GetTextDocumentIdentifier(LSP.VSInternalValidateBreakableRangeParams request) => request.TextDocument; public Task<LSP.Range?> HandleRequestAsync(LSP.VSInternalValidateBreakableRangeParams request, RequestContext context, CancellationToken cancellationToken) => GetBreakableRangeAsync(context.GetRequiredDocument(), request.Range, cancellationToken); public static async Task<LSP.Range?> GetBreakableRangeAsync(Document document, LSP.Range range, CancellationToken cancellationToken) { var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var span = ProtocolConversions.RangeToTextSpan(range, text); var breakpointService = document.Project.Services.GetRequiredService<IBreakpointResolutionService>(); if (span.Length > 0) { // If we have a non-empty span then it means that the debugger is asking us to adjust an // existing span. In Everett we didn't do this so we had some good and some bad // behavior. For example if you had a breakpoint on: "int i = 1;" and you changed it to "int // i = 1, j = 2;", then the breakpoint wouldn't adjust. That was bad. However, if you had the // breakpoint on an open or close curly brace then it would always "stick" to that brace // which was good. // // So we want to keep the best parts of both systems. We want to appropriately "stick" // to tokens and we also want to adjust spans intelligently. // // However, it turns out the latter is hard to do when there are parse errors in the // code. Things like missing name nodes cause a lot of havoc and make it difficult to // track a closing curly brace. // // So the way we do this is that we default to not intelligently adjusting the spans // while there are parse errors. But when there are no parse errors then the span is // adjusted. if (document.SupportsSyntaxTree) { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(tree); if (tree.GetDiagnostics(cancellationToken).Any(d => d.Severity == DiagnosticSeverity.Error)) { // Keep the span as is. return range; } } } var result = await breakpointService.ResolveBreakpointAsync(document, span, cancellationToken).ConfigureAwait(false); if (result == null) { return null; } // zero-width range means line breakpoint: var breakpointSpan = result.IsLineBreakpoint ? new TextSpan(span.Start, length: 0) : result.TextSpan; var breakpointRange = ProtocolConversions.TextSpanToRange(breakpointSpan, text); // if the breakpoint we get is smaller than what was requested, then we might be in a situation where // the breakpoint was expanded due to the user typing some code above the placement. For example: // // $$ // BP: Console.WriteLine(1); // // If the user types "int a =" we'll expand the breakpoint, as syntactically its an assigment expression, but then // when they continue to type "1;" we'll get a request for a breakpoint that spans two lines, and then the above // resolve call will shrink it to one. In that case, we prefer to stick to the end of the requested range. // // Similar exists for a single line, for example give: // // BP: int a = $$ GetData(); // // If the user types "1;" we'd shrink the breakpoint, so stick to the end of the range. if (!result.IsLineBreakpoint && BreakpointRangeIsSmaller(breakpointRange, range)) { var secondResult = await breakpointService.ResolveBreakpointAsync(document, new TextSpan(span.End, length: 0), cancellationToken).ConfigureAwait(false); if (secondResult is not null) { breakpointSpan = secondResult.IsLineBreakpoint ? new TextSpan(span.Start, length: 0) : secondResult.TextSpan; breakpointRange = ProtocolConversions.TextSpanToRange(breakpointSpan, text); } } return breakpointRange; } private static bool BreakpointRangeIsSmaller(LSP.Range breakpointRange, LSP.Range existingRange) { var breakpointLineDelta = breakpointRange.End.Line - breakpointRange.Start.Line; var existingLineDelta = existingRange.End.Line - existingRange.Start.Line; return breakpointLineDelta < existingLineDelta || (breakpointLineDelta == existingLineDelta && breakpointRange.End.Character - breakpointRange.Start.Character < existingRange.End.Character - existingRange.Start.Character); } }