File: Workspaces\TestWorkspaceFixture.cs
Web Access
Project: src\src\EditorFeatures\TestUtilities\Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj (Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities)
// 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.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.VisualStudio.Text;
using Roslyn.Test.Utilities;
 
namespace Microsoft.CodeAnalysis.Test.Utilities
{
    public abstract class TestWorkspaceFixture : IDisposable
    {
        public int Position;
        public string Code;
 
        private EditorTestWorkspace _workspace;
        private EditorTestHostDocument _currentDocument;
 
        public EditorTestHostDocument CurrentDocument => _currentDocument ?? _workspace.Documents.Single();
 
        public EditorTestWorkspace GetWorkspace(TestComposition composition = null)
        {
            _workspace ??= CreateWorkspace(composition);
            return _workspace;
        }
 
        public EditorTestWorkspace GetWorkspace(string markup, TestComposition composition = null, string workspaceKind = null)
        {
            // If it looks like XML, we'll treat it as XML; any parse error would be rejected and will throw.
            // We'll do a case insensitive search here so if somebody has a lowercase W it'll be tried (and
            // rejected by the XML parser) rather than treated as regular text.
            if (markup.TrimStart().StartsWith("<Workspace>", StringComparison.OrdinalIgnoreCase))
            {
                CloseTextView();
                _workspace?.Dispose();
 
                _workspace = EditorTestWorkspace.CreateWorkspace(XElement.Parse(markup), composition: composition, workspaceKind: workspaceKind);
                _currentDocument = _workspace.Documents.First(d => d.CursorPosition.HasValue);
                Position = _currentDocument.CursorPosition.Value;
                Code = _currentDocument.GetTextBuffer().CurrentSnapshot.GetText();
                return _workspace;
            }
            else
            {
                MarkupTestFile.GetPosition(markup.NormalizeLineEndings(), out Code, out Position);
                var workspace = GetWorkspace(composition);
                _currentDocument = workspace.Documents.Single();
                return workspace;
            }
        }
 
        protected abstract EditorTestWorkspace CreateWorkspace(TestComposition composition);
 
        public void Dispose()
        {
            if (_workspace is null)
                return;
 
            try
            {
                CloseTextView();
                _currentDocument = null;
                Code = null;
                Position = 0;
                _workspace?.Dispose();
            }
            finally
            {
                _workspace = null;
            }
        }
 
        public Document UpdateDocument(string text, SourceCodeKind sourceCodeKind, bool cleanBeforeUpdate = true)
        {
            var hostDocument = _currentDocument ?? (GetWorkspace()).Documents.Single();
 
            // clear the document
            if (cleanBeforeUpdate)
            {
                UpdateText(hostDocument.GetTextBuffer(), string.Empty);
            }
 
            // and set the content
            UpdateText(hostDocument.GetTextBuffer(), text);
 
            GetWorkspace().OnDocumentSourceCodeKindChanged(hostDocument.Id, sourceCodeKind);
 
            return GetWorkspace().CurrentSolution.GetDocument(hostDocument.Id);
        }
 
        private static void UpdateText(ITextBuffer textBuffer, string text)
        {
            using var edit = textBuffer.CreateEdit();
            edit.Replace(0, textBuffer.CurrentSnapshot.Length, text);
            edit.Apply();
        }
 
        private void CloseTextView()
        {
            // The standard use for TestWorkspaceFixture is to call this method in the test's dispose to make sure it's ready to be used for
            // the next test. But some tests in a test class won't use it, so _workspace might still be null.
            if (_workspace?.Documents != null)
            {
                foreach (var document in _workspace?.Documents)
                {
                    document.CloseTextView();
                }
            }
 
            // The editor caches TextFormattingRunProperties instances for better perf, but since things like
            // Brushes are DispatcherObjects, they are tied to the thread they are created on. Since we're going
            // to be run on a different thread, clear out their collection.
            var textFormattingRunPropertiesType = typeof(VisualStudio.Text.Formatting.TextFormattingRunProperties);
            var existingPropertiesField = textFormattingRunPropertiesType.GetField("ExistingProperties", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
            var existingProperties = (List<VisualStudio.Text.Formatting.TextFormattingRunProperties>)existingPropertiesField.GetValue(null);
            existingProperties.Clear();
        }
    }
}