File: Workspace\Solution\TextDocumentState.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    internal partial class TextDocumentState
    {
        protected readonly SolutionServices solutionServices;
 
        internal ITextAndVersionSource TextAndVersionSource { get; }
        public readonly LoadTextOptions LoadTextOptions;
 
        // Checksums for this solution state
        private readonly ValueSource<DocumentStateChecksums> _lazyChecksums;
 
        public DocumentInfo.DocumentAttributes Attributes { get; }
 
        /// <summary>
        /// A <see cref="IDocumentServiceProvider"/> associated with this document
        /// </summary>
        public IDocumentServiceProvider Services { get; }
 
        protected TextDocumentState(
            SolutionServices solutionServices,
            IDocumentServiceProvider? documentServiceProvider,
            DocumentInfo.DocumentAttributes attributes,
            ITextAndVersionSource textAndVersionSource,
            LoadTextOptions loadTextOptions)
        {
            this.solutionServices = solutionServices;
 
            this.LoadTextOptions = loadTextOptions;
            TextAndVersionSource = textAndVersionSource;
 
            Attributes = attributes;
            Services = documentServiceProvider ?? DefaultTextDocumentServiceProvider.Instance;
 
            // This constructor is called whenever we're creating a new TextDocumentState from another
            // TextDocumentState, and so we populate all the fields from the inputs. We will always create
            // a new AsyncLazy to compute the checksum though, and that's because there's no practical way for
            // the newly created TextDocumentState to have the same checksum as a previous TextDocumentState:
            // if we're creating a new state, it's because something changed, and we'll have to create a new checksum.
            _lazyChecksums = new AsyncLazy<DocumentStateChecksums>(ComputeChecksumsAsync, cacheResult: true);
        }
 
        public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, LoadTextOptions loadTextOptions)
            : this(solutionServices,
                   info.DocumentServiceProvider,
                   info.Attributes,
                   textAndVersionSource: info.TextLoader != null
                    ? CreateRecoverableText(info.TextLoader, solutionServices)
                    : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, encoding: null, loadTextOptions.ChecksumAlgorithm), VersionStamp.Default, info.FilePath)),
                   loadTextOptions)
        {
        }
 
        public DocumentId Id => Attributes.Id;
        public string? FilePath => Attributes.FilePath;
        public IReadOnlyList<string> Folders => Attributes.Folders;
        public string Name => Attributes.Name;
 
        private static ITextAndVersionSource CreateStrongText(TextAndVersion text)
            => new ConstantTextAndVersionSource(text);
 
        private static ITextAndVersionSource CreateStrongText(TextLoader loader)
            => new LoadableTextAndVersionSource(loader, cacheResult: true);
 
        private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, LoadTextOptions loadTextOptions, SolutionServices services)
        {
            var result = new RecoverableTextAndVersion(new ConstantTextAndVersionSource(text), services);
 
            // This RecoverableTextAndVersion is created directly from a TextAndVersion instance. In its initial state,
            // the RecoverableTextAndVersion keeps a strong reference to the initial TextAndVersion, and only
            // transitions to a weak reference backed by temporary storage after the first time GetValue (or
            // GetValueAsync) is called. Since we know we are creating a RecoverableTextAndVersion for the purpose of
            // avoiding problematic address space overhead, we call GetValue immediately to force the object to weakly
            // hold its data from the start.
            result.GetValue(loadTextOptions, CancellationToken.None);
 
            return result;
        }
 
        private static ITextAndVersionSource CreateRecoverableText(TextLoader loader, SolutionServices services)
            => new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), services);
 
        public ITemporaryTextStorageInternal? Storage
            => (TextAndVersionSource as RecoverableTextAndVersion)?.Storage;
 
        public bool TryGetText([NotNullWhen(returnValue: true)] out SourceText? text)
        {
            if (this.TextAndVersionSource.TryGetValue(LoadTextOptions, out var textAndVersion))
            {
                text = textAndVersion.Text;
                return true;
            }
            else
            {
                text = null;
                return false;
            }
        }
 
        public bool TryGetTextVersion(out VersionStamp version)
        {
            // try fast path first
            if (this.TextAndVersionSource is ITextVersionable versionable)
            {
                return versionable.TryGetTextVersion(LoadTextOptions, out version);
            }
 
            if (this.TextAndVersionSource.TryGetValue(LoadTextOptions, out var textAndVersion))
            {
                version = textAndVersion.Version;
                return true;
            }
            else
            {
                version = default;
                return false;
            }
        }
 
        public bool TryGetTextAndVersion([NotNullWhen(true)] out TextAndVersion? textAndVersion)
            => TextAndVersionSource.TryGetValue(LoadTextOptions, out textAndVersion);
 
        public ValueTask<SourceText> GetTextAsync(CancellationToken cancellationToken)
        {
            if (TryGetText(out var text))
            {
                return new ValueTask<SourceText>(text);
            }
 
            return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(
                static (self, cancellationToken) => self.GetTextAndVersionAsync(cancellationToken),
                static (textAndVersion, _) => textAndVersion.Text,
                this,
                cancellationToken);
        }
 
        public SourceText GetTextSynchronously(CancellationToken cancellationToken)
        {
            var textAndVersion = this.TextAndVersionSource.GetValue(LoadTextOptions, cancellationToken);
            return textAndVersion.Text;
        }
 
        public VersionStamp GetTextVersionSynchronously(CancellationToken cancellationToken)
        {
            var textAndVersion = this.TextAndVersionSource.GetValue(LoadTextOptions, cancellationToken);
            return textAndVersion.Version;
        }
 
        public async Task<VersionStamp> GetTextVersionAsync(CancellationToken cancellationToken)
        {
            // try fast path first
            if (TryGetTextVersion(out var version))
            {
                return version;
            }
 
            var textAndVersion = await GetTextAndVersionAsync(cancellationToken).ConfigureAwait(false);
            return textAndVersion.Version;
        }
 
        public TextDocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
        {
            var newTextSource = mode == PreservationMode.PreserveIdentity
                ? CreateStrongText(newTextAndVersion)
                : CreateRecoverableText(newTextAndVersion, LoadTextOptions, solutionServices);
 
            return UpdateText(newTextSource, mode, incremental: true);
        }
 
        public TextDocumentState UpdateText(SourceText newText, PreservationMode mode)
        {
            var newVersion = GetNewerVersion();
            var newTextAndVersion = TextAndVersion.Create(newText, newVersion, FilePath);
 
            return UpdateText(newTextAndVersion, mode);
        }
 
        public TextDocumentState UpdateText(TextLoader loader, PreservationMode mode)
        {
            // don't blow up on non-text documents.
            var newTextSource = mode == PreservationMode.PreserveIdentity
                ? CreateStrongText(loader)
                : CreateRecoverableText(loader, solutionServices);
 
            return UpdateText(newTextSource, mode, incremental: false);
        }
 
        protected virtual TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental)
        {
            return new TextDocumentState(
                solutionServices,
                this.Services,
                this.Attributes,
                textAndVersionSource: newTextSource,
                LoadTextOptions);
        }
 
        private ValueTask<TextAndVersion> GetTextAndVersionAsync(CancellationToken cancellationToken)
        {
            if (this.TextAndVersionSource.TryGetValue(LoadTextOptions, out var textAndVersion))
            {
                return new ValueTask<TextAndVersion>(textAndVersion);
            }
            else
            {
                return new ValueTask<TextAndVersion>(TextAndVersionSource.GetValueAsync(LoadTextOptions, cancellationToken));
            }
        }
 
        internal virtual async Task<Diagnostic?> GetLoadDiagnosticAsync(CancellationToken cancellationToken)
            => (await GetTextAndVersionAsync(cancellationToken).ConfigureAwait(false)).LoadDiagnostic;
 
        private VersionStamp GetNewerVersion()
        {
            if (this.TextAndVersionSource.TryGetValue(LoadTextOptions, out var textAndVersion))
            {
                return textAndVersion.Version.GetNewerVersion();
            }
 
            return VersionStamp.Create();
        }
 
        public virtual async Task<VersionStamp> GetTopLevelChangeTextVersionAsync(CancellationToken cancellationToken)
        {
            var textAndVersion = await this.TextAndVersionSource.GetValueAsync(LoadTextOptions, cancellationToken).ConfigureAwait(false);
            return textAndVersion.Version;
        }
 
        /// <summary>
        /// Only checks if the source of the text has changed, no content check is done.
        /// </summary>
        public bool HasTextChanged(TextDocumentState oldState, bool ignoreUnchangeableDocument)
        {
            if (ignoreUnchangeableDocument && !oldState.CanApplyChange())
            {
                return false;
            }
 
            return oldState.TextAndVersionSource != TextAndVersionSource;
        }
 
        public bool HasInfoChanged(TextDocumentState oldState)
            => oldState.Attributes != Attributes;
    }
}