Skip to content

Commit

Permalink
feat: load html for oss in ls (#570)
Browse files Browse the repository at this point in the history
  • Loading branch information
teodora-sandu authored Jul 12, 2024
1 parent e76f54c commit 849f18b
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 144 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
### Added
- Updated Open Source, Containers and IaC products to include `.snyk` in the list of supported build files.
- When a `.snyk` file changes, the OSS cache will be dropped triggering a scan.
- Render the OSS suggestion panel using HTML from the Languag Server.

### Fixed
- Refresh the OSS cache for Language Server scans when a file is changed.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package io.snyk.plugin.ui.jcef

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.colors.ColorKey
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.colors.FontPreferences
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.jcef.JBCefBrowserBase
import com.intellij.ui.jcef.JBCefJSQuery
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import io.snyk.plugin.getDocument
import io.snyk.plugin.navigateToSource
import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame
import org.cef.handler.CefLoadHandlerAdapter
import java.awt.Color

class OpenFileLoadHandlerGenerator(
private val project: Project,
Expand Down Expand Up @@ -45,38 +39,6 @@ class OpenFileLoadHandlerGenerator(
return JBCefJSQuery.Response("success")
}

fun toCssHex(color: Color): String {
return "#%02x%02x%02x".format(color.red, color.green, color.blue)
}

fun shift(
colorComponent: Int,
d: Double,
): Int {
val n = (colorComponent * d).toInt()
return n.coerceIn(0, 255)
}

fun getCodeDiffColors(
baseColor: Color,
isHighContrast: Boolean,
): Pair<Color, Color> {
val addedColor =
if (isHighContrast) {
Color(28, 68, 40) // high contrast green
} else {
Color(shift(baseColor.red, 0.75), baseColor.green, shift(baseColor.blue, 0.75))
}

val removedColor =
if (isHighContrast) {
Color(84, 36, 38) // high contrast red
} else {
Color(shift(baseColor.red, 1.25), shift(baseColor.green, 0.85), shift(baseColor.blue, 0.85))
}
return Pair(addedColor, removedColor)
}

fun generate(jbCefBrowser: JBCefBrowserBase): CefLoadHandlerAdapter {
val openFileQuery = JBCefJSQuery.create(jbCefBrowser)
val isDarkTheme = EditorColorsManager.getInstance().isDarkEditor
Expand Down Expand Up @@ -124,60 +86,6 @@ class OpenFileLoadHandlerGenerator(
})();
"""
browser.executeJavaScript(script, browser.url, 0)

val baseColor = UIUtil.getTextFieldBackground()
val (addedColor, removedColor) = getCodeDiffColors(baseColor, isHighContrast)

val textColor = toCssHex(JBUI.CurrentTheme.Label.foreground())
val linkColor = toCssHex(JBUI.CurrentTheme.Link.Foreground.ENABLED)
val dataFlowColor = toCssHex(baseColor)
val borderColor = toCssHex(JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground())
val editorColor =
toCssHex(UIUtil.getTextFieldBackground())

val globalScheme = EditorColorsManager.getInstance().globalScheme
val tearLineColor =
globalScheme.getColor(ColorKey.find("TEARLINE_COLOR")) // The closest color to target_rgb = (198, 198, 200)
val tabItemHoverColor =
globalScheme.getColor(ColorKey.find("INDENT_GUIDE")) // The closest color to target_rgb = RGB (235, 236, 240)

val editorColorsManager = EditorColorsManager.getInstance()
val editorColorsScheme: EditorColorsScheme = editorColorsManager.globalScheme
val fontPreferences: FontPreferences = editorColorsScheme.fontPreferences
val editorFont = fontPreferences.fontFamily

val themeScript = """
(function(){
if (window.themeApplied) {
return;
}
window.themeApplied = true;
const style = getComputedStyle(document.documentElement);
const properties = {
'--text-color': "$textColor",
'--link-color': "$linkColor",
'--data-flow-body-color': "$dataFlowColor",
'--example-line-added-color': "${toCssHex(addedColor)}",
'--example-line-removed-color': "${toCssHex(removedColor)}",
'--tab-item-github-icon-color': "$textColor",
'--tab-item-hover-color': "${tabItemHoverColor?.let { toCssHex(it) }}",
'--scrollbar-thumb-color': "${tearLineColor?.let { toCssHex(it) }}",
'--tabs-bottom-color': "${tearLineColor?.let { toCssHex(it) }}",
'--border-color': "$borderColor",
'--editor-color': "$editorColor",
'--editor-font': "'$editorFont'",
};
for (let [property, value] of Object.entries(properties)) {
document.documentElement.style.setProperty(property, value);
}
// Add theme class to body
const isDarkTheme = $isDarkTheme;
const isHighContrast = $isHighContrast;
document.body.classList.add(isHighContrast ? 'high-contrast' : (isDarkTheme ? 'dark' : 'light'));
})();
"""
browser.executeJavaScript(themeScript, browser.url, 0)
}
}
}
Expand Down
119 changes: 119 additions & 0 deletions src/main/kotlin/io/snyk/plugin/ui/jcef/ThemeBasedStylingGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.snyk.plugin.ui.jcef

import com.intellij.openapi.editor.colors.ColorKey
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.colors.FontPreferences
import com.intellij.ui.jcef.JBCefBrowserBase
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame
import org.cef.handler.CefLoadHandlerAdapter
import java.awt.Color

class ThemeBasedStylingGenerator {
fun toCssHex(color: Color): String {
return "#%02x%02x%02x".format(color.red, color.green, color.blue)
}

fun shift(
colorComponent: Int,
d: Double,
): Int {
val n = (colorComponent * d).toInt()
return n.coerceIn(0, 255)
}

fun getCodeDiffColors(
baseColor: Color,
isHighContrast: Boolean,
): Pair<Color, Color> {
val addedColor =
if (isHighContrast) {
Color(28, 68, 40) // high contrast green
} else {
Color(shift(baseColor.red, 0.75), baseColor.green, shift(baseColor.blue, 0.75))
}

val removedColor =
if (isHighContrast) {
Color(84, 36, 38) // high contrast red
} else {
Color(shift(baseColor.red, 1.25), shift(baseColor.green, 0.85), shift(baseColor.blue, 0.85))
}
return Pair(addedColor, removedColor)
}

fun generate(jbCefBrowser: JBCefBrowserBase): CefLoadHandlerAdapter {
val isDarkTheme = EditorColorsManager.getInstance().isDarkEditor
val isHighContrast =
EditorColorsManager.getInstance().globalScheme.name.contains("High contrast", ignoreCase = true)

return object : CefLoadHandlerAdapter() {
override fun onLoadEnd(
browser: CefBrowser,
frame: CefFrame,
httpStatusCode: Int,
) {
if (frame.isMain) {
val baseColor = UIUtil.getTextFieldBackground()
val (addedColor, removedColor) = getCodeDiffColors(baseColor, isHighContrast)

val textColor = toCssHex(JBUI.CurrentTheme.Label.foreground())
val linkColor = toCssHex(JBUI.CurrentTheme.Link.Foreground.ENABLED)
val dataFlowColor = toCssHex(baseColor)
val borderColor = toCssHex(JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground())
val editorColor =
toCssHex(UIUtil.getTextFieldBackground())
val labelColor = toCssHex(JBUI.CurrentTheme.Label.foreground())

val globalScheme = EditorColorsManager.getInstance().globalScheme
val tearLineColor =
globalScheme.getColor(ColorKey.find("TEARLINE_COLOR")) // The closest color to target_rgb = (198, 198, 200)
val tabItemHoverColor =
globalScheme.getColor(ColorKey.find("INDENT_GUIDE")) // The closest color to target_rgb = RGB (235, 236, 240)

val editorColorsManager = EditorColorsManager.getInstance()
val editorColorsScheme: EditorColorsScheme = editorColorsManager.globalScheme
val fontPreferences: FontPreferences = editorColorsScheme.fontPreferences
val editorFont = fontPreferences.fontFamily

val themeScript = """
(function(){
if (window.themeApplied) {
return;
}
window.themeApplied = true;
const style = getComputedStyle(document.documentElement);
const properties = {
'--text-color': "$textColor",
'--link-color': "$linkColor",
'--data-flow-body-color': "$dataFlowColor",
'--example-line-added-color': "${toCssHex(addedColor)}",
'--example-line-removed-color': "${toCssHex(removedColor)}",
'--tab-item-github-icon-color': "$textColor",
'--tab-item-hover-color': "${tabItemHoverColor?.let { toCssHex(it) }}",
'--scrollbar-thumb-color': "${tearLineColor?.let { toCssHex(it) }}",
'--tabs-bottom-color': "${tearLineColor?.let { toCssHex(it) }}",
'--border-color': "$borderColor",
'--editor-color': "$editorColor",
'--editor-font': "'$editorFont'",
'--label-color': "'$labelColor'",
};
for (let [property, value] of Object.entries(properties)) {
document.documentElement.style.setProperty(property, value);
}
// Add theme class to body
const isDarkTheme = $isDarkTheme;
const isHighContrast = $isHighContrast;
document.body.classList.add(isHighContrast ? 'high-contrast' : (isDarkTheme ? 'dark' : 'light'));
})();
"""
browser.executeJavaScript(themeScript, browser.url, 0)
}
}
}
}
}
11 changes: 7 additions & 4 deletions src/main/kotlin/io/snyk/plugin/ui/jcef/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import com.intellij.ui.jcef.JBCefBrowserBuilder
import org.cef.handler.CefLoadHandlerAdapter
import java.awt.Component

typealias LoadHandlerGenerator = (jbCefBrowser: JBCefBrowser) -> CefLoadHandlerAdapter

object JCEFUtils {
fun getJBCefBrowserComponentIfSupported(
html: String,
loadHandlerGenerator: (jbCefBrowser: JBCefBrowser) -> CefLoadHandlerAdapter,
loadHandlerGenerators: List<LoadHandlerGenerator>,
): Component? {
if (!JBCefApp.isSupported()) {
return null
Expand All @@ -21,9 +23,10 @@ object JCEFUtils {
.setMouseWheelEventEnable(true).build()
jbCefBrowser.setOpenLinksInExternalBrowser(true)

val loadHandler = loadHandlerGenerator(jbCefBrowser)
cefClient.addLoadHandler(loadHandler, jbCefBrowser.cefBrowser)

for (loadHandlerGenerator in loadHandlerGenerators) {
val loadHandler = loadHandlerGenerator(jbCefBrowser)
cefClient.addLoadHandler(loadHandler, jbCefBrowser.cefBrowser)
}
jbCefBrowser.loadHTML(html, jbCefBrowser.cefBrowser.url)

return jbCefBrowser.component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.uiDesigner.core.GridLayoutManager
import com.intellij.util.ui.JBUI
import io.snyk.plugin.SnykFile
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.ui.DescriptionHeaderPanel
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
import io.snyk.plugin.ui.baseGridConstraintsAnchorWest
import io.snyk.plugin.ui.descriptionHeaderPanel
import io.snyk.plugin.ui.jcef.JCEFUtils
import io.snyk.plugin.ui.jcef.LoadHandlerGenerator
import io.snyk.plugin.ui.jcef.OpenFileLoadHandlerGenerator
import io.snyk.plugin.ui.jcef.ThemeBasedStylingGenerator
import io.snyk.plugin.ui.panelGridConstraints
import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel
import io.snyk.plugin.ui.wrapWithScrollPane
Expand All @@ -23,6 +24,7 @@ import java.awt.Font
import java.nio.file.Paths
import javax.swing.JLabel
import javax.swing.JPanel
import kotlin.collections.set

class SuggestionDescriptionPanelFromLS(
snykFile: SnykFile,
Expand All @@ -36,22 +38,31 @@ class SuggestionDescriptionPanelFromLS(
"Snyk encountered an issue while rendering the vulnerability description. Please try again, or contact support if the problem persists. We apologize for any inconvenience caused."

init {
if (
pluginSettings().isGlobalIgnoresFeatureEnabled &&
issue.canLoadSuggestionPanelFromHTML()
) {
val virtualFiles = LinkedHashMap<String, VirtualFile?>()
for (dataFlow in issue.additionalData.dataFlow) {
virtualFiles[dataFlow.filePath] =
VirtualFileManager.getInstance().findFileByNioPath(Paths.get(dataFlow.filePath))
if (issue.canLoadSuggestionPanelFromHTML()) {
val loadHandlerGenerators: MutableList<LoadHandlerGenerator> =
emptyList<LoadHandlerGenerator>().toMutableList()

loadHandlerGenerators += {
ThemeBasedStylingGenerator().generate(it)
}

val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile.project, virtualFiles)
val html = this.getStyledHTML()
val jbCefBrowserComponent =
JCEFUtils.getJBCefBrowserComponentIfSupported(html) {
if (issue.additionalData.getProductType() == ProductType.CODE_SECURITY ||
issue.additionalData.getProductType() == ProductType.CODE_QUALITY
) {
val virtualFiles = LinkedHashMap<String, VirtualFile?>()
for (dataFlow in issue.additionalData.dataFlow) {
virtualFiles[dataFlow.filePath] =
VirtualFileManager.getInstance().findFileByNioPath(Paths.get(dataFlow.filePath))
}

val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile.project, virtualFiles)
loadHandlerGenerators += {
openFileLoadHandlerGenerator.generate(it)
}
}
val html = this.getStyledHTML()
val jbCefBrowserComponent =
JCEFUtils.getJBCefBrowserComponentIfSupported(html, loadHandlerGenerators)
if (jbCefBrowserComponent == null) {
val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT)
this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER)
Expand Down Expand Up @@ -94,7 +105,9 @@ class SuggestionDescriptionPanelFromLS(
JPanel(
GridLayoutManager(lastRowToAddSpacer + 1, 1, JBUI.insets(0, 10, 20, 10), -1, 20),
).apply {
if (issue.additionalData.getProductType() == ProductType.CODE_SECURITY || issue.additionalData.getProductType() == ProductType.CODE_QUALITY) {
if (issue.additionalData.getProductType() == ProductType.CODE_SECURITY ||
issue.additionalData.getProductType() == ProductType.CODE_QUALITY
) {
this.add(
SnykCodeOverviewPanel(issue.additionalData),
panelGridConstraints(2),
Expand Down Expand Up @@ -134,8 +147,11 @@ class SuggestionDescriptionPanelFromLS(
issue.additionalData.getProductType() == ProductType.CODE_QUALITY
) {
ideStyle = SnykStylesheets.SnykCodeSuggestion
} else if (issue.additionalData.getProductType() == ProductType.OSS) {
ideStyle = SnykStylesheets.SnykOSSSuggestion
}
html = html.replace("\${ideStyle}", "<style nonce=\${nonce}>$ideStyle</style>")
html = html.replace("\${headerEnd}", "")
html =
html.replace(
"\${ideScript}",
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/snyk/common/lsp/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,10 @@ data class ScanIssue(

fun canLoadSuggestionPanelFromHTML(): Boolean {
return when (this.additionalData.getProductType()) {
ProductType.OSS -> false
ProductType.CODE_SECURITY, ProductType.CODE_QUALITY -> this.additionalData.details != null
ProductType.OSS -> true
ProductType.CODE_SECURITY, ProductType.CODE_QUALITY ->
pluginSettings().isGlobalIgnoresFeatureEnabled && this.additionalData.details != null

else -> TODO()
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/stylesheets/SnykStylesheets.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package stylesheets
object SnykStylesheets {
private fun getStylesheet(name: String): String {
return this::class.java.getResource(name)?.readText()
?: ""
?: ""
}

val SnykCodeSuggestion = getStylesheet("/stylesheets/snyk_code_suggestion.css")
val SnykOSSSuggestion = getStylesheet("/stylesheets/snyk_oss_suggestion.css")
}

Loading

0 comments on commit 849f18b

Please sign in to comment.