File: EditorConfigSettings\Updater\SettingsUpdaterBase.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
 
internal abstract class SettingsUpdaterBase<TOption, TValue> : ISettingUpdater<TOption, TValue>
{
    private readonly List<(TOption option, TValue value)> _queue = [];
    private readonly SemaphoreSlim _guard = new(1);
    private readonly IAsynchronousOperationListener _listener;
    protected readonly Workspace Workspace;
    protected readonly string EditorconfigPath;
 
    protected abstract SourceText? GetNewText(SourceText analyzerConfigDocument, IReadOnlyList<(TOption option, TValue value)> settingsToUpdate, CancellationToken token);
 
    protected SettingsUpdaterBase(Workspace workspace, string editorconfigPath)
    {
        Workspace = workspace;
        _listener = workspace.Services.GetRequiredService<IWorkspaceAsynchronousOperationListenerProvider>().GetListener();
        EditorconfigPath = editorconfigPath;
    }
 
    public void QueueUpdate(TOption setting, TValue value)
    {
        var token = _listener.BeginAsyncOperation(nameof(QueueUpdate));
        _ = QueueUpdateAsync().CompletesAsyncOperation(token);
 
        return;
 
        // local function
        async Task QueueUpdateAsync()
        {
            using (await _guard.DisposableWaitAsync().ConfigureAwait(false))
            {
                _queue.Add((setting, value));
            }
        }
    }
 
    public async Task<SourceText?> GetChangedEditorConfigAsync(AnalyzerConfigDocument? analyzerConfigDocument, CancellationToken token)
    {
        if (analyzerConfigDocument is null)
            return null;
 
        var originalText = await analyzerConfigDocument.GetValueTextAsync(token).ConfigureAwait(false);
        using (await _guard.DisposableWaitAsync(token).ConfigureAwait(false))
        {
            var newText = GetNewText(originalText, _queue, token);
            if (newText is null || newText.Equals(originalText))
            {
                _queue.Clear();
                return null;
            }
            else
            {
                _queue.Clear();
                return newText;
            }
        }
    }
 
    public async Task<IReadOnlyList<TextChange>?> GetChangedEditorConfigAsync(CancellationToken token)
    {
        var solution = Workspace.CurrentSolution;
        var analyzerConfigDocument = solution.Projects
            .SelectMany(p => p.AnalyzerConfigDocuments)
            .FirstOrDefault(d => d.FilePath == EditorconfigPath);
        var newText = await GetChangedEditorConfigAsync(analyzerConfigDocument, token).ConfigureAwait(false);
        if (newText is null)
        {
            return null;
        }
 
        var originalText = await analyzerConfigDocument!.GetValueTextAsync(token).ConfigureAwait(false);
        return newText.GetTextChanges(originalText);
    }
 
    public async Task<SourceText?> GetChangedEditorConfigAsync(SourceText originalText, CancellationToken token)
    {
        using (await _guard.DisposableWaitAsync(token).ConfigureAwait(false))
        {
            var newText = GetNewText(originalText, _queue, token);
            if (newText is null || newText.Equals(originalText))
            {
                _queue.Clear();
                return null;
            }
            else
            {
                _queue.Clear();
                return newText;
            }
        }
    }
 
    public async Task<bool> HasAnyChangesAsync()
    {
        using (await _guard.DisposableWaitAsync().ConfigureAwait(false))
        {
            return _queue.Any();
        }
    }
}