Skip to content

Commit c54806f

Browse files
feat(secrets): add Snyk Secrets product type [IDE-1126][IDE-1749] (#788)
* chore: add skills and rules * feat(ui): add JCEF-based HTML tree view behind registry flag [IDE-1750] Implement Phase 7 of the HTML tree view integration. When the IntelliJ Registry flag `snyk.useHtmlTreeView` is enabled, the native Swing tree + severity toolbar in SnykToolWindowPanel is replaced with a JCEF-based HtmlTreePanel that renders server-driven HTML from the `$/snyk.treeView` LSP notification. Changes: - Add `snyk.useHtmlTreeView` registry key in plugin.xml - Add `isHtmlTreeViewEnabled()` helper in Utils.kt - Add `SnykTreeViewParams` data class and `SnykTreeViewListener` topic - Add `@JsonNotification("$/snyk.treeView")` handler in SnykLanguageClient - Add `executeCommandWithArgs()` public method on LanguageServerWrapper - Add `TreeViewBridgeHandler` with unified `__ideExecuteCommand__` JS bridge - Add `HtmlTreePanel` JCEF panel subscribing to tree view notifications - Conditionally swap TreePanel/HtmlTreePanel in SnykToolWindowPanel - Hide native ViewActions and expand/collapse toolbar when flag is ON - Add unit tests for bridge dispatch logic and LSP notification handler * chore: update .gitignore * chore: update build * chore: update hooks to use spotlessApply * chore: update skill * fix: introduce init HTML * chore: adjust coverage rules * test: add coverage for HtmlTreePanel, executeCommandWithArgs, TreeViewBridgeHandler [IDE-1750] - Add HtmlTreePanelTest: init, dispose, JCEF null path, notification handling, resource file validation, placeholder replacement - Add executeCommandWithArgs tests to LanguageServerWrapperTest: not-initialized guard, successful execution, argument passing, null result - Extend TreeViewBridgeHandlerTest: concurrent dispatch, LS exception handling, null callbackExecutor, default values, missing fields * chore: update skills * chore: update required protocol version to 23 * fix: invoke JS callback on null LS result to prevent callback leak [IDE-1750] - Remove result != null guard in dispatchCommand so callbacks fire even when LanguageServerWrapper.executeCommandWithArgs returns null (e.g. navigateToRange side-effect commands). Prevents leaked entries in window.__ideCallbacks__ and stalled UI state. - Change snyk.useHtmlTreeView registry key to restartRequired=true since the panel swap only happens at SnykToolWindowPanel init time. - Update test to assert callback IS invoked with null result (TDD). * chore: update verification skill * fix: address PR review findings for HTML tree view [IDE-1750] - Fix JCEF browser memory leak: store browser as field, dispose in dispose() - Replace SimpleToolWindowPanel with JPanel (fixes UiDataProvider warning) - Add command allowlist to TreeViewBridgeHandler (security hardening) - Add callbackId validation regex to prevent JS injection - Skip native tree refresh when HTML tree view is enabled (performance) - Replace Thread.sleep with Awaitility in tests - Add tests for buildBridgeScript, ALLOWED_COMMANDS, and all new guards * fix: fall back to native TreePanel when JCEF is unsupported [IDE-1750] Add JBCefApp.isSupported() check before creating HtmlTreePanel to prevent blank panel on systems without JCEF support. Falls back to native Swing TreePanel gracefully. * fix: implement showDocument for file URIs to enable tree view navigation [IDE-1750] The LS snyk.navigateToRange command calls window/showDocument with a file URI back to the client. The previous implementation fell through to super.showDocument() which threw UnsupportedOperationException. Now handles file URIs by resolving the VirtualFile and navigating to the selection range. Unsupported schemes return ShowDocumentResult(false) gracefully instead of throwing. * fix: correct command allowlist — replace snyk.getTreeView with snyk.setNodeExpanded [IDE-1750] The JS tree view uses snyk.setNodeExpanded for expand/collapse state persistence but it was missing from the allowlist. snyk.getTreeView was listed but never used by the JS. * fix: display issue details in HTML tree view + deduplicate tree HTML [IDE-1750] - onShowIssueDetail: bypass native tree search when HTML tree enabled, directly create SuggestionDescriptionPanel from cached ScanIssue - HtmlTreePanel: skip loadHTML when raw HTML hash unchanged to prevent tree collapse on redundant LS re-emissions * fix: broaden showDocument to all products + add debug logging [IDE-1750] - showDocument: match any snyk:// URI with showInDetailPanel action, not just Snyk Code. Map product string to correct ProductType. - onShowIssueDetail: bypass native tree for HTML tree mode, directly create SuggestionDescriptionPanel from cached ScanIssue. - Add diagnostic logging to trace showDocument and cache lookup flow. * feat: add tree view CSS variable mappings to ThemeBasedStylingGenerator [IDE-1750] Add 7 missing --vscode-* CSS variable mappings needed by the HTML tree view styles.css: tree-indentGuidesStroke, sideBar-background, badge-background/foreground, list-activeSelectionBackground/Foreground, list-hoverBackground. * feat: select HTML tree node when gutter icon clicked [IDE-1750] - Add HtmlTreePanel.selectNode() to execute __selectTreeNode__ JS bridge - Store HtmlTreePanel reference in SnykToolWindowPanel - Wire selectNodeAndDisplayDescription to also select in HTML tree - Expands ancestor nodes and scrolls issue into view * feat: select HTML tree node on showDocument snyk:// URI [IDE-1750] Wire onShowIssueDetail to also call htmlTreePanel.selectNode when processing snyk:// showDocument requests, so the tree highlights the corresponding issue node. * feat: reset HTML tree on clean all results action [IDE-1750] - Add HtmlTreePanel.reset() to reload initial empty HTML - Call reset() from doCleanUi so 'Clean all results' clears the HTML tree * feat: replace spinner init HTML with proper product nodes tree [IDE-1750] TreeViewInit.html now renders the same structure as the LS tree: - Filter toolbar with severity SVG buttons (all active) - Expand/collapse all buttons - 3 product nodes (Open Source, Code Security, Infrastructure As Code) with their inline SVG icons, matching the LS tree.html template - Uses the same CSS classes for theme integration via var(--vscode-*) * fix: update description panel directly when HTML tree active [IDE-1750] When isHtmlTreeViewEnabled(), selectNodeAndDisplayDescription now directly loads SuggestionDescriptionPanel instead of relying on native JTree node lookup (which may not have nodes populated). Fixes gutter icon click sometimes not updating the description panel. * refactor: extract selectNodeInHtmlTreeAndShowDescription + fix unsafe offset [IDE-1750] - Extract duplicated HTML tree description panel update logic into private selectNodeInHtmlTreeAndShowDescription method, called from both onShowIssueDetail and selectNodeAndDisplayDescription. - Fix unsafe offset calculation in showDocument file URI handler by clamping startOffset/endOffset to document.textLength, preventing IndexOutOfBoundsException when character exceeds line length. * test: add coverage for HtmlTreePanel reset, selectNode, and dedup [IDE-1750] Add tests for: - reset() reloads init HTML into browser - reset() skipped after dispose - reset() no-op when JCEF is null - selectNode() no-op when JCEF is null - duplicate HTML content is skipped (hash check) HtmlTreePanel coverage: 71.2% -> 86.4% * chore: bump required LS protocol version to 24 [IDE-1750] * feat(treeview): add missing commands to TreeViewBridgeHandler allowlist [IDE-1750] Add snyk.showScanErrorDetails and snyk.updateFolderConfig to the JCEF bridge allowlist so tree.js can invoke them via __ideExecuteCommand__. * test(treeview): update allowlist tests for new commands [IDE-1750] Add snyk.showScanErrorDetails and snyk.updateFolderConfig to the expected commands in TreeViewBridgeHandlerTest. * fix: address PR review findings in SnykToolWindowPanel, SnykLanguageClient, Types [IDE-1750] - Add missing lateinit to scanListenerLS property declaration - Extract Document.getSafeOffset() to Utils.kt as shared utility - Use getSafeOffset in showDocument file navigation to prevent IndexOutOfBoundsException from out-of-range character offsets - Replace private getSafeOffset copy in Types.kt with shared utility - Remove duplicated isHtmlTreeViewEnabled() check in onShowIssueDetail listener, delegate to selectNodeAndDisplayDescription which handles it * fix: dispose JCEF browsers in SuggestionDescriptionPanel and fix JS callback leak - Make SuggestionDescriptionPanel implement Disposable to properly clean up JCEF browser processes when panels are replaced - Add clearDescriptionPanel() helper that disposes old Disposable children before removeAll(), preventing cumulative native process/memory leaks - Send error callback to JS when commands are rejected (not in allowlist) or when execution throws, preventing __ideCallbacks__ memory leaks - Add tests for Disposable behavior and error callback invocation * fix: validate callbackId before allowlist check and downgrade log levels - Move SAFE_CALLBACK_ID validation before allowlist rejection to prevent unsafe callbackIds from reaching callbackExecutor (JS injection risk) - Downgrade verbose logger.info to debug in onShowIssueDetail handler * fix: address PR review bot findings and downgrade verbose logging - Fix JCEF fallback: check htmlTreePanel != null instead of registry flag in scheduleDebouncedTreeRefresh so native tree refreshes when JCEF is unsupported even if HTML tree flag is enabled - Fix async race: add isDisposed flag to SuggestionDescriptionPanel to prevent orphaned JCEF browsers when panel is disposed during LS call - Fix hash collision: use string equality instead of hashCode() for HTML deduplication in HtmlTreePanel - Downgrade routine logger.info to debug in SnykLanguageClient (scan completed, force-saved settings, logTrace, MessageType.Log, showDocument) * fix: show native toolbar actions when JCEF is unsupported Guard toolbar action hiding in SnykToolWindow with JBCefApp.isSupported() check so native ViewActions and expand/collapse actions remain visible when JCEF is unavailable even if the HTML tree registry flag is enabled. This matches the fallback logic in SnykToolWindowPanel. * fix: adjust log levels per PR review comments - L468: info→debug for missing issueID (routine condition) - L500: warn→error for unsupported URI scheme (error condition) * fix: optimize onShowIssueDetail performance and dispose panel on destroy - Guard debug log evaluation with isDebugEnabled to avoid expensive flatten/map operations when debug logging is disabled - Use firstNotNullOfOrNull instead of flatten().firstOrNull() to avoid creating intermediate lists for issue lookup - Use sequence + sumOf for debug stats to reduce allocations - Call clearDescriptionPanel() in dispose() to ensure last active SuggestionDescriptionPanel JCEF browser is released on shutdown * fix: delegate http/https URIs to browser and cap getSafeOffset at line end - showDocument now opens http/https URIs via BrowserUtil.browse() instead of returning false, enabling LS-triggered 'Open in Browser' actions - getSafeOffset caps offset at line end (getLineEndOffset) instead of document end (textLength) to prevent bleeding into next line when character index exceeds actual line length - Add test for https browser delegation, update existing tests * fix: delegate unsupported URI schemes to super.showDocument Instead of returning false for unknown schemes, delegate to the LSP4j default handler via super.showDocument(param) to preserve platform-level behavior. * fix: update show document handler * feat(secrets): add Snyk Secrets product type with annotations, caching, and tests - Add SECRETS to ProductType enum with tree name and description - Add Secrets to LsProduct enum with short/long name mappings - Create SnykSecretsAnnotator extending SnykAnnotator for editor annotations - Add secrets icons (secrets.svg, secrets_disabled.svg) - Add currentSecretsResultsLS and currentSecretsError to SnykCachedResults - Handle LsProduct.Secrets in SnykLanguageClient diagnostics pipeline - Add SECRETS branches to getSnykCachedResultsForProduct, CodeActionIntention - Fix ScanIssue.title() to handle SECRETS type (was TODO() crash) - Fix CodeActionIntention.getIcon() for SECRETS (was TODO() crash) - Fix scanningStarted to clear currentSecretsError on new scan - Move annotation refresh debouncing from SnykToolWindowPanel to SnykCachedResults - Rename IAC treeName from Configuration to Snyk Infrastructure as Code - Add isSnykSecretsRunning utility function - Add secretsScanEnabled setting to SnykApplicationSettingsStateService - Add scanningSecretsFinished to SnykScanListener - Add tests for ScanIssue SECRETS methods (title, issueNaming, priority, etc.) - Add tests for SnykCachedResults secrets cache and LsProduct mapping * chore: update secrets icons * fix(secrets): add secretsEnabled settings check in SnykSecretsAnnotator Suppress secrets annotations when the user disables the product in settings, consistent with SnykOSSAnnotator and SnykIaCAnnotator. * fix(lsp): prevent NPE on null baseBranch in FolderConfig.copy perf(core): optimize memory consumption and execution time during high volume LSP message processing - Lazily evaluate SnykFile.relativePath to reduce coroutine allocations - Use O(1) map lookup for issue fetching in SnykAnnotator instead of O(N) iteration - Lazily evaluate Document textRange and VirtualFile in ScanIssue to prevent massive memory allocations for large projects * Revert "fix(lsp): prevent NPE on null baseBranch in FolderConfig.copy" This reverts commit b01aded. * fix: ignore action in integrity check notification * test(jcef): replace MockK verify(timeout) with Awaitility for reliable async verification [IDE-1749] MockK's verify(timeout=...) has a known race condition on Windows that causes NoSuchElementException (List is empty) when the internal call list is polled concurrently. Replaced both occurrences in TreeViewBridgeHandlerTest with await().atMost(...).untilAsserted { verify {...} } which is thread-safe. * fix(settings): use ParametersListUtil.parse for additionalParameters tokenization [IDE-1126] Replace naive split(" ", lineSeparator) with ParametersListUtil.parse() which correctly handles double-quoted arguments containing spaces, preventing them from being split into multiple tokens. Note: single-quoted arguments are not supported by ParametersListUtil; users should use double quotes for arguments containing spaces. Add table tests covering: simple flags, single-quoted (split, unsupported), double-quoted (preserved), newline-separated, empty input, and the real-world complex CLI invocation from the bug report. * chore: remove smells * feat: register secrets annotator * fix: check file version for product int when clearing cache * chore: cleanup warnings * fix(cache): atomically drain pendingAnnotationRefreshFiles to prevent silent file loss [IDE-1749] Replace non-atomic toList().also { clear() } with removeIf-based drain in flushPendingAnnotationRefreshes. A file added between toList() and clear() was silently dropped and would only be re-queued on the next diagnostic update. With ConcurrentHashMap.newKeySet().removeIf, each element is removed and collected atomically per-element. Any file added after the drain has passed its bucket stays in the set and is processed on the next flush cycle — never lost. Extract the drain into internal drainPendingAnnotationRefreshFiles() for testability. Add table tests covering empty, single, multiple, and pre-drain concurrent-add scenarios. * chore: cleanup smells * refactor(settings): remove dead code calls to isScanTypeChanged and isSeverityEnablementChanged [IDE-1749] These calls had no effect after their return values were unassigned — the methods have no side effects, so calling them without using the result is pure dead code. * chore: add isSecretsRunning to isScanRunning
1 parent 1862e38 commit c54806f

24 files changed

+722
-189
lines changed

src/main/kotlin/icons/SnykIcons.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ object SnykIcons {
2020
val OPEN_SOURCE_SECURITY_DISABLED = getIcon("/icons/oss_disabled.svg", SnykIcons::class.java)
2121
val SNYK_CODE = getIcon("/icons/code.svg", SnykIcons::class.java)
2222
val SNYK_CODE_DISABLED = getIcon("/icons/code_disabled.svg", SnykIcons::class.java)
23+
val SNYK_SECRETS = getIcon("/icons/secrets.svg", SnykIcons::class.java)
24+
val SNYK_SECRETS_DISABLED = getIcon("/icons/secrets_disabled.svg", SnykIcons::class.java)
2325
val IAC = getIcon("/icons/iac.svg", SnykIcons::class.java)
2426
val IAC_DISABLED = getIcon("/icons/iac_disabled.svg", SnykIcons::class.java)
2527

src/main/kotlin/io/snyk/plugin/Utils.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ fun getSnykCachedResultsForProduct(
8989
ProductType.OSS -> getSnykCachedResults(project)?.currentOSSResultsLS
9090
ProductType.IAC -> getSnykCachedResults(project)?.currentIacResultsLS
9191
ProductType.CODE_SECURITY -> getSnykCachedResults(project)?.currentSnykCodeResultsLS
92+
ProductType.SECRETS -> getSnykCachedResults(project)?.currentSecretsResultsLS
9293
}
9394

9495
fun getAnalyticsScanListener(project: Project): AnalyticsScanListener? =
@@ -223,10 +224,15 @@ private fun isProductScanRunning(project: Project, productType: ProductType): Bo
223224
fun isSnykCodeRunning(project: Project): Boolean =
224225
isProductScanRunning(project, ProductType.CODE_SECURITY)
225226

227+
fun isSecretsRunning(project: Project): Boolean = isProductScanRunning(project, ProductType.SECRETS)
228+
226229
fun isIacRunning(project: Project): Boolean = isProductScanRunning(project, ProductType.IAC)
227230

228231
fun isScanRunning(project: Project): Boolean =
229-
isOssRunning(project) || isSnykCodeRunning(project) || isIacRunning(project)
232+
isOssRunning(project) ||
233+
isSnykCodeRunning(project) ||
234+
isIacRunning(project) ||
235+
isSecretsRunning(project)
230236

231237
fun isCliDownloading(): Boolean = getSnykCliDownloaderService().isCliDownloading()
232238

src/main/kotlin/io/snyk/plugin/events/SnykScanListener.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface SnykScanListener {
1111
val SNYK_SCAN_TOPIC = Topic.create("Snyk scan LS", SnykScanListener::class.java)
1212
}
1313

14+
// these update the tree and should not be needed for the HTML tree anymore
1415
fun scanningStarted(snykScan: SnykScanParams) {}
1516

1617
fun scanningSnykCodeFinished()
@@ -21,5 +22,6 @@ interface SnykScanListener {
2122

2223
fun scanningError(snykScan: SnykScanParams)
2324

25+
// these are needed to update the local IDE issue cache for annotations and code vision
2426
fun onPublishDiagnostics(product: LsProduct, snykFile: SnykFile, issues: Set<ScanIssue>)
2527
}

src/main/kotlin/io/snyk/plugin/services/SnykApplicationSettingsStateService.kt

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@ class SnykApplicationSettingsStateService :
2929

3030
@Deprecated("left for old users migration only") var useTokenAuthentication = false
3131
var authenticationType = AuthenticationType.OAUTH2
32+
33+
// download settings
3234
var currentLSProtocolVersion: Int? = 0
33-
var isGlobalIgnoresFeatureEnabled = false
3435
var cliBaseDownloadURL: String = "https://downloads.snyk.io"
3536
var cliPath: String = getDefaultCliPath()
3637
var cliReleaseChannel = "stable"
37-
var issuesToDisplay: String = DISPLAY_ALL_ISSUES
3838
var manageBinariesAutomatically: Boolean = true
39+
40+
// testing flag
3941
var fileListenerEnabled: Boolean = true
42+
4043
// TODO migrate to
4144
// https://plugins.jetbrains.com/docs/intellij/persisting-sensitive-data.html?from=jetbrains.org
4245
var token: String? = null
@@ -50,23 +53,30 @@ class SnykApplicationSettingsStateService :
5053
// can't be private -> serialization will not work
5154
@Deprecated("left for old users migration only") var cliScanEnable: Boolean = true
5255

56+
// products enablement store
5357
var ossScanEnable: Boolean = true
5458
var snykCodeSecurityIssuesScanEnable: Boolean = true
5559
var iacScanEnabled: Boolean = true
60+
var secretsEnabled: Boolean = false
61+
62+
// feature flag / server-side enablement
5663
var sastOnServerEnabled: Boolean? = null
5764
var sastSettingsError: Boolean? = null
65+
66+
// filters enablement store
67+
var issuesToDisplay: String = DISPLAY_ALL_ISSUES // delta
5868
var lowSeverityEnabled = true
5969
var mediumSeverityEnabled = true
6070
var highSeverityEnabled = true
6171
var criticalSeverityEnabled = true
72+
var riskScoreThreshold: Int? = null
73+
var treeFiltering = TreeFiltering()
6274

75+
// ignore display option store
76+
var isGlobalIgnoresFeatureEnabled = false
6377
var openIssuesEnabled = true
6478
var ignoredIssuesEnabled = false
6579

66-
var riskScoreThreshold: Int? = null
67-
68-
var treeFiltering = TreeFiltering()
69-
7080
var lastCheckDate: Date? = null
7181
var pluginFirstRun = true
7282

src/main/kotlin/io/snyk/plugin/services/download/SnykCliDownloaderService.kt

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.snyk.plugin.services.download
22

3-
import com.intellij.openapi.actionSystem.AnAction
4-
import com.intellij.openapi.actionSystem.AnActionEvent
3+
import com.intellij.notification.NotificationAction
54
import com.intellij.openapi.components.Service
65
import com.intellij.openapi.diagnostic.logger
76
import com.intellij.openapi.progress.ProgressIndicator
@@ -54,6 +53,7 @@ class SnykCliDownloaderService {
5453
}
5554

5655
fun downloadLatestRelease(indicator: ProgressIndicator, project: Project) {
56+
if (!pluginSettings().manageBinariesAutomatically) return
5757
currentProgressIndicator = indicator
5858
logger.debug("CLI download starting")
5959
publishAsyncApp(SnykCliDownloadListener.CLI_DOWNLOAD_TOPIC) { cliDownloadStarted() }
@@ -124,6 +124,7 @@ class SnykCliDownloaderService {
124124
* @param force - force the download
125125
*/
126126
fun cliSilentAutoUpdate(indicator: ProgressIndicator, project: Project, force: Boolean = false) {
127+
if (!pluginSettings().manageBinariesAutomatically) return
127128
if (force || isFourDaysPassedSinceLastCheck() || !matchesRequiredLsProtocolVersion()) {
128129
val latestReleaseInfo = requestLatestReleasesInformation()
129130

@@ -207,23 +208,19 @@ class SnykCliDownloaderService {
207208

208209
private fun showCliIntegrityWarning(project: Project) {
209210
val redownloadAction =
210-
object : AnAction("Redownload CLI") {
211-
override fun actionPerformed(e: AnActionEvent) {
212-
getSnykTaskQueueService(project)?.downloadLatestRelease(force = true)
213-
}
211+
NotificationAction.createSimpleExpiring("Redownload CLI") {
212+
getSnykTaskQueueService(project)?.downloadLatestRelease(force = true)
214213
}
215214

216215
val ignoreAction =
217-
object : AnAction("Ignore") {
218-
override fun actionPerformed(e: AnActionEvent) {
219-
// Update stored checksum to current file to stop warning
220-
val cliFile = getCliFile()
221-
if (cliFile.exists()) {
222-
try {
223-
pluginSettings().cliSha256 = downloader.calculateSha256(cliFile.readBytes())
224-
} catch (_: Exception) {
225-
// Ignore
226-
}
216+
NotificationAction.createSimpleExpiring("Ignore") {
217+
// Update stored checksum to current file to stop warning
218+
val cliFile = getCliFile()
219+
if (cliFile.exists()) {
220+
try {
221+
pluginSettings().cliSha256 = downloader.calculateSha256(cliFile.readBytes())
222+
} catch (_: Exception) {
223+
// Ignore
227224
}
228225
}
229226
}

src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.intellij.openapi.components.service
88
import com.intellij.openapi.options.SearchableConfigurable
99
import com.intellij.openapi.progress.runBackgroundableTask
1010
import com.intellij.openapi.project.Project
11+
import com.intellij.util.execution.ParametersListUtil
1112
import io.snyk.plugin.events.SnykProductsOrSeverityListener
1213
import io.snyk.plugin.events.SnykResultsFilteringListener
1314
import io.snyk.plugin.events.SnykSettingsListener
@@ -111,8 +112,6 @@ class SnykProjectSettingsConfigurable(val project: Project) : SearchableConfigur
111112
}
112113

113114
val rescanNeeded = isCoreParamsModified()
114-
val productSelectionChanged = snykSettingsDialog.isScanTypeChanged()
115-
val severitySelectionChanged = snykSettingsDialog.isSeverityEnablementChanged()
116115

117116
if (isCustomEndpointModified()) {
118117
settingsStateService.customEndpointUrl = customEndpoint
@@ -235,7 +234,7 @@ fun applyFolderConfigChanges(
235234

236235
val updatedConfig =
237236
existingConfig.copy(
238-
additionalParameters = additionalParameters.split(" ", System.lineSeparator()),
237+
additionalParameters = ParametersListUtil.parse(additionalParameters),
239238
// Clear the preferredOrg field if the auto org selection is enabled.
240239
preferredOrg = if (autoSelectOrgEnabled) "" else preferredOrgText.trim(),
241240
orgSetByUser = !autoSelectOrgEnabled,
@@ -272,7 +271,7 @@ fun handleReleaseChannelChange(project: Project) {
272271
if (!pluginSettings().manageBinariesAutomatically) return
273272

274273
ApplicationManager.getApplication().invokeLater {
275-
@Suppress("CanBeVal") var notification: Notification? = null
274+
var notification: Notification? = null
276275
val downloadAction =
277276
object : AnAction("Download") {
278277
override fun actionPerformed(e: AnActionEvent) {
@@ -287,7 +286,6 @@ fun handleReleaseChannelChange(project: Project) {
287286
notification?.expire()
288287
}
289288
}
290-
@Suppress("AssignedValueIsNeverRead")
291289
notification =
292290
SnykBalloonNotificationHelper.showInfo(
293291
"You changed the release channel. Would you like to download a new Snyk CLI now?",

src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt

Lines changed: 2 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.snyk.plugin.ui.toolwindow
22

3-
import com.intellij.codeInsight.codeVision.CodeVisionHost
4-
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
53
import com.intellij.openapi.Disposable
64
import com.intellij.openapi.application.ApplicationManager
75
import com.intellij.openapi.application.invokeLater
@@ -12,9 +10,7 @@ import com.intellij.openapi.project.Project
1210
import com.intellij.openapi.ui.SimpleToolWindowPanel
1311
import com.intellij.openapi.util.Disposer
1412
import com.intellij.openapi.util.io.toNioPathOrNull
15-
import com.intellij.openapi.vfs.VirtualFile
1613
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
17-
import com.intellij.psi.PsiManager
1814
import com.intellij.ui.OnePixelSplitter
1915
import com.intellij.ui.TreeSpeedSearch
2016
import com.intellij.ui.TreeUIHelper
@@ -128,11 +124,6 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
128124
private val pendingReloadNodes: MutableSet<DefaultMutableTreeNode> =
129125
java.util.concurrent.ConcurrentHashMap.newKeySet()
130126

131-
// Debouncing for annotation refresh - coalesces per-file diagnostic updates
132-
private val annotationRefreshAlarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this)
133-
private val pendingAnnotationRefreshFiles: MutableSet<VirtualFile> =
134-
java.util.concurrent.ConcurrentHashMap.newKeySet()
135-
136127
// Debouncing for tree refresh - coalesces rapid diagnostic updates per product
137128
private val treeRefreshAlarm = Alarm(Alarm.ThreadToUse.SWING_THREAD, this)
138129
private val pendingTreeRefreshProducts: MutableSet<LsProduct> =
@@ -148,15 +139,9 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
148139
// Debounce delay in milliseconds - coalesces rapid scan updates
149140
private const val RELOAD_DEBOUNCE_MS = 100
150141

151-
// Debounce delay for annotation refresh - allows collecting multiple files
152-
private const val ANNOTATION_REFRESH_DEBOUNCE_MS = 150
153-
154142
// Debounce delay for tree refresh - allows diagnostics to accumulate before refreshing tree
155143
private const val TREE_REFRESH_DEBOUNCE_MS = 200
156144

157-
// Max files to refresh individually before falling back to global refresh
158-
private const val MAX_INDIVIDUAL_ANNOTATION_REFRESH = 20
159-
160145
val OSS_ROOT_TEXT = " " + ProductType.OSS.treeName
161146
val CODE_SECURITY_ROOT_TEXT = " " + ProductType.CODE_SECURITY.treeName
162147
val IAC_ROOT_TEXT = " " + ProductType.IAC.treeName
@@ -236,16 +221,6 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
236221
snykFile: SnykFile,
237222
issues: Set<ScanIssue>,
238223
) {
239-
getSnykCachedResults(project)?.let {
240-
when (product) {
241-
LsProduct.Code -> it.currentSnykCodeResultsLS[snykFile] = issues
242-
LsProduct.OpenSource -> it.currentOSSResultsLS[snykFile] = issues
243-
LsProduct.InfrastructureAsCode -> it.currentIacResultsLS[snykFile] = issues
244-
LsProduct.Unknown -> Unit
245-
}
246-
}
247-
// Schedule debounced annotation refresh - coalesces rapid per-file updates
248-
scheduleAnnotationRefresh(snykFile.virtualFile)
249224
// Schedule debounced tree refresh - coalesces rapid diagnostic updates per product
250225
scheduleDebouncedTreeRefresh(product)
251226
}
@@ -509,66 +484,14 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
509484
}
510485
}
511486

512-
/**
513-
* Schedules a debounced annotation refresh for the given file. Collects files over a short window
514-
* and then refreshes them in batch.
515-
*/
516-
private fun scheduleAnnotationRefresh(virtualFile: VirtualFile) {
517-
pendingAnnotationRefreshFiles.add(virtualFile)
518-
annotationRefreshAlarm.cancelAllRequests()
519-
annotationRefreshAlarm.addRequest(
520-
{ flushPendingAnnotationRefreshes() },
521-
ANNOTATION_REFRESH_DEBOUNCE_MS,
522-
)
523-
}
524-
525-
/** Flushes all pending annotation refreshes, either individually or as a global refresh. */
526-
private fun flushPendingAnnotationRefreshes() {
527-
if (isDisposed || project.isDisposed) return
528-
529-
val filesToRefresh =
530-
pendingAnnotationRefreshFiles.toList().also { pendingAnnotationRefreshFiles.clear() }
531-
532-
if (filesToRefresh.isEmpty()) return
533-
534-
// Invalidate code vision for all affected files
535-
invokeLater {
536-
if (!project.isDisposed) {
537-
project
538-
.service<CodeVisionHost>()
539-
.invalidateProvider(CodeVisionHost.LensInvalidateSignal(null))
540-
}
541-
}
542-
543-
// Batch all refreshes into a single invokeLater to avoid EDT queue flooding
544-
invokeLater {
545-
if (isDisposed || project.isDisposed) return@invokeLater
546-
val analyzer = DaemonCodeAnalyzer.getInstance(project)
547-
548-
if (filesToRefresh.size > MAX_INDIVIDUAL_ANNOTATION_REFRESH) {
549-
// Too many files - do a global refresh
550-
analyzer.restart()
551-
} else {
552-
// Refresh each file individually within same EDT task
553-
filesToRefresh.forEach { file ->
554-
if (file.isValid) {
555-
PsiManager.getInstance(project).findFile(file)?.let { psiFile ->
556-
analyzer.restart(psiFile)
557-
}
558-
}
559-
}
560-
}
561-
}
562-
}
563-
564487
/**
565488
* Schedules a debounced tree refresh for the given product. Collects products over a short window
566489
* and then refreshes them in batch. This ensures the tree stays in sync when diagnostics arrive
567490
* after scan completion.
568491
*/
569492
private fun scheduleDebouncedTreeRefresh(product: LsProduct) {
570493
if (product == LsProduct.Unknown) return
571-
if (htmlTreePanel != null) return
494+
if (htmlTreePanel != null) return // this also covers secrets
572495
pendingTreeRefreshProducts.add(product)
573496
treeRefreshAlarm.cancelAllRequests()
574497
treeRefreshAlarm.addRequest({ flushPendingTreeRefreshes() }, TREE_REFRESH_DEBOUNCE_MS)
@@ -615,6 +538,7 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
615538
scanListenerLS.displayIacResults(results)
616539
}
617540
}
541+
LsProduct.Secrets -> Unit // we use the HTML tree for secrets
618542
LsProduct.Unknown -> Unit
619543
}
620544
}

src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowSnykScanListener.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ class SnykToolWindowSnykScanListener(
9191
cache?.currentIacError = null
9292
removeChildrenAndRefresh(rootIacIssuesTreeNode)
9393
}
94+
LsProduct.Secrets -> {
95+
cache?.currentSecretsResultsLS?.clear()
96+
cache?.currentSecretsError = null
97+
// no need to refresh the tree for secrets as they are not displayed in the tool window
98+
}
9499
LsProduct.Unknown -> Unit
95100
}
96101
this.snykToolWindowPanel.updateTreeRootNodesPresentation()
@@ -110,7 +115,6 @@ class SnykToolWindowSnykScanListener(
110115
displaySnykCodeResults(results)
111116
this.snykToolWindowPanel.triggerSelectionListeners = true
112117
}
113-
refreshAnnotationsForOpenFiles(project)
114118
}
115119

116120
override fun scanningOssFinished() {
@@ -125,7 +129,6 @@ class SnykToolWindowSnykScanListener(
125129
displayOssResults(results)
126130
this.snykToolWindowPanel.triggerSelectionListeners = true
127131
}
128-
refreshAnnotationsForOpenFiles(project)
129132
}
130133

131134
override fun scanningIacFinished() {
@@ -140,7 +143,6 @@ class SnykToolWindowSnykScanListener(
140143
displayIacResults(results)
141144
this.snykToolWindowPanel.triggerSelectionListeners = true
142145
}
143-
refreshAnnotationsForOpenFiles(project)
144146
}
145147

146148
override fun scanningError(snykScan: SnykScanParams) {
@@ -156,6 +158,9 @@ class SnykToolWindowSnykScanListener(
156158
LsProduct.InfrastructureAsCode -> {
157159
removeChildrenAndRefresh(rootIacIssuesTreeNode)
158160
}
161+
// secrets are not displayed in the tool window right now, as we want to switch to HTML Tree
162+
// View
163+
LsProduct.Secrets -> Unit
159164
LsProduct.Unknown -> Unit
160165
}
161166
snykToolWindowPanel.updateTreeRootNodesPresentation()
@@ -172,7 +177,7 @@ class SnykToolWindowSnykScanListener(
172177
product: LsProduct,
173178
snykFile: SnykFile,
174179
issues: Set<ScanIssue>,
175-
) {}
180+
) = Unit
176181

177182
fun displaySnykCodeResults(snykResults: Map<SnykFile, Set<ScanIssue>>) {
178183
if (disposed) return

0 commit comments

Comments
 (0)