gcpJGpE(C|0qWBAaYvtjR2_=ovN0N(|I53&U6;%i_3X8ZIpWZgz&``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{y$F_$B+ufvq6sQ7##)H{xbd_{ddD=m!#yQ
zK{o`%6a62t?Pz4}ly!f=;=!`9h`E`2gNm`&R*9-8{<1wwmc0)3-MjLoO@lx4U#Tg2
z&kr<63AjFd@}%(I#yGBkWkFBUyH-loL$4`eOM&fIzopr
E0C10v;Q#;t
literal 0
HcmV?d00001
diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/low.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/low.png
new file mode 100644
index 0000000000000000000000000000000000000000..81df61cf9c73c19ff6d606deb28087a7b810b4cf
GIT binary patch
literal 360
zcmV-u0hj)XP)c>vdjOb)*#93teTB*{y5ZPy#e2Jo9gFyYf8d`Oq0OS#UBhFpX9Kd*ce
z!0(DA1vJ71@Exb@pG`)VN!07l)r#ZZ9AKvQ@2@AEW0%1;rAs)}Y9+`9O%Tr@G6My1
z{qP$0piu0i+M4bqL|Op72MkZnB@-V1)311WPAKzZ;$bHVf>lNP6Gsf&*_zxOL_rw8
z{gzTNmH;*5Y$%L+%oe++W&?gl2m<9~Sqo;W*y&;(H^~~TZBY)gb(LBG0000``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{(nyw$B+ufvq9EEjE*92K582I9k6QfS2^lg
zz`RgNVSxpk`G%(qI~a|GUbD3@h4pVtO@AXM8Eg7A?b+UUFDt(`#5=dB-Z5L%v4~w<
zEN?}p;hcUZ6;lZ>6*0-xZf_?gX|LNVk@!u`VUz14#`_Pt<~-LBnlk_Gj&JKfr>vg;
zmqTPjdF|^`jE$^L#!Fu|((8NU6_yvBni_4D@g%e+
zN%n<<=jO}vnwNBa`kE89`Vvc0+kqCnS6`VDmNs;q-(_Q8(7ZO7XGg5g2Rmjt8ABJo
zh4B+Q7He`hpEz^%%;^t-?+UbzCQtFNIVQ&Z<>T}n%uD~c+6SzjnF0(722WQ%mvv4F
FO#rO7jC241
literal 0
HcmV?d00001
diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/critical.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/critical.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b8a7e566bf91265176845a8d05e42ed1fd4bb84
GIT binary patch
literal 385
zcmV-{0e=38P)n~|c7c}BIUfbq#ilO?@LG%Q4)wci7JCn%A48mi+
zCZz*(7Z(|`g)*GZ5ub^!Yc^B8eL!<`!FsjgJ-FdJd9L&cTX=u^VDMbuH?>WF1R6>G
zG*j>Ox@4;J+vw8lKPGpD&<4LmAi;3DeiNZOS4)Q2KS9oZJKh`o*7cZHFg9yW_5;?4
zz^n@Yb#O)+MWkWG%gJe;@;09-%z>639?CPYQF2Jyu@Y#hJ3iOOs<3)DU|TKmhKeRu
f&(i|$ik;OD8HZNFmMN5f00000NkvXXu0mjfs-~l5
literal 0
HcmV?d00001
diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/high.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/high.png
new file mode 100644
index 0000000000000000000000000000000000000000..fc36e9291cf922d3e52f0e5db9aeda747c757b91
GIT binary patch
literal 360
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{$EcQ$B+ufvy=S$7#&59?Z2LrW-LBqVy5~G
zOQ-0*;0@}Ed&=fDA1U(;
zI_TE-^}uuXfIs_>+`4YbUCwezv9V-{!=EP;R%!V?Ig?e>#64ZO4a0DCTl}KOp{?K@As1D4KGgv1_XnrtDnm{r-UW|
DDG!lj
literal 0
HcmV?d00001
diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/low.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/low.png
new file mode 100644
index 0000000000000000000000000000000000000000..a0574bce17918585465932e870d194e6d4639690
GIT binary patch
literal 349
zcmV-j0iyniP)L*Z6uk6r3@-2j;mrJ(kK1^%WLfLp
z)vG}^^MzXF&(l0HaGVYdp$w|!U!)SB>iOr}FXNs+L>nYtmqz*wT(K!OF#QjD7szP3
v!bicf{{>Bs0Mt|pca*UQqet;U8*j-Oe(^@6d)#;Q00000NkvXXu0mjfHp!2?
literal 0
HcmV?d00001
diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/medium.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/medium.png
new file mode 100644
index 0000000000000000000000000000000000000000..c0b5679fdd2fa2835cf73f9badfff90632e93509
GIT binary patch
literal 359
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{vS^l$B+ufvq9E;O%4LJ63L5Om-cpjWVm(v
z0p}OCN1k7}<{dcC`Gn;W?~64ZH$2l9_HsU8R?*wAXzsozsp;wI_k7aWf0#}w=RYVU
z6`#I@OWrw^>-l1a$t7~NRn<$Tg|&FT%ZwDc{fuSCnnf+j`x^WGE(Ll@U648MbK&ac
za>k-Yv1WGf1wrhqvU0Y&9c9|7cvrvhX&c*|vsWfdM4#iBccLssRxD?BXtq|u`pRvS
zCA@Drvdo_>X3g>NQfX<+r_9D2cI_gjYEku_`nGrGDar8|DsJ~U$Y%X-JDa6UYE$2f
z6aSmqGbJ><`OJ-8>qMo_{bW4rp#e|WyvO<<8NxH1UYnP4Yy^e_gQu&X%Q~loCIFhX
BhMWKZ
literal 0
HcmV?d00001
diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj
index 05fa58cf..be516ba9 100644
--- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj
+++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj
@@ -143,6 +143,9 @@
DevAssistHoverPopup.xaml
+
+ DevAssistHoverPopup.xaml
+
CxWindowControl.xaml
@@ -158,6 +161,10 @@
+
+
+
+
@@ -210,6 +217,8 @@
19.225.1
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -350,6 +359,15 @@
+
+
+
+
+
+
+
+
+
@@ -375,10 +393,9 @@
Designer
MSBuild:Compile
-
- Designer
- MSBuild:Compile
-
+
+ ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers.DevAssistHoverPopup.xaml
+
diff --git a/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs b/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs
index 77e16c87..cdef08a5 100644
--- a/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs
+++ b/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs
@@ -1,4 +1,5 @@
-๏ปฟusing Microsoft.VisualStudio.Shell;
+using ast_visual_studio_extension.CxExtension.Commands;
+using Microsoft.VisualStudio.Shell;
using System;
using System.Runtime.InteropServices;
using System.Threading;
@@ -7,6 +8,7 @@
namespace ast_visual_studio_extension
{
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
+ [ProvideMenuResource("Menus1.ctmenu", 1)]
[Guid(ast_visual_studio_extensionPackage.PackageGuidString)]
public sealed class ast_visual_studio_extensionPackage : AsyncPackage
{
@@ -26,9 +28,10 @@ public sealed class ast_visual_studio_extensionPackage : AsyncPackage
/// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
{
- // When initialized asynchronously, the current thread may be a background thread at this point.
- // Do any initialization that requires the UI thread after switching to the UI thread.
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+
+ // Register "Test DevAssist Hover Popup" so it appears under Tools (menu from Menus1.ctmenu).
+ await TestGutterIconsDirectCommand.InitializeAsync(this);
}
#endregion
diff --git a/docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md b/docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md
new file mode 100644
index 00000000..e2f1dc58
--- /dev/null
+++ b/docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md
@@ -0,0 +1,110 @@
+# DevAssist POC: What Visual Studio Gives vs What We Built Ourselves
+
+A short note in simple words: for each part of the POC, what Visual Studio already does, what it cannot do, and what we built ourselves (and why).
+
+---
+
+## 1. Gutter icon (the small icons left of the code)
+
+**What Visual Studio already does:**
+Visual Studio can show a small icon in the strip on the left of the code (the โgutterโ) when there is an error or warning on that line. It uses a fixed set of iconsโfor example, a red mark for errors and a yellow one for warnings. We could have used that and gotten one standard icon per line.
+
+**Why we did not use it:**
+That built-in option only gives you the same error/warning style. You cannot use your own pictures (e.g. different icons for โCriticalโ, โHighโ, โMediumโ, โLowโ) or custom styling (like colours or layout). We wanted different icons for each severity so users can see at a glance how serious the finding is (like in JetBrains). So we built our own.
+
+**What we built instead:**
+We built our own way to show icons in that strip. Our code decides which line gets which icon and uses our own picture files (e.g. skull for malicious, shield for high). The main pieces are: **DevAssistGlyphTag** (stores โthis line has this severityโ), **DevAssistGlyphTagger** (figures out which lines need an icon), and **DevAssistGlyphFactory** (draws our icon in the gutter).
+
+*Classes (for reference):*
+VS provides: **IErrorTag** / **ErrorTag** (default gutter icon for errors), and **IGlyphFactory** / **IGlyphFactoryProvider** (the mechanism to draw in the gutter). We implement: **DevAssistGlyphTag**, **DevAssistGlyphTagger**, **DevAssistGlyphTaggerProvider**, **DevAssistGlyphFactory**, **DevAssistGlyphFactoryProvider**.
+
+**Why we did not use the older โline markerโ API (IVsTextLineMarker):**
+Visual Studio also has an older way to put marks in the editor (used e.g. in our ASCA feature): **IVsTextLineMarker**, **IVsTextLines.CreateLineMarker**, **IVsTextMarkerClient**. We did not use that for DevAssist because: (1) It is the old system; the newer one fits better with the rest of our code. (2) It does not support our own images or stylingโyou only get the built-in mark types. (3) Our squiggles and hover already use the newer system, so we kept the gutter on the same system for consistency.
+
+---
+
+## 2. Underline (the squiggly line under the code)
+
+**What Visual Studio already does:**
+Visual Studio can draw a squiggly line under text and show a small tooltip when you hover. We use that as-is. We do not draw the squiggle ourselves.
+
+**What we had to add:**
+Visual Studio does not know where our โvulnerabilitiesโ are. We had to tell it: โtreat these lines as errors and show this text in the tooltip.โ So we wrote **DevAssistErrorTagger**: it feeds the line and the message to Visual Studio, and Visual Studio does the rest (squiggle + tooltip).
+
+*Classes (for reference):*
+VS provides: **IErrorTag**, **ErrorTag** (VS draws the squiggle and tooltip from these). We implement: **DevAssistErrorTagger**, **DevAssistErrorTaggerProvider** (they return **ErrorTag** for each line that has a vulnerability).
+
+**In short:** We use the default squiggle and tooltip. We only added the logic that says which lines to underline and what text to show.
+
+---
+
+## 3. Problem window (list of findings)
+
+**What Visual Studio already does:**
+Visual Studio has an โError Listโ window (View โ Error List) that shows build errors and warnings. Other add-ins can add their items there so everything appears in one list.
+
+**Why we did not use it (for this POC):**
+In this POC we wanted our own window so we can show findings in our own way (e.g. grouped by file, with our own columns and actions). So we built a separate โDevAssist findingsโ window: **DevAssistFindingsWindow** and **DevAssistFindingsControl**. It is our own list/tree, not the built-in Error List.
+
+*Classes (for reference):*
+VS provides: **Error List** window (extensions can add items via **IErrorList** and table sources). We implement: **DevAssistFindingsWindow**, **DevAssistFindingsControl**, **ShowFindingsWindowCommand** (our own tool window and UI).
+
+**In short:** We did not put DevAssist findings into the standard Error List. We built our own window to show them.
+
+---
+
+## 4. Mouse hover popup (the box that appears when you hover)
+
+**What Visual Studio already does:**
+When you hover over code, Visual Studio can show a small popup (Quick Info) with plain textโfor example, type or description. We do use that system so our content can appear when Quick Info is shown.
+
+**Why we also built our own popup:**
+That default popup is good for simple text only. It does not reliably show our rich layout: logo, severity icon, coloured badge, clickable links (โFix with Checkmarx One Assistโ, โView detailsโ, etc.). So we added our own popup that appears on hover and shows our full design (**DevAssistHoverPopup**). That way users always see the logo, links, and layout we want.
+
+*Classes (for reference):*
+VS provides: **IQuickInfoControllerProvider**, **IQuickInfoSourceProvider**, **IQuickInfoSource** (Quick Info session and default presenter). We implement: **DevAssistQuickInfoSource** (feeds content into Quick Info), **DevAssistQuickInfoController** (opens our popup on hover), **DevAssistQuickInfoSourceProvider**, **DevAssistQuickInfoControllerProvider**, **DevAssistHoverPopup** (our WPF popup with logo, links, layout).
+
+**In short:** We still use Quick Info for simple content, but we show our own popup as well so the rich hover (logo, links, layout) always appears correctly.
+
+---
+
+## Summary (in one table)
+
+| What we built | What Visual Studio gives (VS classes) | What we implemented (our classes) |
+|----------------------|---------------------------------------|---------------------------------------------------------|
+| Gutter icon | **IErrorTag**, **IGlyphFactory**, **IGlyphFactoryProvider** (one standard icon; we didnโt use for gutter) | **DevAssistGlyphTag**, **DevAssistGlyphTagger**, **DevAssistGlyphTaggerProvider**, **DevAssistGlyphFactory**, **DevAssistGlyphFactoryProvider** |
+| Underline (squiggle) | **IErrorTag**, **ErrorTag** (VS draws squiggle and tooltip) | **DevAssistErrorTagger**, **DevAssistErrorTaggerProvider** (return ErrorTag per line) |
+| Problem window | **Error List**, **IErrorList** (we didnโt use) | **DevAssistFindingsWindow**, **DevAssistFindingsControl**, **ShowFindingsWindowCommand** |
+| Hover popup | **IQuickInfoSourceProvider**, **IQuickInfoSource** (Quick Info; we use for simple content) | **DevAssistQuickInfoSource**, **DevAssistQuickInfoController**, **DevAssistQuickInfoControllerProvider**, **DevAssistHoverPopup** |
+
+---
+
+## Where we already use Visual Studioโs built-in features
+
+| Area | What we use from VS (inbuilt) |
+|------|------------------------------|
+| **Underline (squiggle)** | We use the built-in error layer fully. We only supply **IErrorTag** / **ErrorTag** (line + tooltip text); VS draws the squiggle and tooltip. No custom drawing. |
+| **Gutter** | We use VSโs gutter mechanism (**IGlyphFactory**). We only supply our tag and our icon image; VS hosts the margin and calls our factory. We do not draw the margin ourselves. |
+| **Hover** | We use the **Quick Info** system (**IQuickInfoSource**). We push content into it so the default session can show something. We also show our own popup for the rich layout. |
+| **Theme** | We use VS theme colours in code (**EnvironmentColors**) and apply them after load so the popup respects light/dark theme. |
+
+---
+
+## Suggestions: where we could use more built-in features
+
+1. **Problem window โ Error List (IErrorList)**
+ **Current:** We have our own โDevAssist findingsโ window.
+ **Suggestion:** Also (or instead) add DevAssist findings to the **Error List** via **IErrorList** and a table source. Users would see findings in the same list as build errors, get โgo to lineโ and filters for free, and might not need to open a separate window. We could keep our custom window for a richer view and use the Error List for a quick, built-in list.
+
+2. **Hover โ Rely more on Quick Info**
+ **Current:** We use Quick Info and a custom WPF popup so the rich hover (logo, links) always shows.
+ **Suggestion:** If we ever need a simpler experience, we could try feeding only **ContainerElement** / **ClassifiedTextElement** (text + basic formatting) into Quick Info and rely on the default presenter onlyโno custom popup. We would lose the custom logo and clickable links in the hover, but we would use the inbuilt feature only.
+
+3. **Gutter โ Standard error/warning icon (only if we drop custom icons)**
+ **Current:** We use a custom glyph tag and factory so we can show different icons per severity.
+ **Suggestion:** If we ever donโt need severity-specific icons, we could use the same **IErrorTag** we use for the squiggle and let VS show its default error/warning icon in the gutter. That would remove the need for **DevAssistGlyphTag** and **DevAssistGlyphFactory**, but weโd lose custom images (e.g. skull, shield).
+
+4. **Navigating from Error List**
+ If we add items to the Error List (suggestion 1), we get built-in โclick to go to lineโ and document opening. No extra code needed for that navigation.
+
+**Summary:** We already use built-in features for the squiggle (fully), the gutter mechanism, and Quick Info. The main place to use *more* built-in is the **Error List (IErrorList)** for the list of findings; the rest is already aligned with VS where possible, with custom parts only where we need custom icons or rich hover layout.
From 92b8d6d30934fba812826255b4ba0b880c1c4025 Mon Sep 17 00:00:00 2001
From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com>
Date: Fri, 13 Feb 2026 14:14:27 +0530
Subject: [PATCH 06/45] Added mockData to show the details
---
.../Commands/ShowFindingsWindowCommand.cs | 81 +------
.../DevAssist/Core/DevAssistMockData.cs | 199 ++++++++++++++++++
.../DevAssistTextViewCreationListener.cs | 69 +-----
.../DevAssistFindingsControl.xaml | 24 ++-
.../ast-visual-studio-extension.csproj | 1 +
5 files changed, 232 insertions(+), 142 deletions(-)
create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs
diff --git a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs
index d67601f0..dc8c9780 100644
--- a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs
+++ b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs
@@ -7,6 +7,7 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Task = System.Threading.Tasks.Task;
+using ast_visual_studio_extension.CxExtension.DevAssist.Core;
using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow;
namespace ast_visual_studio_extension.CxExtension.Commands
@@ -84,81 +85,13 @@ private void PopulateTestData(DevAssistFindingsControl control)
{
if (control == null) return;
- var fileNodes = new ObservableCollection();
+ // Same common mock data as gutter, underline, and popup hover (one source of truth)
+ var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(DevAssistMockData.DefaultFilePath);
+ var fileNodes = DevAssistMockData.BuildFileNodesFromVulnerabilities(
+ vulnerabilities,
+ loadSeverityIcon: LoadSeverityIcon,
+ loadFileIcon: () => LoadIcon("document.png"));
- // File 1: go.mod with High and Medium vulnerabilities
- var file1 = new FileNode
- {
- FileName = "go.mod",
- FilePath = "C:\\Projects\\TestProject\\go.mod",
- FileIcon = LoadIcon("document.png")
- };
-
- file1.Vulnerabilities.Add(new VulnerabilityNode
- {
- Severity = "High",
- SeverityIcon = LoadSeverityIcon("High"),
- PackageName = "helm.sh/helm/v3",
- PackageVersion = "v3.18.2",
- Line = 38,
- Column = 1,
- FilePath = file1.FilePath
- });
-
- file1.Vulnerabilities.Add(new VulnerabilityNode
- {
- Severity = "Medium",
- SeverityIcon = LoadSeverityIcon("Medium"),
- PackageName = "github.com/docker/docker",
- PackageVersion = "v20.10.7",
- Line = 42,
- Column = 1,
- FilePath = file1.FilePath
- });
-
- // Add severity counts
- file1.SeverityCounts.Add(new SeverityCount { Severity = "High", Count = 1, Icon = LoadSeverityIcon("High") });
- file1.SeverityCounts.Add(new SeverityCount { Severity = "Medium", Count = 1, Icon = LoadSeverityIcon("Medium") });
-
- fileNodes.Add(file1);
-
- // File 2: package.json with Malicious and Low vulnerabilities
- var file2 = new FileNode
- {
- FileName = "package.json",
- FilePath = "C:\\Projects\\TestProject\\package.json",
- FileIcon = LoadIcon("document.png")
- };
-
- file2.Vulnerabilities.Add(new VulnerabilityNode
- {
- Severity = "Malicious",
- SeverityIcon = LoadSeverityIcon("Malicious"),
- PackageName = "evil-package",
- PackageVersion = "1.0.0",
- Line = 15,
- Column = 4,
- FilePath = file2.FilePath
- });
-
- file2.Vulnerabilities.Add(new VulnerabilityNode
- {
- Severity = "Low",
- SeverityIcon = LoadSeverityIcon("Low"),
- PackageName = "old-library",
- PackageVersion = "2.3.1",
- Line = 23,
- Column = 4,
- FilePath = file2.FilePath
- });
-
- // Add severity counts
- file2.SeverityCounts.Add(new SeverityCount { Severity = "Malicious", Count = 1, Icon = LoadSeverityIcon("Malicious") });
- file2.SeverityCounts.Add(new SeverityCount { Severity = "Low", Count = 1, Icon = LoadSeverityIcon("Low") });
-
- fileNodes.Add(file2);
-
- // Use SetAllFileNodes to enable filtering
control.SetAllFileNodes(fileNodes);
}
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs
new file mode 100644
index 00000000..a46531b6
--- /dev/null
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs
@@ -0,0 +1,199 @@
+using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models;
+using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Media;
+
+namespace ast_visual_studio_extension.CxExtension.DevAssist.Core
+{
+ ///
+ /// Common mock data used to demonstrate all four POC features:
+ /// underline (squiggle), gutter icon, problem window, and popup hover.
+ /// One source of truth so editor and findings window show the same data.
+ ///
+ public static class DevAssistMockData
+ {
+ /// Default file path used for mock vulnerabilities (editor and findings window).
+ public const string DefaultFilePath = "Program.cs";
+
+ ///
+ /// Returns the common list of mock vulnerabilities used for:
+ /// - Gutter icons (severity-specific icons on lines 1, 3, 5, 7, 9)
+ /// - Underline (squiggles on the same lines)
+ /// - Popup hover (hover over those lines to see rich popup with OSS/ASCA content)
+ /// - Problem window (when converted to FileNodes via BuildFileNodesFromVulnerabilities)
+ ///
+ /// Optional file path; if null or empty, uses DefaultFilePath.
+ public static List GetCommonVulnerabilities(string filePath = null)
+ {
+ var path = string.IsNullOrEmpty(filePath) ? DefaultFilePath : filePath;
+
+ return new List
+ {
+ // Line 1 โ Malicious (OSS) โ shows in gutter, underline, hover, problem window
+ new Vulnerability
+ {
+ Id = "POC-001",
+ Title = "Malicious Package",
+ Description = "Test Malicious vulnerability โ known malicious package in dependencies.",
+ Severity = SeverityLevel.Malicious,
+ Scanner = ScannerType.OSS,
+ LineNumber = 1,
+ ColumnNumber = 0,
+ FilePath = path,
+ PackageName = "node-ipc",
+ PackageVersion = "10.1.1",
+ RecommendedVersion = "10.2.0",
+ CveName = "CVE-Malicious-Example",
+ CvssScore = 9.8,
+ LearnMoreUrl = "https://example.com/cve"
+ },
+ // Line 3 โ Critical (ASCA)
+ new Vulnerability
+ {
+ Id = "POC-002",
+ Title = "SQL Injection",
+ Description = "Test Critical vulnerability โ user input concatenated into SQL without sanitization.",
+ Severity = SeverityLevel.Critical,
+ Scanner = ScannerType.ASCA,
+ LineNumber = 3,
+ ColumnNumber = 0,
+ FilePath = path,
+ RuleName = "SQL_INJECTION",
+ RemediationAdvice = "Use parameterized queries or prepared statements."
+ },
+ // Line 5 โ High (OSS) โ first of two on same line (severity count in popup)
+ new Vulnerability
+ {
+ Id = "POC-003",
+ Title = "High-Risk Package",
+ Description = "Test High vulnerability โ vulnerable version of package.",
+ Severity = SeverityLevel.High,
+ Scanner = ScannerType.OSS,
+ LineNumber = 5,
+ ColumnNumber = 0,
+ FilePath = path,
+ PackageName = "lodash",
+ PackageVersion = "4.17.15",
+ RecommendedVersion = "4.17.21",
+ CveName = "CVE-2020-8203",
+ CvssScore = 7.4
+ },
+ // Line 5 โ Medium (second on same line)
+ new Vulnerability
+ {
+ Id = "POC-004",
+ Title = "Medium Severity Finding",
+ Description = "Test Medium vulnerability on same line as High.",
+ Severity = SeverityLevel.Medium,
+ Scanner = ScannerType.ASCA,
+ LineNumber = 5,
+ ColumnNumber = 0,
+ FilePath = path,
+ RuleName = "WEAK_CRYPTO",
+ RemediationAdvice = "Use a stronger algorithm."
+ },
+ // Line 7 โ Medium (OSS)
+ new Vulnerability
+ {
+ Id = "POC-005",
+ Title = "Outdated Dependency",
+ Description = "Test Medium vulnerability โ dependency has a known issue.",
+ Severity = SeverityLevel.Medium,
+ Scanner = ScannerType.OSS,
+ LineNumber = 7,
+ ColumnNumber = 0,
+ FilePath = path,
+ PackageName = "axios",
+ PackageVersion = "0.21.0",
+ RecommendedVersion = "0.27.0"
+ },
+ // Line 9 โ Low
+ new Vulnerability
+ {
+ Id = "POC-006",
+ Title = "Low Severity",
+ Description = "Test Low vulnerability โ minor finding.",
+ Severity = SeverityLevel.Low,
+ Scanner = ScannerType.OSS,
+ LineNumber = 9,
+ ColumnNumber = 0,
+ FilePath = path,
+ PackageName = "debug",
+ PackageVersion = "2.6.9"
+ }
+ };
+ }
+
+ ///
+ /// Builds the Findings window tree (FileNode with VulnerabilityNodes) from the common vulnerability list.
+ /// Use the same list from GetCommonVulnerabilities so problem window shows the same data as gutter/underline/hover.
+ ///
+ /// Typically from GetCommonVulnerabilities().
+ /// Callback to get severity icon (e.g. from ShowFindingsWindowCommand). Can be null; then SeverityIcon is not set.
+ /// Callback to get file icon. Can be null.
+ public static ObservableCollection BuildFileNodesFromVulnerabilities(
+ List vulnerabilities,
+ Func loadSeverityIcon = null,
+ Func loadFileIcon = null)
+ {
+ if (vulnerabilities == null || vulnerabilities.Count == 0)
+ return new ObservableCollection();
+
+ var fileIcon = loadFileIcon?.Invoke();
+ var grouped = vulnerabilities
+ .GroupBy(v => string.IsNullOrEmpty(v.FilePath) ? DefaultFilePath : v.FilePath)
+ .OrderBy(g => g.Key);
+
+ var fileNodes = new ObservableCollection();
+
+ foreach (var group in grouped)
+ {
+ var filePath = group.Key;
+ var fileName = System.IO.Path.GetFileName(filePath);
+ if (string.IsNullOrEmpty(fileName)) fileName = filePath;
+
+ var fileNode = new FileNode
+ {
+ FileName = fileName,
+ FilePath = filePath,
+ FileIcon = fileIcon
+ };
+
+ foreach (var v in group)
+ {
+ var vulnNode = new VulnerabilityNode
+ {
+ Severity = v.Severity.ToString(),
+ SeverityIcon = loadSeverityIcon?.Invoke(v.Severity.ToString()),
+ Description = v.Title ?? v.Description,
+ PackageName = v.PackageName,
+ PackageVersion = v.PackageVersion,
+ Line = v.LineNumber,
+ Column = v.ColumnNumber,
+ FilePath = v.FilePath
+ };
+ fileNode.Vulnerabilities.Add(vulnNode);
+ }
+
+ // Severity counts for badges
+ var severityCounts = fileNode.Vulnerabilities
+ .GroupBy(n => n.Severity)
+ .Select(g => new SeverityCount
+ {
+ Severity = g.Key,
+ Count = g.Count(),
+ Icon = loadSeverityIcon?.Invoke(g.Key)
+ });
+ foreach (var sc in severityCounts)
+ fileNode.SeverityCounts.Add(sc);
+
+ fileNodes.Add(fileNode);
+ }
+
+ return fileNodes;
+ }
+ }
+}
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs
index 23a903ff..85ddd0c6 100644
--- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs
@@ -3,7 +3,7 @@
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
-using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models;
+using ast_visual_studio_extension.CxExtension.DevAssist.Core;
using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers;
namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons
@@ -56,73 +56,14 @@ public void TextViewCreated(IWpfTextView textView)
if (glyphTagger != null && errorTagger != null)
{
- System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, adding test vulnerabilities");
+ System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, adding common mock data (underline, gutter, hover, problem window)");
- // Create test vulnerabilities
- var vulnerabilities = new List
- {
- new Vulnerability
- {
- Id = "TEST-001",
- Severity = SeverityLevel.Malicious,
- LineNumber = 1,
- Description = "Test Malicious vulnerability"
- },
- new Vulnerability
- {
- Id = "TEST-002",
- Severity = SeverityLevel.Critical,
- LineNumber = 3,
- Description = "Test Critical vulnerability"
- },
- new Vulnerability
- {
- Id = "TEST-003",
- Severity = SeverityLevel.High,
- LineNumber = 5,
- Description = "Test High vulnerability"
- },
- new Vulnerability
- {
- Id = "TEST-004",
- Severity = SeverityLevel.Medium,
- LineNumber = 7,
- Description = "Test Medium vulnerability"
- },
- new Vulnerability
- {
- Id = "TEST-005",
- Severity = SeverityLevel.Low,
- LineNumber = 9,
- Description = "Test Low vulnerability"
- },
- new Vulnerability
- {
- Id = "TEST-006",
- Severity = SeverityLevel.Ok,
- LineNumber = 11,
- Description = "Test OK"
- },
- new Vulnerability
- {
- Id = "TEST-007",
- Severity = SeverityLevel.Unknown,
- LineNumber = 13,
- Description = "Test UnKnown"
- },
- new Vulnerability
- {
- Id = "TEST-008",
- Severity = SeverityLevel.Ignored,
- LineNumber = 15,
- Description = "Test Ignored"
- }
- };
+ // One common mock data for all POC features: underline, gutter icon, popup hover, and problem window
+ var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath: null);
- // Update both taggers with the same vulnerabilities
glyphTagger.UpdateVulnerabilities(vulnerabilities);
errorTagger.UpdateVulnerabilities(vulnerabilities);
- System.Diagnostics.Debug.WriteLine("DevAssist: Test vulnerabilities added to both taggers successfully");
+ System.Diagnostics.Debug.WriteLine("DevAssist: Common mock vulnerabilities added to both taggers successfully");
}
else
{
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml
index 0613cb55..3b3ceb29 100644
--- a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml
@@ -277,12 +277,17 @@
Background="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBackgroundBrushKey}}"
Foreground="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowTextBrushKey}}">
-
+
-
diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj
index be516ba9..8a0d8a25 100644
--- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj
+++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj
@@ -166,6 +166,7 @@
+
From 7f3debf12086b3f2bb77b4d86155e2669a0da217 Mon Sep 17 00:00:00 2001
From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com>
Date: Mon, 16 Feb 2026 21:40:02 +0530
Subject: [PATCH 07/45] Changes related to problem window
---
.../DevAssistErrorHandlerTests.cs | 99 ++++++++++
.../Commands/ShowFindingsWindowCommand.cs | 11 +-
.../Core/DevAssistDisplayCoordinator.cs | 184 ++++++++++++++++++
.../DevAssist/Core/DevAssistErrorHandler.cs | 70 +++++++
.../Core/GutterIcons/DevAssistGlyphFactory.cs | 2 +-
.../Core/GutterIcons/DevAssistGlyphTagger.cs | 74 ++++---
.../DevAssistTextViewCreationListener.cs | 22 ++-
.../Core/Markers/DevAssistErrorTagger.cs | 75 ++++---
.../Markers/DevAssistQuickInfoController.cs | 113 ++++++-----
.../DevAssistFindingsControl.xaml.cs | 57 +++++-
.../ast-visual-studio-extension.csproj | 2 +
11 files changed, 597 insertions(+), 112 deletions(-)
create mode 100644 ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs
create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs
create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs
diff --git a/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs b/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs
new file mode 100644
index 00000000..be5ecb0e
--- /dev/null
+++ b/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs
@@ -0,0 +1,99 @@
+using System;
+using Xunit;
+using ast_visual_studio_extension.CxExtension.DevAssist.Core;
+
+namespace ast_visual_studio_extension_tests.cx_unit_tests.cx_extension_tests
+{
+ ///
+ /// Unit tests for DevAssist error-handling scenarios.
+ /// Verifies that TryRun, TryGet, and LogAndSwallow never rethrow and behave correctly.
+ ///
+ public class DevAssistErrorHandlerTests
+ {
+ [Fact]
+ public void TryRun_ReturnsTrue_WhenActionSucceeds()
+ {
+ bool executed = false;
+ bool result = DevAssistErrorHandler.TryRun(() => { executed = true; }, "Test");
+
+ Assert.True(result);
+ Assert.True(executed);
+ }
+
+ [Fact]
+ public void TryRun_ReturnsFalse_WhenActionThrows()
+ {
+ bool result = DevAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test exception"), "Test");
+
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void TryRun_DoesNotRethrow_WhenActionThrows()
+ {
+ var ex = Record.Exception(() =>
+ DevAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test"), "Test"));
+
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void TryRun_HandlesNullAction_WithoutThrowing()
+ {
+ bool result = DevAssistErrorHandler.TryRun(null, "Test");
+
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void TryGet_ReturnsValue_WhenFunctionSucceeds()
+ {
+ int value = DevAssistErrorHandler.TryGet(() => 42, "Test", 0);
+
+ Assert.Equal(42, value);
+ }
+
+ [Fact]
+ public void TryGet_ReturnsDefault_WhenFunctionThrows()
+ {
+ int value = DevAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 99);
+
+ Assert.Equal(99, value);
+ }
+
+ [Fact]
+ public void TryGet_DoesNotRethrow_WhenFunctionThrows()
+ {
+ var ex = Record.Exception(() =>
+ DevAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 0));
+
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void TryGet_ReturnsDefaultT_WhenFunctionIsNull()
+ {
+ int value = DevAssistErrorHandler.TryGet(null, "Test", 7);
+
+ Assert.Equal(7, value);
+ }
+
+ [Fact]
+ public void LogAndSwallow_DoesNotThrow_WhenGivenException()
+ {
+ var ex = Record.Exception(() =>
+ DevAssistErrorHandler.LogAndSwallow(new InvalidOperationException("Test"), "TestContext"));
+
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void LogAndSwallow_DoesNotThrow_WhenGivenNull()
+ {
+ var ex = Record.Exception(() =>
+ DevAssistErrorHandler.LogAndSwallow(null, "TestContext"));
+
+ Assert.Null(ex);
+ }
+ }
+}
diff --git a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs
index dc8c9780..b7baea9a 100644
--- a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs
+++ b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs
@@ -85,13 +85,20 @@ private void PopulateTestData(DevAssistFindingsControl control)
{
if (control == null) return;
- // Same common mock data as gutter, underline, and popup hover (one source of truth)
+ // Use coordinator's current findings (from last UpdateFindings) so problem window matches gutter/underline
+ var current = DevAssistDisplayCoordinator.GetCurrentFindings();
+ if (current != null && current.Count > 0)
+ {
+ DevAssistDisplayCoordinator.RefreshProblemWindow(control, LoadSeverityIcon, () => LoadIcon("document.png"));
+ return;
+ }
+
+ // No current findings (no file opened yet), show mock data so the window is not empty
var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(DevAssistMockData.DefaultFilePath);
var fileNodes = DevAssistMockData.BuildFileNodesFromVulnerabilities(
vulnerabilities,
loadSeverityIcon: LoadSeverityIcon,
loadFileIcon: () => LoadIcon("document.png"));
-
control.SetAllFileNodes(fileNodes);
}
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs
new file mode 100644
index 00000000..88a3a83f
--- /dev/null
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Reflection;
+using Microsoft.VisualStudio.Text;
+using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models;
+using ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons;
+using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers;
+using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow;
+
+namespace ast_visual_studio_extension.CxExtension.DevAssist.Core
+{
+ ///
+ /// Single coordinator for DevAssist display (Option B).
+ /// Takes one List<Vulnerability> and updates gutter, underline, and problem window in one go.
+ /// Stores issues per file (like JetBrains ProblemHolderService) and notifies via IssuesUpdated so the findings window can subscribe and stay in sync.
+ ///
+ public static class DevAssistDisplayCoordinator
+ {
+ private static readonly object _lock = new object();
+ private static Dictionary> _fileToIssues = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Normalizes a file path for use as the per-file map key (same file always maps to the same key).
+ ///
+ private static string NormalizePath(string path)
+ {
+ if (string.IsNullOrEmpty(path)) return path;
+ try
+ {
+ return Path.GetFullPath(path);
+ }
+ catch
+ {
+ return path;
+ }
+ }
+
+ ///
+ /// Gets the file path for the buffer when it is backed by a file (e.g. for passing to mock data or scan).
+ /// Returns null if the buffer has no associated document.
+ ///
+ public static string GetFilePathForBuffer(ITextBuffer buffer) => TryGetFilePathFromBuffer(buffer);
+
+ ///
+ /// Tries to get the file path for the buffer from ITextDocument (when the buffer is backed by a file).
+ /// Uses reflection so we don't require an extra assembly reference.
+ ///
+ private static string TryGetFilePathFromBuffer(ITextBuffer buffer)
+ {
+ if (buffer?.Properties == null) return null;
+ try
+ {
+ // ITextDocument is in Microsoft.VisualStudio.Text.Logic (or Text.Data); key is often the type
+ var docType = Type.GetType("Microsoft.VisualStudio.Text.ITextDocument, Microsoft.VisualStudio.Text.Logic", false)
+ ?? Type.GetType("Microsoft.VisualStudio.Text.ITextDocument, Microsoft.VisualStudio.Text.Data", false);
+ if (docType == null) return null;
+ if (!buffer.Properties.TryGetProperty(docType, out object doc) || doc == null) return null;
+ var pathProp = docType.GetProperty("FilePath", BindingFlags.Public | BindingFlags.Instance);
+ return pathProp?.GetValue(doc) as string;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Raised when issues are updated (any file). Subscribers (e.g. findings window) can refresh to stay in sync (JetBrains ISSUE_TOPIC-like).
+ ///
+ public static event Action>> IssuesUpdated;
+
+ ///
+ /// Gets all issues by file path (like JetBrains ProblemHolderService.GetAllIssues).
+ ///
+ public static IReadOnlyDictionary> GetAllIssuesByFile()
+ {
+ lock (_lock)
+ {
+ var copy = new Dictionary>(_fileToIssues.Count, StringComparer.OrdinalIgnoreCase);
+ foreach (var kv in _fileToIssues)
+ copy[kv.Key] = new List(kv.Value);
+ return copy;
+ }
+ }
+
+ ///
+ /// Gets the current findings as a single flattened list (for backward compatibility and for BuildFileNodesFromVulnerabilities).
+ ///
+ public static List GetCurrentFindings()
+ {
+ lock (_lock)
+ {
+ if (_fileToIssues.Count == 0) return null;
+ var flat = new List();
+ foreach (var list in _fileToIssues.Values)
+ flat.AddRange(list);
+ return flat;
+ }
+ }
+
+ ///
+ /// Updates gutter icons, underlines (squiggles), and stored findings for the problem window in one call.
+ /// Stores issues per file and raises IssuesUpdated so the findings window can stay in sync (JetBrains-like).
+ ///
+ /// Text buffer for the open file (used to get glyph and error taggers).
+ /// Findings to show; can be null or empty to clear for this file.
+ /// Optional. File path for per-file storage. If null, uses first vulnerability's FilePath when list is non-empty.
+ public static void UpdateFindings(ITextBuffer buffer, List vulnerabilities, string filePath = null)
+ {
+ if (buffer == null)
+ {
+ System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: buffer is null");
+ return;
+ }
+
+ var list = vulnerabilities ?? new List();
+
+ // 1. Update gutter
+ var glyphTagger = DevAssistErrorHandler.TryGet(() => DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetGlyphTagger", null);
+ if (glyphTagger != null)
+ DevAssistErrorHandler.TryRun(() => glyphTagger.UpdateVulnerabilities(list), "Coordinator.GlyphTagger.UpdateVulnerabilities");
+ else
+ System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: glyph tagger not found for buffer");
+
+ // 2. Update underline
+ var errorTagger = DevAssistErrorHandler.TryGet(() => DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetErrorTagger", null);
+ if (errorTagger != null)
+ DevAssistErrorHandler.TryRun(() => errorTagger.UpdateVulnerabilities(list), "Coordinator.ErrorTagger.UpdateVulnerabilities");
+ else
+ System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: error tagger not found for buffer");
+
+ // 3. Store per file and notify (JetBrains ProblemHolderService + ISSUE_TOPIC-like)
+ DevAssistErrorHandler.TryRun(() =>
+ {
+ // Prefer explicit filePath, then path from buffer (so we can clear when list is empty), then first vulnerability
+ string resolvedPath = filePath ?? TryGetFilePathFromBuffer(buffer) ?? (list.Count > 0 ? list[0].FilePath : null);
+ string key = NormalizePath(resolvedPath);
+ if (string.IsNullOrEmpty(key)) return;
+
+ IReadOnlyDictionary> snapshot;
+ lock (_lock)
+ {
+ if (list.Count == 0)
+ _fileToIssues.Remove(key);
+ else
+ _fileToIssues[key] = new List(list);
+ var copy = new Dictionary>(_fileToIssues.Count, StringComparer.OrdinalIgnoreCase);
+ foreach (var kv in _fileToIssues)
+ copy[kv.Key] = new List(kv.Value);
+ snapshot = copy;
+ }
+ IssuesUpdated?.Invoke(snapshot);
+ }, "Coordinator.StoreCurrentFindings");
+
+ System.Diagnostics.Debug.WriteLine($"DevAssistDisplayCoordinator: updated gutter, underline, and per-file findings ({list.Count} for file)");
+ }
+
+ ///
+ /// Updates the problem window control with the current findings (builds FileNodes and calls SetAllFileNodes).
+ /// Call this when the Findings window is shown so it displays the same data as gutter/underline.
+ ///
+ /// The DevAssist Findings control to update.
+ /// Optional; if null, severity icons are not set.
+ /// Optional; if null, file icon is not set.
+ public static void RefreshProblemWindow(
+ DevAssistFindingsControl findingsControl,
+ Func loadSeverityIcon = null,
+ Func loadFileIcon = null)
+ {
+ if (findingsControl == null) return;
+
+ DevAssistErrorHandler.TryRun(() =>
+ {
+ List current = GetCurrentFindings();
+ ObservableCollection fileNodes = current != null && current.Count > 0
+ ? DevAssistMockData.BuildFileNodesFromVulnerabilities(current, loadSeverityIcon, loadFileIcon)
+ : new ObservableCollection();
+ findingsControl.SetAllFileNodes(fileNodes);
+ }, "Coordinator.RefreshProblemWindow");
+ }
+ }
+}
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs
new file mode 100644
index 00000000..0547b784
--- /dev/null
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Diagnostics;
+
+namespace ast_visual_studio_extension.CxExtension.DevAssist.Core
+{
+ ///
+ /// Central error handling for DevAssist so third-party plugins or VS errors
+ /// do not crash gutter, underline, problem window, or hover.
+ /// We log and swallow exceptions at VS callback boundaries (GetTags, GenerateGlyph, etc.).
+ ///
+ internal static class DevAssistErrorHandler
+ {
+ private const string Category = "DevAssist";
+
+ ///
+ /// Logs the exception and returns without rethrowing.
+ /// Use at VS/extension callback boundaries (GetTags, GenerateGlyph, event handlers)
+ /// so our code never throws into VS or other extensions.
+ ///
+ /// The exception (can be from our code, third-party, or VS).
+ /// Short description of where it happened (e.g. "GlyphTagger.GetTags").
+ public static void LogAndSwallow(Exception ex, string context)
+ {
+ if (ex == null) return;
+ try
+ {
+ Debug.WriteLine($"[{Category}] {context}: {ex.Message}");
+ Debug.WriteLine($"[{Category}] {ex.StackTrace}");
+ }
+ catch
+ {
+ // Do not throw from error handler
+ }
+ }
+
+ ///
+ /// Wraps an action in try-catch; on exception logs and swallows (does not rethrow).
+ /// Returns true if the action ran without exception, false otherwise.
+ ///
+ public static bool TryRun(Action action, string context)
+ {
+ try
+ {
+ action?.Invoke();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogAndSwallow(ex, context);
+ return false;
+ }
+ }
+
+ ///
+ /// Tries to run a function; on exception logs, swallows, and returns default(T).
+ ///
+ public static T TryGet(Func func, string context, T defaultValue = default)
+ {
+ try
+ {
+ return func != null ? func() : defaultValue;
+ }
+ catch (Exception ex)
+ {
+ LogAndSwallow(ex, context);
+ return defaultValue;
+ }
+ }
+ }
+}
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs
index 40958e18..7efdeb66 100644
--- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs
@@ -64,7 +64,7 @@ public UIElement GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag)
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine($"DevAssist: Icon loading failed: {ex.Message}");
+ DevAssistErrorHandler.LogAndSwallow(ex, "GlyphFactory.GenerateGlyph");
return null;
}
}
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs
index 0e8d91b4..fd0c14d2 100644
--- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs
@@ -30,49 +30,67 @@ public DevAssistGlyphTagger(ITextBuffer buffer)
public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
{
- System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags called - spans count: {spans.Count}, vulnerabilities count: {_vulnerabilitiesByLine.Count}");
+ var result = new List>();
+ System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}");
- if (spans.Count == 0 || _vulnerabilitiesByLine.Count == 0)
+ if (spans == null || spans.Count == 0 || _vulnerabilitiesByLine.Count == 0)
{
System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags returning early - no spans or vulnerabilities");
- yield break;
+ return result;
}
- var snapshot = spans[0].Snapshot;
+ ITextSnapshot snapshot = null;
+ try
+ {
+ snapshot = spans[0].Snapshot;
+ }
+ catch (Exception ex)
+ {
+ DevAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (snapshot)");
+ }
+
+ if (snapshot == null) return result;
int tagCount = 0;
foreach (var span in spans)
{
- var startLine = snapshot.GetLineNumberFromPosition(span.Start);
- var endLine = snapshot.GetLineNumberFromPosition(span.End);
-
- for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
+ try
{
- if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities))
+ var startLine = snapshot.GetLineNumberFromPosition(span.Start);
+ var endLine = snapshot.GetLineNumberFromPosition(span.End);
+
+ for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
{
- // Get the most severe vulnerability for this line (for gutter icon)
- var mostSevere = GetMostSevereVulnerability(vulnerabilities);
- if (mostSevere != null)
+ if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities))
{
- var line = snapshot.GetLineFromLineNumber(lineNumber);
- var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length);
-
- var tooltipText = BuildTooltipText(vulnerabilities);
- var tag = new DevAssistGlyphTag(
- mostSevere.Severity.ToString(),
- tooltipText,
- mostSevere.Id
- );
-
- tagCount++;
- System.Diagnostics.Debug.WriteLine($"DevAssist: Creating tag #{tagCount} for line {lineNumber}, severity: {mostSevere.Severity}");
- yield return new TagSpan(lineSpan, tag);
+ var mostSevere = GetMostSevereVulnerability(vulnerabilities);
+ if (mostSevere != null)
+ {
+ var line = snapshot.GetLineFromLineNumber(lineNumber);
+ var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length);
+
+ var tooltipText = BuildTooltipText(vulnerabilities);
+ var tag = new DevAssistGlyphTag(
+ mostSevere.Severity.ToString(),
+ tooltipText,
+ mostSevere.Id
+ );
+
+ tagCount++;
+ System.Diagnostics.Debug.WriteLine($"DevAssist: Creating tag #{tagCount} for line {lineNumber}, severity: {mostSevere.Severity}");
+ result.Add(new TagSpan(lineSpan, tag));
+ }
}
}
}
+ catch (Exception ex)
+ {
+ DevAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (span)");
+ }
}
System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags completed - returned {tagCount} tags");
+ return result;
}
///
@@ -80,6 +98,11 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCo
/// Based on JetBrains ProblemDecorator.decorateUI pattern
///
public void UpdateVulnerabilities(List vulnerabilities)
+ {
+ DevAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "GlyphTagger.UpdateVulnerabilities");
+ }
+
+ private void UpdateVulnerabilitiesCore(List vulnerabilities)
{
System.Diagnostics.Debug.WriteLine($"DevAssist: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities");
@@ -103,7 +126,6 @@ public void UpdateVulnerabilities(List vulnerabilities)
System.Diagnostics.Debug.WriteLine($"DevAssist: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines");
System.Diagnostics.Debug.WriteLine($"DevAssist: TagsChanged event has {(TagsChanged != null ? TagsChanged.GetInvocationList().Length : 0)} subscribers");
- // Notify that tags have changed
var snapshot = _buffer.CurrentSnapshot;
var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length);
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs
index 85ddd0c6..8dcb8e2d 100644
--- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
+using System.Threading;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using ast_visual_studio_extension.CxExtension.DevAssist.Core;
@@ -17,6 +18,8 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons
[TextViewRole(PredefinedTextViewRoles.Document)]
internal class DevAssistTextViewCreationListener : IWpfTextViewCreationListener
{
+ private static int _fallbackDocumentCounter;
+
public void TextViewCreated(IWpfTextView textView)
{
System.Diagnostics.Debug.WriteLine("DevAssist: TextViewCreated - C# file opened");
@@ -56,14 +59,21 @@ public void TextViewCreated(IWpfTextView textView)
if (glyphTagger != null && errorTagger != null)
{
- System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, adding common mock data (underline, gutter, hover, problem window)");
+ System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, updating via coordinator (gutter, underline, problem window)");
- // One common mock data for all POC features: underline, gutter icon, popup hover, and problem window
- var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath: null);
+ // Single coordinator call: updates gutter, underline, and current findings for problem window (Option B)
+ var filePath = DevAssistDisplayCoordinator.GetFilePathForBuffer(buffer);
+ // When path is unknown (e.g. ITextDocument not available), use a unique key per buffer so multi-file doesn't overwrite with "Program.cs"
+ if (string.IsNullOrEmpty(filePath))
+ {
+ var fallback = Interlocked.Increment(ref _fallbackDocumentCounter);
+ filePath = $"Document {fallback}";
+ System.Diagnostics.Debug.WriteLine($"DevAssist: GetFilePathForBuffer returned null, using fallback: {filePath}");
+ }
+ var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath);
+ DevAssistDisplayCoordinator.UpdateFindings(buffer, vulnerabilities, filePath);
- glyphTagger.UpdateVulnerabilities(vulnerabilities);
- errorTagger.UpdateVulnerabilities(vulnerabilities);
- System.Diagnostics.Debug.WriteLine("DevAssist: Common mock vulnerabilities added to both taggers successfully");
+ System.Diagnostics.Debug.WriteLine("DevAssist: Coordinator updated gutter, underline, and findings successfully");
}
else
{
diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs
index 7145e180..7ef1ea35 100644
--- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs
+++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs
@@ -26,49 +26,68 @@ public DevAssistErrorTagger(ITextBuffer buffer)
public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans)
{
- System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags called - spans count: {spans.Count}, vulnerabilities count: {_vulnerabilitiesByLine.Count}");
+ var result = new List>();
+ System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}");
- if (spans.Count == 0 || _vulnerabilitiesByLine.Count == 0)
+ if (spans == null || spans.Count == 0 || _vulnerabilitiesByLine.Count == 0)
{
System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags returning early - no spans or vulnerabilities");
- yield break;
+ return result;
}
- var snapshot = spans[0].Snapshot;
+ ITextSnapshot snapshot = null;
+ try
+ {
+ snapshot = spans[0].Snapshot;
+ }
+ catch (Exception ex)
+ {
+ DevAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (snapshot)");
+ }
+
+ if (snapshot == null) return result;
int tagCount = 0;
foreach (var span in spans)
{
- var startLine = snapshot.GetLineNumberFromPosition(span.Start);
- var endLine = snapshot.GetLineNumberFromPosition(span.End);
-
- for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
+ try
{
- if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities))
+ var startLine = snapshot.GetLineNumberFromPosition(span.Start);
+ var endLine = snapshot.GetLineNumberFromPosition(span.End);
+
+ for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
{
- foreach (var vulnerability in vulnerabilities)
+ if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities))
{
- if (!ShouldShowUnderline(vulnerability.Severity))
+ foreach (var vulnerability in vulnerabilities)
{
- System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}");
- continue;
- }
+ if (!ShouldShowUnderline(vulnerability.Severity))
+ {
+ System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}");
+ continue;
+ }
- var line = snapshot.GetLineFromLineNumber(lineNumber);
- var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length);
+ var line = snapshot.GetLineFromLineNumber(lineNumber);
+ var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length);
- var tooltipText = BuildTooltipText(vulnerability);
- IErrorTag tag = new ErrorTag("Error", tooltipText);
+ var tooltipText = BuildTooltipText(vulnerability);
+ IErrorTag tag = new ErrorTag("Error", tooltipText);
- tagCount++;
- System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}");
- yield return new TagSpan(lineSpan, tag);
+ tagCount++;
+ System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}");
+ result.Add(new TagSpan(lineSpan, tag));
+ }
}
}
}
+ catch (Exception ex)
+ {
+ DevAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (span)");
+ }
}
System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags completed - returned {tagCount} error tags");
+ return result;
}
///
@@ -112,6 +131,11 @@ private static string BuildTooltipText(Vulnerability vulnerability)
/// Similar to JetBrains MarkupModel.removeAllHighlighters() + addRangeHighlighter()
///
public void UpdateVulnerabilities(List vulnerabilities)
+ {
+ DevAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "ErrorTagger.UpdateVulnerabilities");
+ }
+
+ private void UpdateVulnerabilitiesCore(List vulnerabilities)
{
System.Diagnostics.Debug.WriteLine($"DevAssist Markers: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities");
@@ -121,7 +145,6 @@ public void UpdateVulnerabilities(List vulnerabilities)
{
foreach (var vulnerability in vulnerabilities)
{
- // Convert 1-based line number to 0-based for Visual Studio
int lineNumber = vulnerability.LineNumber - 1;
if (!_vulnerabilitiesByLine.ContainsKey(lineNumber))
@@ -136,7 +159,6 @@ public void UpdateVulnerabilities(List vulnerabilities)
System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines");
- // Notify that tags have changed
var snapshot = _buffer.CurrentSnapshot;
var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length);
@@ -160,9 +182,10 @@ public void ClearVulnerabilities()
///