/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/Core/Def/Diagnostics/VisualStudioVenusSpanMappingService.cs
291 строка
13 KB
Cyrus Najmabadi
Make sealed
26 мар 2025, 22:21
26 мар 2025, 22:21
d99b771
Код
Авторство
О чём код?
// 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. #nullable disable using System; using System.Composition; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; [ExportWorkspaceService(typeof(IWorkspaceVenusSpanMappingService), ServiceLayer.Default), Shared] internal sealed partial class VisualStudioVenusSpanMappingService : IWorkspaceVenusSpanMappingService { private readonly VisualStudioWorkspaceImpl _workspace; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioVenusSpanMappingService(VisualStudioWorkspaceImpl workspace) => _workspace = workspace; public void GetAdjustedDiagnosticSpan( DocumentId documentId, Location location, out TextSpan sourceSpan, out FileLinePositionSpan originalLineInfo, out FileLinePositionSpan mappedLineInfo) { sourceSpan = location.SourceSpan; originalLineInfo = location.GetLineSpan(); mappedLineInfo = location.GetMappedLineSpan(); // check quick bail out case. if (location == Location.None) { return; } // Update the original source span, if required. if (!TryAdjustSpanIfNeededForVenus(documentId, originalLineInfo, mappedLineInfo, out var originalSpan, out var mappedSpan)) { return; } if (originalSpan.Start != originalLineInfo.StartLinePosition || originalSpan.End != originalLineInfo.EndLinePosition) { originalLineInfo = new FileLinePositionSpan(originalLineInfo.Path, originalSpan.Start, originalSpan.End); var textLines = GetTextLines(documentId, location); if (textLines != null) { // adjust sourceSpan only if we could get text lines var startPos = textLines.GetPosition(originalSpan.Start); var endPos = textLines.GetPosition(originalSpan.End); sourceSpan = TextSpan.FromBounds(startPos, Math.Max(startPos, endPos)); } } if (mappedSpan.Start != mappedLineInfo.StartLinePosition || mappedSpan.End != mappedLineInfo.EndLinePosition) { mappedLineInfo = new FileLinePositionSpan(mappedLineInfo.Path, mappedSpan.Start, mappedSpan.End); } } private TextLineCollection GetTextLines(DocumentId currentDocumentId, Location location) { // normal case - all C# and VB should hit this if (location.SourceTree != null) { return location.SourceTree.GetText().Lines; } // special case for typescript and etc that don't use our compilations. var filePath = location.GetLineSpan().Path; if (filePath != null) { // as a sanity check, make sure given location is on the current document // we do the check down the stack for C# and VB using SyntaxTree in location // but for typescript and other, we don't have the tree, so adding this as // sanity check. later we could convert this to Contract to crash VS and // know about the issue. var solution = _workspace.CurrentSolution; var documentIds = solution.GetDocumentIdsWithFilePath(filePath); if (documentIds.Contains(currentDocumentId)) { // text most likely already read in return solution.GetDocument(currentDocumentId).State.GetTextSynchronously(CancellationToken.None).Lines; } } // we don't know how to get text lines for the given location return null; } private static bool TryAdjustSpanIfNeededForVenus( DocumentId documentId, FileLinePositionSpan originalLineInfo, FileLinePositionSpan mappedLineInfo, out LinePositionSpan originalSpan, out LinePositionSpan mappedSpan) { var startChanged = true; if (!TryAdjustSpanIfNeededForVenus(documentId, originalLineInfo.StartLinePosition.Line, originalLineInfo.StartLinePosition.Character, out var startLineColumn)) { startChanged = false; startLineColumn = new MappedSpan(originalLineInfo.StartLinePosition.Line, originalLineInfo.StartLinePosition.Character, mappedLineInfo.StartLinePosition.Line, mappedLineInfo.StartLinePosition.Character); } var endChanged = true; if (!TryAdjustSpanIfNeededForVenus(documentId, originalLineInfo.EndLinePosition.Line, originalLineInfo.EndLinePosition.Character, out var endLineColumn)) { endChanged = false; endLineColumn = new MappedSpan(originalLineInfo.EndLinePosition.Line, originalLineInfo.EndLinePosition.Character, mappedLineInfo.EndLinePosition.Line, mappedLineInfo.EndLinePosition.Character); } // start and end position can be swapped when mapped between primary and secondary buffer if start position is within visible span (at the edge) // but end position is outside of visible span. in that case, swap start and end position. originalSpan = GetLinePositionSpan(startLineColumn.OriginalLinePosition, endLineColumn.OriginalLinePosition); mappedSpan = GetLinePositionSpan(startLineColumn.MappedLinePosition, endLineColumn.MappedLinePosition); return startChanged || endChanged; } private static LinePositionSpan GetLinePositionSpan(LinePosition position1, LinePosition position2) { if (position1 <= position2) { return new LinePositionSpan(position1, position2); } return new LinePositionSpan(position2, position1); } public static LinePosition GetAdjustedLineColumn(DocumentId documentId, int originalLine, int originalColumn, int mappedLine, int mappedColumn) { if (TryAdjustSpanIfNeededForVenus(documentId, originalLine, originalColumn, out var span)) { return span.MappedLinePosition; } return new LinePosition(mappedLine, mappedColumn); } private static bool TryAdjustSpanIfNeededForVenus(DocumentId documentId, int originalLine, int originalColumn, out MappedSpan mappedSpan) { mappedSpan = default; if (documentId == null) { return false; } var containedDocument = ContainedDocument.TryGetContainedDocument(documentId); if (containedDocument == null) { return false; } var originalSpanOnSecondaryBuffer = new TextManager.Interop.TextSpan() { iStartLine = originalLine, iStartIndex = originalColumn, iEndLine = originalLine, iEndIndex = originalColumn }; var bufferCoordinator = containedDocument.BufferCoordinator; var containedLanguageHost = containedDocument.ContainedLanguageHost; var spansOnPrimaryBuffer = new TextManager.Interop.TextSpan[1]; if (VSConstants.S_OK == bufferCoordinator.MapSecondaryToPrimarySpan(originalSpanOnSecondaryBuffer, spansOnPrimaryBuffer)) { // easy case, we can map span in subject buffer to surface buffer. no need to adjust any span mappedSpan = new MappedSpan(originalLine, originalColumn, spansOnPrimaryBuffer[0].iStartLine, spansOnPrimaryBuffer[0].iStartIndex); return true; } // we can't directly map span in subject buffer to surface buffer. see whether there is any visible span we can use from the subject buffer span if (containedLanguageHost != null && VSConstants.S_OK != containedLanguageHost.GetNearestVisibleToken(originalSpanOnSecondaryBuffer, spansOnPrimaryBuffer)) { // no visible span we can use. return false; } // We need to map both the original and mapped location into visible code so that features such as error list, squiggle, etc. points to user visible area // We have the mapped location in the primary buffer. var nearestVisibleSpanOnPrimaryBuffer = new TextManager.Interop.TextSpan() { iStartLine = spansOnPrimaryBuffer[0].iStartLine, iStartIndex = spansOnPrimaryBuffer[0].iStartIndex, iEndLine = spansOnPrimaryBuffer[0].iStartLine, iEndIndex = spansOnPrimaryBuffer[0].iStartIndex }; // Map this location back to the secondary span to re-adjust the original location to be in user-code in secondary buffer. var spansOnSecondaryBuffer = new TextManager.Interop.TextSpan[1]; if (VSConstants.S_OK != bufferCoordinator.MapPrimaryToSecondarySpan(nearestVisibleSpanOnPrimaryBuffer, spansOnSecondaryBuffer)) { // we can't adjust original position but we can adjust mapped one mappedSpan = new MappedSpan(originalLine, originalColumn, nearestVisibleSpanOnPrimaryBuffer.iStartLine, nearestVisibleSpanOnPrimaryBuffer.iStartIndex); return true; } var nearestVisibleSpanOnSecondaryBuffer = spansOnSecondaryBuffer[0]; var originalLocationMovedAboveInFile = IsOriginalLocationMovedAboveInFile(originalLine, originalColumn, nearestVisibleSpanOnSecondaryBuffer.iStartLine, nearestVisibleSpanOnSecondaryBuffer.iStartIndex); if (!originalLocationMovedAboveInFile) { mappedSpan = new MappedSpan(nearestVisibleSpanOnSecondaryBuffer.iStartLine, nearestVisibleSpanOnSecondaryBuffer.iStartIndex, nearestVisibleSpanOnPrimaryBuffer.iStartLine, nearestVisibleSpanOnPrimaryBuffer.iStartIndex); return true; } if (TryFixUpNearestVisibleSpan(bufferCoordinator, nearestVisibleSpanOnSecondaryBuffer.iStartLine, nearestVisibleSpanOnSecondaryBuffer.iStartIndex, out var adjustedPosition)) { // span has changed yet again, re-calculate span return TryAdjustSpanIfNeededForVenus(documentId, adjustedPosition.Line, adjustedPosition.Character, out mappedSpan); } mappedSpan = new MappedSpan(nearestVisibleSpanOnSecondaryBuffer.iStartLine, nearestVisibleSpanOnSecondaryBuffer.iStartIndex, nearestVisibleSpanOnPrimaryBuffer.iStartLine, nearestVisibleSpanOnPrimaryBuffer.iStartIndex); return true; } private static bool TryFixUpNearestVisibleSpan( TextManager.Interop.IVsTextBufferCoordinator bufferCoordinator, int originalLine, int originalColumn, out LinePosition adjustedPosition) { // GetNearestVisibleToken gives us the position right at the end of visible span. // Move the position one position to the left so that squiggle can show up on last token. if (originalColumn > 1) { adjustedPosition = new LinePosition(originalLine, originalColumn - 1); return true; } if (originalLine > 1) { if (VSConstants.S_OK == bufferCoordinator.GetSecondaryBuffer(out var secondaryBuffer) && VSConstants.S_OK == secondaryBuffer.GetLengthOfLine(originalLine - 1, out var length)) { adjustedPosition = new LinePosition(originalLine - 1, length); return true; } } adjustedPosition = LinePosition.Zero; return false; } private static bool IsOriginalLocationMovedAboveInFile(int originalLine, int originalColumn, int movedLine, int movedColumn) { if (movedLine < originalLine) { return true; } if (movedLine == originalLine && movedColumn < originalColumn) { return true; } return false; } private readonly struct MappedSpan { private readonly int _originalLine; private readonly int _originalColumn; private readonly int _mappedLine; private readonly int _mappedColumn; public MappedSpan(int originalLine, int originalColumn, int mappedLine, int mappedColumn) { _originalLine = originalLine; _originalColumn = originalColumn; _mappedLine = mappedLine; _mappedColumn = mappedColumn; } public LinePosition OriginalLinePosition { get { return new LinePosition(_originalLine, _originalColumn); } } public LinePosition MappedLinePosition { get { return new LinePosition(_mappedLine, _mappedColumn); } } } }