未验证 提交 f56d67a6 编写于 作者: H Heejae Chang 提交者: GitHub

Update Roslyn snapshot when editorconfig is updated. for now, it only… (#26221)

* Update Roslyn snapshot when editorconfig is updated. for now, it only updates when editorconfig is saved.

* added more comments

* addressed PR feedbacks
上级 cc5b6648
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorLogger;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Utilities;
......@@ -17,6 +16,8 @@ namespace Microsoft.CodeAnalysis.Editor.Options
// isn't yet available outside of Visual Studio.
internal sealed partial class EditorConfigDocumentOptionsProvider : IDocumentOptionsProvider
{
private const int EventDelayInMillisecond = 50;
private readonly object _gate = new object();
/// <summary>
......@@ -25,14 +26,21 @@ internal sealed partial class EditorConfigDocumentOptionsProvider : IDocumentOpt
/// </summary>
private readonly Dictionary<DocumentId, Task<ICodingConventionContext>> _openDocumentContexts = new Dictionary<DocumentId, Task<ICodingConventionContext>>();
private readonly Workspace _workspace;
private readonly ICodingConventionsManager _codingConventionsManager;
private readonly IErrorLoggerService _errorLogger;
internal EditorConfigDocumentOptionsProvider(Workspace workspace)
private ResettableDelay _resettableDelay;
internal EditorConfigDocumentOptionsProvider(Workspace workspace, ICodingConventionsManager codingConventionsManager)
{
_codingConventionsManager = CodingConventionsManagerFactory.CreateCodingConventionsManager();
_workspace = workspace;
_codingConventionsManager = codingConventionsManager;
_errorLogger = workspace.Services.GetService<IErrorLoggerService>();
_resettableDelay = ResettableDelay.CompletedDelay;
workspace.DocumentOpened += Workspace_DocumentOpened;
workspace.DocumentClosed += Workspace_DocumentClosed;
}
......@@ -47,7 +55,13 @@ private void Workspace_DocumentClosed(object sender, DocumentEventArgs e)
// Ensure we dispose the context, which we'll do asynchronously
contextTask.ContinueWith(
t => t.Result.Dispose(),
t =>
{
var context = t.Result;
context.CodingConventionsChangedAsync -= OnCodingConventionsChangedAsync;
context.Dispose();
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
......@@ -59,7 +73,14 @@ private void Workspace_DocumentOpened(object sender, DocumentEventArgs e)
{
lock (_gate)
{
_openDocumentContexts.Add(e.Document.Id, Task.Run(() => GetConventionContextAsync(e.Document.FilePath, CancellationToken.None)));
var contextTask = Task.Run(async () =>
{
var context = await GetConventionContextAsync(e.Document.FilePath, CancellationToken.None).ConfigureAwait(false);
context.CodingConventionsChangedAsync += OnCodingConventionsChangedAsync;
return context;
});
_openDocumentContexts.Add(e.Document.Id, contextTask);
}
}
......@@ -122,5 +143,39 @@ private Task<ICodingConventionContext> GetConventionContextAsync(string path, Ca
() => _codingConventionsManager.GetConventionContextAsync(path, cancellationToken),
defaultValue: EmptyCodingConventionContext.Instance);
}
private Task OnCodingConventionsChangedAsync(object sender, CodingConventionsChangedEventArgs arg)
{
// this is a temporary workaround. once we finish the work to put editorconfig file as a part of roslyn solution snapshot,
// that system will automatically pick up option changes and update snapshot. and it will work regardless
// whether a file is opened in editor or not.
//
// but until then, we need to explicitly touch workspace to update snapshot. and
// only works for open files. it is not easy to track option changes for closed files with current model.
// related tracking issue - https://github.com/dotnet/roslyn/issues/26250
lock (_gate)
{
if (!_resettableDelay.Task.IsCompleted)
{
_resettableDelay.Reset();
}
else
{
// since this event gets raised for all documents that are affected by 1 editconfig file,
// and since for now we make that event as whole solution changed event, we don't need to update
// snapshot for each events. aggregate all events to 1.
var delay = new ResettableDelay(EventDelayInMillisecond);
delay.Task.ContinueWith(_ => _workspace.OnOptionChanged(),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
_resettableDelay = delay;
}
}
return Task.CompletedTask;
}
}
}
......@@ -3,15 +3,25 @@
using System;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.CodingConventions;
namespace Microsoft.CodeAnalysis.Editor.Options
{
[Export(typeof(IDocumentOptionsProviderFactory))]
class EditorConfigDocumentOptionsProviderFactory : IDocumentOptionsProviderFactory
{
private readonly ICodingConventionsManager _codingConventionsManager;
[ImportingConstructor]
[Obsolete("Never call this directly")]
public EditorConfigDocumentOptionsProviderFactory(ICodingConventionsManager codingConventionsManager)
{
_codingConventionsManager = codingConventionsManager;
}
public IDocumentOptionsProvider Create(Workspace workspace)
{
return new EditorConfigDocumentOptionsProvider(workspace);
return new EditorConfigDocumentOptionsProvider(workspace, _codingConventionsManager);
}
}
}
......@@ -9,6 +9,8 @@ namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities
{
internal class ResettableDelay
{
public static readonly ResettableDelay CompletedDelay = new ResettableDelay();
private readonly int _delayInMilliseconds;
private readonly TaskCompletionSource<object> _taskCompletionSource;
......@@ -39,6 +41,16 @@ public ResettableDelay(int delayInMilliseconds, TaskScheduler foregroundTaskSche
}
}
private ResettableDelay()
{
// create resettableDelay with completed state
_delayInMilliseconds = 0;
_taskCompletionSource = new TaskCompletionSource<object>();
_taskCompletionSource.SetResult(null);
Reset();
}
public Task Task => _taskCompletionSource.Task;
public void Reset()
......
......@@ -1051,5 +1051,18 @@ public OptionSet Options
return this.Workspace.Options;
}
}
/// <summary>
/// Update current solution as a result of option changes.
///
/// this is a temporary workaround until editorconfig becomes real part of roslyn solution snapshot.
/// until then, this will explicitly fork current solution snapshot
/// </summary>
internal Solution WithOptionChanged()
{
// options are associated with solution snapshot. creating new snapshot
// will cause us to retrieve new options
return new Solution(_state);
}
}
}
......@@ -725,5 +725,27 @@ protected virtual Project AdjustReloadedProject(Project oldProject, Project relo
return newSolution.GetProject(oldProject.Id);
}
/// <summary>
/// Update current solution as a result of option changes.
///
/// this is a temporary workaround until editorconfig becomes real part of roslyn solution snapshot.
/// until then, this will explicitly move current solution forward when such event happened
/// </summary>
internal void OnOptionChanged()
{
using (_serializationLock.DisposableWait())
{
var oldSolution = this.CurrentSolution;
var newSolution = this.SetCurrentSolution(oldSolution.WithOptionChanged());
// for now, this says whole solution is changed.
// in future, we probably going to just raise additional file changed event (for editconfig file) and then
// let IOptionService.OptionChanged event to raise what option has changed.
// currently, since editorconfig file is not part of solution, we can't say which file is changed.
// 1 editorconfig file can affect multiple files in multiple projects so solution changed is easiest options for now.
this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.SolutionChanged, oldSolution, newSolution);
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册