/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/CSharp/StringCopyPaste/StringCopyPasteData.cs
188 строк
8 KB
Cyrus Najmabadi
Move more into extensions
02 окт 2025, 23:59
02 окт 2025, 23:59
404fb80
Код
Авторство
О чём код?
// 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.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.StringCopyPaste; /// <summary> /// Data about a string that a user has copied a subsection of. This will itself be placed on the clipboard so that /// it can be retrieved later on if the user pastes. /// </summary> [method: JsonConstructor] internal sealed class StringCopyPasteData(ImmutableArray<StringCopyPasteContent> contents) { public ImmutableArray<StringCopyPasteContent> Contents { get; } = contents; public string? ToJson() { try { return JsonSerializer.Serialize(this, typeof(StringCopyPasteData)); } catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) { } return null; } public static StringCopyPasteData? FromJson(string? json) { if (RoslynString.IsNullOrWhiteSpace(json)) return null; try { var value = JsonSerializer.Deserialize(JsonDocument.Parse(json), typeof(StringCopyPasteData)); if (value is null) return null; return (StringCopyPasteData)value; } catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) { } return null; } /// <summary> /// Given a <paramref name="stringExpression"/> for a string literal or interpolated string, and the <paramref /// name="selectionSpan"/> the user has selected in it, tries to determine the interpreted content within that /// expression that has been copied. "interpreted" in this context means the actual value of the content that /// was selected, with things like escape characters embedded as the actual characters they represent. /// </summary> public static StringCopyPasteData? TryCreate(IVirtualCharLanguageService virtualCharService, ExpressionSyntax stringExpression, TextSpan selectionSpan) => stringExpression switch { LiteralExpressionSyntax literal => TryCreateForLiteral(virtualCharService, literal, selectionSpan), InterpolatedStringExpressionSyntax interpolatedString => TryCreateForInterpolatedString(virtualCharService, interpolatedString, selectionSpan), _ => throw ExceptionUtilities.UnexpectedValue(stringExpression.Kind()), }; private static StringCopyPasteData? TryCreateForLiteral(IVirtualCharLanguageService virtualCharService, LiteralExpressionSyntax literal, TextSpan span) => TryGetContentForSpan(virtualCharService, literal.Token, span, out var content) ? new StringCopyPasteData([content]) : null; /// <summary> /// Given a string <paramref name="token"/>, and the <paramref name="selectionSpan"/> the user has selected that /// overlaps with it, tries to determine the interpreted content within that token that has been copied. /// "interpreted" in this context means the actual value of the content that was selected, with things like /// escape characters embedded as the actual characters they represent. /// </summary> private static bool TryGetNormalizedStringForSpan( IVirtualCharLanguageService virtualCharService, SyntaxToken token, TextSpan selectionSpan, [NotNullWhen(true)] out string? normalizedText) { normalizedText = null; // First, try to convert this token to a sequence of virtual chars. var virtualChars = virtualCharService.TryConvertToVirtualChars(token); if (virtualChars.IsDefaultOrEmpty()) return false; // Then find the start/end of the token's characters that overlap with the selection span. var firstOverlappingChar = virtualChars.FirstOrNull(vc => vc.Span.OverlapsWith(selectionSpan)); var lastOverlappingChar = virtualChars.LastOrNull(vc => vc.Span.OverlapsWith(selectionSpan)); if (firstOverlappingChar is null || lastOverlappingChar is null) return false; // Don't allow partial selection of an escaped character. e.g. if they select 'n' in '\n' if (selectionSpan.Start > firstOverlappingChar.Value.Span.Start) return false; if (selectionSpan.End < lastOverlappingChar.Value.Span.End) return false; var firstCharIndexInclusive = virtualChars.IndexOf(firstOverlappingChar.Value); var lastCharIndexInclusive = virtualChars.IndexOf(lastOverlappingChar.Value); // Grab that subsequence of characters and get the final interpreted string for it. var subsequence = virtualChars[firstCharIndexInclusive..(lastCharIndexInclusive + 1)]; normalizedText = subsequence.CreateString(); return true; } private static bool TryGetContentForSpan( IVirtualCharLanguageService virtualCharService, SyntaxToken token, TextSpan selectionSpan, out StringCopyPasteContent content) { if (!TryGetNormalizedStringForSpan(virtualCharService, token, selectionSpan, out var text)) { content = default; return false; } else { content = StringCopyPasteContent.ForText(text); return true; } } private static StringCopyPasteData? TryCreateForInterpolatedString( IVirtualCharLanguageService virtualCharService, InterpolatedStringExpressionSyntax interpolatedString, TextSpan selectionSpan) { using var _ = ArrayBuilder<StringCopyPasteContent>.GetInstance(out var result); foreach (var interpolatedContent in interpolatedString.Contents) { // Only consider portions of the interpolated string that overlap the selection. if (interpolatedContent.Span.OverlapsWith(selectionSpan)) { if (interpolatedContent is InterpolationSyntax interpolation) { // If the user copies a portion of an interpolation, just treat this as a non-smart copy paste // for simplicity. if (!selectionSpan.Contains(interpolation.Span)) return null; // The format clause needs to be written differently depending on what sort of interpolated // string we have (normal, verbatim, raw). So grab the token for it and determine it's actual // interpreted value so we can paste it properly at the destination side. var formatClause = (string?)null; if (interpolation.FormatClause != null && !TryGetNormalizedStringForSpan(virtualCharService, interpolation.FormatClause.FormatStringToken, selectionSpan, out formatClause)) { return null; } // Can grab the expression and alignment-clause as is. That's just normal C# code, and will // remain the same no matter what we past into. result.Add(StringCopyPasteContent.ForInterpolation( interpolation.Expression.ToFullString(), interpolation.AlignmentClause?.ToFullString(), formatClause)); } else if (interpolatedContent is InterpolatedStringTextSyntax stringText) { if (!TryGetContentForSpan(virtualCharService, stringText.TextToken, selectionSpan, out var content)) return null; result.Add(content); } } } return new StringCopyPasteData(result.ToImmutable()); } }