Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using ast_visual_studio_extension.CxExtension.Utils;
using Microsoft.VisualStudio.Threading;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -83,7 +83,7 @@ public void Schedule(string filePath, Func<CancellationToken, Task> work)
}
catch (Exception ex)
{
Debug.WriteLine($"RealtimeFileScanScheduler: debounced work failed for {filePath}: {ex}");
OutputPaneWriter.WriteError($"Realtime scan failed for {Path.GetFileName(filePath)}: {ex.Message}");
}
}, CancellationToken.None);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using ast_visual_studio_extension.CxExtension.CxAssist.Core;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
Expand All @@ -8,15 +9,18 @@
namespace ast_visual_studio_extension.CxExtension.CxAssist.Realtime.Utils
{
/// <summary>
/// Shows Visual Studio status bar progress with animated progress bar.
/// Displays: "Checkmarx is Scanning File : filename.ext" with animated bar underneath
/// Shows Visual Studio status bar progress message during realtime scans.
/// Displays: "Checkmarx is Scanning File : filename.ext" with single-run progress bar.
/// Progress bar fills from 0-100% once per scan, then clears.
/// </summary>
internal static class RealtimeScanProgressIndicator
{
private static readonly object ProgressLock = new object();
private static int _depth;
private static uint _progressCookie;
private static string _currentFileName = string.Empty;
private static System.Timers.Timer _progressTimer;
private static uint _currentProgress = 0;

internal static async Task PushScanAsync(string scannerName, string sourceFilePath)
{
Expand All @@ -38,13 +42,15 @@ internal static async Task PushScanAsync(string scannerName, string sourceFilePa

try
{
// Show animated progress bar with label in same call
// This prevents the gap between text and bar
// Show progress bar that fills from 0-100% once during the scan.
// Progress fills at ~200ms per 10%, completing in ~2 seconds.
string label = $"Checkmarx is Scanning File : {fileName}";
statusBar.Progress(ref _progressCookie, 1, label, 1, 1);
_currentProgress = 0;
StartProgressBar(label);
}
catch
catch (Exception ex)
{
CxAssistErrorHandler.LogAndSwallow(ex, "RealtimeScanProgressIndicator.PushScanAsync");
TrySetTextFallback($"Checkmarx is Scanning File : {fileName}");
}
}
Expand All @@ -59,40 +65,34 @@ internal static async Task PopScanAsync()
if (_depth > 0)
_depth--;

var statusBar = Package.GetGlobalService(typeof(SVsStatusbar)) as IVsStatusbar;
if (statusBar == null)
{
if (_depth == 0)
{
TrySetTextFallback(string.Empty);
ResetProgress();
}
else
{
TrySetTextFallback($"Checkmarx is Scanning File : {_currentFileName}");
}
return;
}

try
{
if (_depth == 0)
{
// Clear the progress bar when all scans complete
statusBar.Progress(ref _progressCookie, 0, string.Empty, 0, 0);
// Stop progress bar and clear status bar
StopProgressBar();

var statusBar = Package.GetGlobalService(typeof(SVsStatusbar)) as IVsStatusbar;
if (statusBar != null)
{
statusBar.Progress(ref _progressCookie, 0, string.Empty, 0, 0);
}
TrySetTextFallback(string.Empty);
ResetProgress();
}
else
{
// Show progress for next scan
// More scans pending - show current file
string label = $"Checkmarx is Scanning File : {_currentFileName}";
statusBar.Progress(ref _progressCookie, 1, label, 1, 1);
TrySetTextFallback(label);
}
}
catch
catch (Exception ex)
{
CxAssistErrorHandler.LogAndSwallow(ex, "RealtimeScanProgressIndicator.PopScanAsync");
if (_depth == 0)
{
StopProgressBar();
TrySetTextFallback(string.Empty);
ResetProgress();
}
Expand All @@ -102,12 +102,88 @@ internal static async Task PopScanAsync()

/// <summary>
/// Resets progress state when all scans complete.
/// _progressCookie must be reset to 0 so VS allocates a fresh one on the next PushScan.
/// Reusing a cookie that VS has already closed causes the bar to silently disappear.
/// </summary>
private static void ResetProgress()
{
_progressCookie = 0;
_currentFileName = string.Empty;
}

/// <summary>
/// Starts progress bar that fills 0-100% once during scan.
/// VS Progress contract: initialize with cookie=0 and fInProgress=1 (VS allocates cookie),
/// then update with the same cookie, then call with fInProgress=0 to clear.
/// Updates every 200ms with +10% increment = ~2 second fill time.
/// </summary>
private static void StartProgressBar(string label)
{
StopProgressBar();

_currentProgress = 0;

// Initialize progress bar (VS allocates _progressCookie when passed as 0)
var statusBar = Package.GetGlobalService(typeof(SVsStatusbar)) as IVsStatusbar;
if (statusBar != null)
{
try
{
statusBar.Progress(ref _progressCookie, 1, label, 0, 100);
}
catch (Exception ex)
{
CxAssistErrorHandler.LogAndSwallow(ex, "RealtimeScanProgressIndicator.StartProgressBar");
return;
}
}

_progressTimer = new System.Timers.Timer(200);
_progressTimer.Elapsed += (sender, e) =>
{
try
{
lock (ProgressLock)
{
_currentProgress += 10;
if (_currentProgress > 100)
_currentProgress = 100;

var sb = Package.GetGlobalService(typeof(SVsStatusbar)) as IVsStatusbar;
if (sb != null)
{
sb.Progress(ref _progressCookie, 1, label, _currentProgress, 100);
}

// Stop timer once progress reaches 100%
if (_currentProgress >= 100)
{
StopProgressBar();
}
}
}
catch (Exception ex)
{
CxAssistErrorHandler.LogAndSwallow(ex, "RealtimeScanProgressIndicator.ProgressTimer");
}
};
_progressTimer.AutoReset = true;
Comment thread
cx-rakesh-kadu marked this conversation as resolved.
_progressTimer.Start();
}

/// <summary>
/// Stops the progress bar timer.
/// </summary>
private static void StopProgressBar()
{
if (_progressTimer != null)
{
_progressTimer.Stop();
_progressTimer.Dispose();
_progressTimer = null;
}
}

private static void TrySetTextFallback(string message)
{
try
Expand Down
Loading