/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Text/Extensions.SnapshotSourceText.cs
376 строк
14 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.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Text; public static partial class Extensions { /// <summary> /// ITextSnapshot implementation of SourceText /// </summary> private class SnapshotSourceText : SourceText { /// <summary> /// The <see cref="ITextImage"/> backing the SourceText instance /// </summary> public readonly ITextImage TextImage; private readonly ITextBufferCloneService? _textBufferCloneService; private readonly Encoding? _encoding; private readonly TextBufferContainer? _container; private SnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITextSnapshot editorSnapshot, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, TextBufferContainer container) : base(checksumAlgorithm: checksumAlgorithm) { Contract.ThrowIfNull(editorSnapshot); _textBufferCloneService = textBufferCloneService; this.TextImage = RecordReverseMapAndGetImage(editorSnapshot); _encoding = encoding ?? editorSnapshot.TextBuffer.GetEncodingOrUTF8(); _container = container; } public SnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITextImage textImage, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, TextBufferContainer? container) : base(checksumAlgorithm: checksumAlgorithm) { Contract.ThrowIfNull(textImage); _textBufferCloneService = textBufferCloneService; this.TextImage = textImage; _encoding = encoding; _container = container; } /// <summary> /// A weak map of all Editor ITextSnapshots and their associated SourceText /// </summary> private static readonly ConditionalWeakTable<ITextSnapshot, SnapshotSourceText> s_textSnapshotMap = new(); /// <summary> /// Reverse map of roslyn text to editor snapshot. unlike forward map, this doesn't strongly hold onto editor snapshot so that /// we don't leak editor snapshot which should go away once editor is closed. roslyn source's lifetime is not usually tied to view. /// </summary> private static readonly ConditionalWeakTable<ITextImage, WeakReference<ITextSnapshot>> s_textImageToEditorSnapshotMap = new(); public static SourceText From(ITextBufferCloneService? textBufferCloneService, ITextSnapshot editorSnapshot) { if (editorSnapshot == null) { throw new ArgumentNullException(nameof(editorSnapshot)); } if (!s_textSnapshotMap.TryGetValue(editorSnapshot, out var snapshot)) { // Explicitly obtain the TextBufferContainer before calling GetValue to avoid reentrancy in // ConditionalWeakTable. https://github.com/dotnet/roslyn/issues/28256 var container = TextBufferContainer.From(editorSnapshot.TextBuffer); // Avoid capturing `textBufferCloneServiceOpt` on the fast path var tempTextBufferCloneService = textBufferCloneService; snapshot = s_textSnapshotMap.GetValue(editorSnapshot, s => new SnapshotSourceText(tempTextBufferCloneService, s, encoding: null, SourceHashAlgorithms.OpenDocumentChecksumAlgorithm, container)); } return snapshot; } /// <summary> /// This only exist to break circular dependency on creating buffer. nobody except extension itself should use it /// </summary> internal static SourceText From(ITextBufferCloneService? textBufferCloneService, ITextSnapshot editorSnapshot, TextBufferContainer container) { if (editorSnapshot == null) { throw new ArgumentNullException(nameof(editorSnapshot)); } Contract.ThrowIfFalse(editorSnapshot.TextBuffer == container.GetTextBuffer()); return s_textSnapshotMap.GetValue(editorSnapshot, s => new SnapshotSourceText(textBufferCloneService, s, encoding: null, SourceHashAlgorithms.OpenDocumentChecksumAlgorithm, container)); } public override Encoding? Encoding { get { return _encoding; } } public ITextSnapshot? TryFindEditorSnapshot() => TryFindEditorSnapshot(this.TextImage); public override SourceTextContainer Container { get { return _container ?? base.Container; } } public override int Length { get { var res = this.TextImage.Length; return res; } } public override char this[int position] { get { return this.TextImage[position]; } } #region Lines protected override TextLineCollection GetLinesCore() => new LineInfo(this); private sealed class LineInfo : TextLineCollection { private readonly SnapshotSourceText _text; public LineInfo(SnapshotSourceText text) => _text = text; public override int Count { get { return _text.TextImage.LineCount; } } public override TextLine this[int index] { get { var line = _text.TextImage.GetLineFromLineNumber(index); return TextLine.FromSpan(_text, TextSpan.FromBounds(line.Start, line.End)); } } public override int IndexOf(int position) => _text.TextImage.GetLineNumberFromPosition(position); public override TextLine GetLineFromPosition(int position) => this[this.IndexOf(position)]; public override LinePosition GetLinePosition(int position) { var textLine = _text.TextImage.GetLineFromPosition(position); return new LinePosition(textLine.LineNumber, position - textLine.Start); } } #endregion public override string ToString() => this.TextImage.GetText(); public override string ToString(TextSpan textSpan) { var editorSpan = new Span(textSpan.Start, textSpan.Length); var res = this.TextImage.GetText(editorSpan); return res; } public override SourceText WithChanges(IEnumerable<TextChange> changes) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } if (!changes.Any()) { return this; } // check whether we can use text buffer factory var factory = _textBufferCloneService; if (factory == null) { // if we can't get the factory, use the default implementation return base.WithChanges(changes); } // otherwise, create a new cloned snapshot var buffer = factory.CloneWithUnknownContentType(TextImage); var baseSnapshot = buffer.CurrentSnapshot; // apply the change to the buffer using (var edit = buffer.CreateEdit()) { foreach (var change in changes) { edit.Replace(change.Span.ToSpan(), change.NewText); } edit.Apply(); } return new ChangedSourceText( textBufferCloneService: _textBufferCloneService, baseText: this, baseSnapshot: baseSnapshot, currentSnapshot: buffer.CurrentSnapshot); } private static ITextImage RecordReverseMapAndGetImage(ITextSnapshot editorSnapshot) { Contract.ThrowIfNull(editorSnapshot); var textImage = ((ITextSnapshot2)editorSnapshot).TextImage; Contract.ThrowIfNull(textImage); // If we're already in the map, there's nothing to update. Do a quick check // to avoid two allocations per call to RecordTextSnapshotAndGetImage. if (!s_textImageToEditorSnapshotMap.TryGetValue(textImage, out var weakReference)) { // put reverse entry that won't hold onto anything weakReference = s_textImageToEditorSnapshotMap.GetValue( textImage, _ => new WeakReference<ITextSnapshot>(editorSnapshot)); } #if DEBUG // forward and reversed map is 1:1 map. snapshot can't be different var snapshot = weakReference.GetTarget(); Contract.ThrowIfFalse(snapshot == editorSnapshot); #endif return textImage; } private static ITextSnapshot? TryFindEditorSnapshot(ITextImage textImage) { if (!s_textImageToEditorSnapshotMap.TryGetValue(textImage, out var weakReference) || !weakReference.TryGetTarget(out var editorSnapshot)) { return null; } return editorSnapshot; } /// <summary> /// Use a separate class for closed files to simplify memory leak investigations /// </summary> internal sealed class ClosedSnapshotSourceText : SnapshotSourceText { public ClosedSnapshotSourceText(ITextBufferCloneService? textBufferCloneService, ITextImage textImage, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm) : base(textBufferCloneService, textImage, encoding, checksumAlgorithm, container: null) { } } /// <summary> /// Perf: Optimize calls to GetChangeRanges after WithChanges by using editor snapshots /// </summary> private sealed class ChangedSourceText : SnapshotSourceText { private readonly SnapshotSourceText _baseText; private readonly ITextImage _baseTextImage; public ChangedSourceText(ITextBufferCloneService? textBufferCloneService, SnapshotSourceText baseText, ITextSnapshot baseSnapshot, ITextSnapshot currentSnapshot) : base(textBufferCloneService, currentSnapshot, baseText.Encoding, baseText.ChecksumAlgorithm, container: TextBufferContainer.From(currentSnapshot.TextBuffer)) { _baseText = baseText; _baseTextImage = ((ITextSnapshot2)baseSnapshot).TextImage; } public override IReadOnlyList<TextChangeRange> GetChangeRanges(SourceText oldText) { if (oldText == null) { throw new ArgumentNullException(nameof(oldText)); } // if they are the same text there is no change. if (oldText == this) { return TextChangeRange.NoChanges; } if (oldText != _baseText) { return [new TextChangeRange(new TextSpan(0, oldText.Length), this.Length)]; } return GetChangeRanges(_baseTextImage, _baseTextImage.Length, this.TextImage); } } public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) => this.TextImage.CopyTo(sourceIndex, destination, destinationIndex, count); public override void Write(TextWriter textWriter, TextSpan span, CancellationToken cancellationToken) => this.TextImage.Write(textWriter, span.ToSpan()); #region GetChangeRangesImplementation public override IReadOnlyList<TextChangeRange> GetChangeRanges(SourceText oldText) { if (oldText == null) { throw new ArgumentNullException(nameof(oldText)); } // if they are the same text there is no change. if (oldText == this) { return TextChangeRange.NoChanges; } // first, check whether the text buffer is still alive. if (this.Container is TextBufferContainer container) { var lastEventArgs = container.LastEventArgs; if (lastEventArgs != null && lastEventArgs.OldText == oldText && lastEventArgs.NewText == this) { return lastEventArgs.Changes; } } var oldSnapshot = oldText.TryFindCorrespondingEditorTextImage(); var newSnapshot = this.TryFindCorrespondingEditorTextImage(); return GetChangeRanges(oldSnapshot, oldText.Length, newSnapshot); } private IReadOnlyList<TextChangeRange> GetChangeRanges(ITextImage? oldImage, int oldTextLength, ITextImage? newImage) { if (oldImage == null || newImage == null || oldImage.Version.Identifier != newImage.Version.Identifier) { // Claim its all changed Logger.Log(FunctionId.Workspace_SourceText_GetChangeRanges, "Invalid Snapshots"); return [new TextChangeRange(new TextSpan(0, oldTextLength), this.Length)]; } else if (AreSameReiteratedVersion(oldImage, newImage)) { // content of two snapshot must be same even if versions are different return TextChangeRange.NoChanges; } else { return ITextImageHelpers.GetChangeRanges(oldImage, newImage); } } private static bool AreSameReiteratedVersion(ITextImage oldImage, ITextImage newImage) { var oldSnapshot = TryFindEditorSnapshot(oldImage); var newSnapshot = TryFindEditorSnapshot(newImage); return oldSnapshot != null && newSnapshot != null && oldSnapshot.Version.ReiteratedVersionNumber == newSnapshot.Version.ReiteratedVersionNumber; } #endregion } }