提交 424efa48 编写于 作者: M Manish Vasani

Show status for "Running code analysis on <Project/Solution>" execution

1. Now we show the progress bar, which is useful for running this command on a solution with large number of projects
2. Periodically refresh the status bar text + progress, as opening any source file/background nuget restore operations overwrite the status bar text.
上级 6d317555
......@@ -131,7 +131,7 @@ public Task<ImmutableArray<DiagnosticData>> GetDiagnosticsAsync(Solution solutio
return SpecializedTasks.EmptyImmutableArray<DiagnosticData>();
}
public async Task ForceAnalyzeAsync(Solution solution, ProjectId? projectId = null, CancellationToken cancellationToken = default)
public async Task ForceAnalyzeAsync(Solution solution, Action<Project> onProjectAnalyzed, ProjectId? projectId = null, CancellationToken cancellationToken = default)
{
if (_map.TryGetValue(solution.Workspace, out var analyzer))
{
......@@ -141,6 +141,7 @@ public async Task ForceAnalyzeAsync(Solution solution, ProjectId? projectId = nu
if (project != null)
{
await analyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false);
onProjectAnalyzed(project);
}
}
else
......@@ -149,8 +150,11 @@ public async Task ForceAnalyzeAsync(Solution solution, ProjectId? projectId = nu
var index = 0;
foreach (var project in solution.Projects)
{
tasks[index++] = Task.Run(
() => analyzer.ForceAnalyzeProjectAsync(project, cancellationToken));
tasks[index++] = Task.Run(async () =>
{
await analyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false);
onProjectAnalyzed(project);
});
}
await Task.WhenAll(tasks).ConfigureAwait(false);
......
......@@ -43,7 +43,7 @@ internal interface IDiagnosticAnalyzerService
/// <summary>
/// Force computes diagnostics and raises diagnostic events for the given project or solution. all diagnostics returned should be up-to-date with respect to the given project or solution.
/// </summary>
Task ForceAnalyzeAsync(Solution solution, ProjectId? projectId = null, CancellationToken cancellationToken = default);
Task ForceAnalyzeAsync(Solution solution, Action<Project> onProjectAnalyzed, ProjectId? projectId = null, CancellationToken cancellationToken = default);
/// <summary>
/// True if given project has any diagnostics
......
......@@ -199,29 +199,25 @@ public void RunAnalyzers(IVsHierarchy? hierarchy)
string? projectOrSolutionName = project?.Name ?? PathUtilities.GetFileName(solution.FilePath);
// Add a message to VS status bar that we are running code analysis.
var statusMessage = projectOrSolutionName != null
? string.Format(ServicesVSResources.Running_code_analysis_for_0, projectOrSolutionName)
: ServicesVSResources.Running_code_analysis_for_Solution;
var statusBar = _serviceProvider?.GetService(typeof(SVsStatusbar)) as IVsStatusbar;
statusBar?.SetText(statusMessage);
var totalProjectCount = project != null ? 1 : (uint)solution.ProjectIds.Count;
var statusBarUpdater = statusBar != null ?
new StatusBarUpdater(statusBar, _threadingContext, projectOrSolutionName, totalProjectCount) :
null;
// Force complete analyzer execution in background.
var asyncToken = _listener.BeginAsyncOperation($"{nameof(VisualStudioDiagnosticAnalyzerService)}_{nameof(RunAnalyzers)}");
Task.Run(async () =>
{
await _diagnosticService.ForceAnalyzeAsync(solution, project?.Id, CancellationToken.None).ConfigureAwait(false);
var onProjectAnalyzed = statusBarUpdater != null ? statusBarUpdater.OnProjectAnalyzed : (Action<Project>)((Project _) => { });
await _diagnosticService.ForceAnalyzeAsync(solution, onProjectAnalyzed, project?.Id, CancellationToken.None).ConfigureAwait(false);
// If user has disabled live analyzer execution for any project(s), i.e. set RunAnalyzersDuringLiveAnalysis = false,
// then ForceAnalyzeAsync will not cause analyzers to execute.
// We explicitly fetch diagnostics for such projects and report these as "Host" diagnostics.
HandleProjectsWithDisabledAnalysis();
// Add a message to VS status bar that we completed executing code analysis.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();
var notificationMesage = projectOrSolutionName != null
? string.Format(ServicesVSResources.Code_analysis_completed_for_0, projectOrSolutionName)
: ServicesVSResources.Code_analysis_completed_for_Solution;
statusBar?.SetText(notificationMesage);
statusBarUpdater?.Dispose();
}).CompletesAsyncOperation(asyncToken);
return;
......@@ -348,5 +344,87 @@ private bool IsBuildActive()
}
}
}
private sealed class StatusBarUpdater : IDisposable
{
private readonly IVsStatusbar _statusBar;
private readonly IThreadingContext _threadingContext;
private readonly uint _totalProjectCount;
private readonly string _statusMessageWhileRunning;
private readonly string _statusMesageOnCompleted;
private readonly Timer _timer;
private int _analyzedProjectCount;
private bool _disposed;
private uint _statusBarCookie;
public StatusBarUpdater(IVsStatusbar statusBar, IThreadingContext threadingContext, string? projectOrSolutionName, uint totalProjectCount)
{
Contract.ThrowIfFalse(threadingContext.HasMainThread);
_statusBar = statusBar;
_threadingContext = threadingContext;
_totalProjectCount = totalProjectCount;
_statusMessageWhileRunning = projectOrSolutionName != null
? string.Format(ServicesVSResources.Running_code_analysis_for_0, projectOrSolutionName)
: ServicesVSResources.Running_code_analysis_for_Solution;
_statusMesageOnCompleted = projectOrSolutionName != null
? string.Format(ServicesVSResources.Code_analysis_completed_for_0, projectOrSolutionName)
: ServicesVSResources.Code_analysis_completed_for_Solution;
// Set the initial status bar progress and text.
_statusBar.Progress(ref _statusBarCookie, fInProgress: 1, _statusMessageWhileRunning, nComplete: 0, nTotal: totalProjectCount);
_statusBar.SetText(_statusMessageWhileRunning);
// Create a timer to periodically update the status message while running analysis.
_timer = new Timer(new TimerCallback(UpdateStatusOnTimer), new AutoResetEvent(false),
dueTime: TimeSpan.FromSeconds(5), period: TimeSpan.FromSeconds(5));
}
internal void OnProjectAnalyzed(Project _)
{
var analyzedProjectCount = Interlocked.Increment(ref _analyzedProjectCount);
UpdateStatus(isRunning: analyzedProjectCount < _totalProjectCount);
}
// Add a message to VS status bar that we are running code analysis.
private void UpdateStatusOnTimer(object state)
=> UpdateStatus(isRunning: true);
public void Dispose()
{
_timer.Dispose();
_disposed = true;
UpdateStatus(isRunning: false);
}
private void UpdateStatus(bool isRunning)
{
Task.Run(async () =>
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();
string message;
uint analyzedCount;
int fInProgress;
if (isRunning && !_disposed && _analyzedProjectCount != _totalProjectCount)
{
message = _statusMessageWhileRunning;
analyzedCount = (uint)_analyzedProjectCount;
fInProgress = 1;
}
else
{
message = _statusMesageOnCompleted;
analyzedCount = _totalProjectCount;
fInProgress = 0;
}
// Update the status bar progress and text.
_statusBar.Progress(ref _statusBarCookie, fInProgress, message, analyzedCount, _totalProjectCount);
_statusBar.SetText(message);
});
}
}
}
}
......@@ -508,7 +508,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
Throw New NotImplementedException()
End Function
Public Function ForceAnalyzeAsync(solution As Solution, Optional projectId As ProjectId = Nothing, Optional cancellationToken As CancellationToken = Nothing) As Task Implements IDiagnosticAnalyzerService.ForceAnalyzeAsync
Public Function ForceAnalyzeAsync(solution As Solution, onProjectAnalyzed As Action(Of Project), Optional projectId As ProjectId = Nothing, Optional cancellationToken As CancellationToken = Nothing) As Task Implements IDiagnosticAnalyzerService.ForceAnalyzeAsync
Throw New NotImplementedException()
End Function
End Class
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册