Skip to content

Commit ff2ba5f

Browse files
authored
feat: style description panel (#220)
* wip: html provider * fix: populate tree after snyk.Scan success * refactor: use css from LS * wip: html provider * fix: merge conflicts * fix: added HtmlProviderFactory tests * fix: use comperator for sorting issues * refactor: create BrowserHandler * fix: cache theme instance
1 parent eed768d commit ff2ba5f

14 files changed

Lines changed: 1971 additions & 152 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.snyk.eclipse.plugin.html;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Random;
6+
7+
import org.eclipse.jface.resource.ColorRegistry;
8+
import org.eclipse.swt.graphics.Color;
9+
import org.eclipse.swt.graphics.RGB;
10+
import org.eclipse.ui.PlatformUI;
11+
import org.eclipse.ui.themes.ITheme;
12+
import org.eclipse.ui.themes.IThemeManager;
13+
14+
public class BaseHtmlProvider {
15+
private final Random random = new Random();
16+
private final Map<String, String> colorCache = new HashMap<>();
17+
private String nonce = "";
18+
19+
public String getCss() {
20+
return "";
21+
}
22+
23+
public String getJs() {
24+
return "";
25+
}
26+
27+
public String getInitScript() {
28+
return "";
29+
}
30+
31+
public String getNonce() {
32+
if(!nonce.isEmpty()) {
33+
return nonce;
34+
}
35+
String allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
36+
StringBuilder nonceBuilder = new StringBuilder(32);
37+
for (int i = 0; i < 32; i++) {
38+
nonceBuilder.append(allowedChars.charAt(random.nextInt(allowedChars.length())));
39+
}
40+
nonce = nonceBuilder.toString();
41+
return nonce;
42+
}
43+
44+
public String replaceCssVariables(String html) {
45+
// Build the CSS with the nonce
46+
String nonce = getNonce();
47+
String css = "<style nonce=\"" + nonce + "\">" + getCss() + "</style>";
48+
html = html.replace("${ideStyle}", css);
49+
html = html.replace("<style nonce=\"ideNonce\" data-ide-style></style>", css);
50+
html = html.replace("var(--default-font)", " ui-sans-serif, \"SF Pro Text\", \"Segoe UI\", \"Ubuntu\", Tahoma, Geneva, Verdana, sans-serif;");
51+
52+
53+
// Replace CSS variables with actual color values
54+
html = html.replace("var(--text-color)", getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_TEXT_COLOR", "#000000"));
55+
html = html.replace("var(--background-color)", getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_BG_START", "#FFFFFF"));
56+
html = html.replace("var(--border-color)", getColorAsHex( "org.eclipse.ui.workbench.ACTIVE_TAB_BORDER_COLOR", "#CCCCCC"));
57+
html = html.replace("var(--link-color)", getColorAsHex("org.eclipse.ui.workbench.HYPERLINK_COLOR", "#0000FF"));
58+
html = html.replace("var(--horizontal-border-color)", getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_HIGHLIGHT_BORDER_COLOR", "#CCCCCC"));
59+
html = html.replace("var(--code-background-color)", getColorAsHex("org.eclipse.ui.workbench.CODE_BACKGROUND_COLOR", "#F0F0F0"));
60+
61+
html = html.replace("${headerEnd}", "");
62+
html = html.replace("${nonce}", nonce);
63+
html = html.replace("ideNonce", nonce);
64+
html = html.replace("${ideScript}", "");
65+
66+
return html;
67+
}
68+
69+
public String getColorAsHex(String colorKey, String defaultColor) {
70+
return colorCache.computeIfAbsent(colorKey, key -> {
71+
ColorRegistry colorRegistry = getColorRegistry();
72+
Color color = colorRegistry.get(colorKey);
73+
if (color == null) {
74+
return defaultColor;
75+
} else {
76+
RGB rgb = color.getRGB();
77+
return String.format("#%02x%02x%02x", rgb.red, rgb.green, rgb.blue);
78+
}
79+
});
80+
}
81+
82+
private ColorRegistry colorRegistry;
83+
private ColorRegistry getColorRegistry() {
84+
if(colorRegistry != null) {
85+
return colorRegistry;
86+
}
87+
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
88+
ITheme currentTheme = themeManager.getCurrentTheme();
89+
colorRegistry = currentTheme.getColorRegistry();
90+
return colorRegistry;
91+
}
92+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package io.snyk.eclipse.plugin.html;
2+
3+
import org.eclipse.ui.PlatformUI;
4+
import org.eclipse.ui.themes.ITheme;
5+
import org.eclipse.ui.themes.IThemeManager;
6+
7+
public class CodeHtmlProvider extends BaseHtmlProvider {
8+
private static CodeHtmlProvider instance = new CodeHtmlProvider();
9+
10+
public static CodeHtmlProvider getInstance() {
11+
if (instance == null) {
12+
synchronized (CodeHtmlProvider.class) {
13+
if (instance == null) {
14+
instance = new CodeHtmlProvider();
15+
}
16+
}
17+
}
18+
return instance;
19+
}
20+
21+
@Override
22+
public String getInitScript() {
23+
String themeScript = getThemeScript();
24+
String initScript = super.getInitScript();
25+
return initScript + "\n" + """
26+
function navigateToIssue(e, target) {
27+
e.preventDefault();
28+
var filePath = target.getAttribute('file-path');
29+
var startLine = target.getAttribute('start-line');
30+
var endLine = target.getAttribute('end-line');
31+
var startCharacter = target.getAttribute('start-character');
32+
var endCharacter = target.getAttribute('end-character');
33+
window.openInEditor(filePath, startLine, endLine, startCharacter, endCharacter);
34+
}
35+
var navigatableLines = document.getElementsByClassName('data-flow-clickable-row');
36+
for(var i = 0; i < navigatableLines.length; i++) {
37+
navigatableLines[i].onclick = function(e) {
38+
navigateToIssue(e, this);
39+
return false;
40+
};
41+
}
42+
if(document.getElementById('position-line')) {
43+
document.getElementById('position-line').onclick = function(e) {
44+
var target = navigatableLines[0];
45+
if(target) {
46+
navigateToIssue(e, target);
47+
}
48+
}
49+
}
50+
// Disable AIfix
51+
if(document.getElementById('ai-fix-wrapper') && document.getElementById('no-ai-fix-wrapper')){
52+
document.getElementById('ai-fix-wrapper').className = 'hidden';
53+
document.getElementById('no-ai-fix-wrapper').className = '';
54+
}
55+
""" + themeScript;
56+
}
57+
58+
private ITheme currentTheme;
59+
private ITheme getCurrentTheme() {
60+
if(currentTheme != null) {
61+
return currentTheme;
62+
}
63+
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
64+
currentTheme = themeManager.getCurrentTheme();
65+
return currentTheme;
66+
}
67+
private String getThemeScript() {
68+
ITheme currentTheme = getCurrentTheme();
69+
String themeId = currentTheme.getId().toLowerCase();
70+
71+
boolean isDarkTheme = themeId.contains("dark");
72+
boolean isHighContrast = themeId.contains("highcontrast") || themeId.contains("high-contrast");
73+
74+
String themeScript = "var isDarkTheme = " + isDarkTheme + ";\n" +
75+
"var isHighContrast = " + isHighContrast + ";\n" +
76+
"document.body.classList.add(isHighContrast ? 'high-contrast' : (isDarkTheme ? 'dark' : 'light'));";
77+
return themeScript;
78+
}
79+
80+
@Override
81+
public String replaceCssVariables(String html) {
82+
html = super.replaceCssVariables(html);
83+
84+
// Replace CSS variables with actual color values
85+
html = html.replace("var(--example-line-removed-color)", super.getColorAsHex("org.eclipse.ui.workbench.lineRemovedColor", "#ff0000"));
86+
html = html.replace("var(--example-line-added-color)", super.getColorAsHex("org.eclipse.ui.workbench.lineAddedColor", "#00ff00"));
87+
88+
return html;
89+
}
90+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.snyk.eclipse.plugin.html;
2+
3+
import io.snyk.eclipse.plugin.domain.ProductConstants;
4+
5+
public class HtmlProviderFactory {
6+
7+
public static BaseHtmlProvider GetHtmlProvider(String product)
8+
{
9+
switch (product) {
10+
case ProductConstants.DISPLAYED_CODE_SECURITY:
11+
case ProductConstants.DISPLAYED_CODE_QUALITY:
12+
return CodeHtmlProvider.getInstance();
13+
case ProductConstants.DISPLAYED_OSS:
14+
return OssHtmlProvider.getInstance();
15+
case ProductConstants.DISPLAYED_IAC:
16+
return IacHtmlProvider.getInstance();
17+
}
18+
return null;
19+
}
20+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.snyk.eclipse.plugin.html;
2+
3+
public class IacHtmlProvider extends BaseHtmlProvider {
4+
private static IacHtmlProvider instance = new IacHtmlProvider();
5+
public static IacHtmlProvider getInstance() {
6+
if (instance == null) {
7+
synchronized (IacHtmlProvider.class) {
8+
if (instance == null) {
9+
instance = new IacHtmlProvider();
10+
}
11+
}
12+
}
13+
return instance;
14+
}
15+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.snyk.eclipse.plugin.html;
2+
3+
public class OssHtmlProvider extends BaseHtmlProvider {
4+
private static OssHtmlProvider instance = new OssHtmlProvider();
5+
public static OssHtmlProvider getInstance() {
6+
if (instance == null) {
7+
synchronized (OssHtmlProvider.class) {
8+
if (instance == null) {
9+
instance = new OssHtmlProvider();
10+
}
11+
}
12+
}
13+
return instance;
14+
}
15+
@Override
16+
public String replaceCssVariables(String html) {
17+
html = super.replaceCssVariables(html);
18+
html = html.replace("var(--container-background-color)", super.getColorAsHex("org.eclipse.ui.workbench.CODE_BACKGROUND_COLOR", "#F0F0F0"));
19+
20+
return html;
21+
}
22+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package io.snyk.eclipse.plugin.views.snyktoolview;
2+
3+
import java.nio.file.Paths;
4+
import java.util.concurrent.CompletableFuture;
5+
6+
import org.eclipse.core.runtime.Platform;
7+
import org.eclipse.jface.viewers.ISelectionChangedListener;
8+
import org.eclipse.jface.viewers.IStructuredSelection;
9+
import org.eclipse.jface.viewers.SelectionChangedEvent;
10+
import org.eclipse.jface.viewers.TreeNode;
11+
import org.eclipse.lsp4e.LSPEclipseUtils;
12+
import org.eclipse.lsp4j.Location;
13+
import org.eclipse.lsp4j.Position;
14+
import org.eclipse.lsp4j.Range;
15+
import org.eclipse.swt.SWT;
16+
import org.eclipse.swt.browser.Browser;
17+
import org.eclipse.swt.browser.BrowserFunction;
18+
import org.eclipse.swt.browser.LocationEvent;
19+
import org.eclipse.swt.browser.LocationListener;
20+
import org.eclipse.swt.program.Program;
21+
import org.eclipse.swt.widgets.Display;
22+
import org.osgi.framework.Bundle;
23+
24+
import io.snyk.eclipse.plugin.html.HtmlProviderFactory;
25+
import io.snyk.eclipse.plugin.utils.ResourceUtils;
26+
27+
public class BrowserHandler {
28+
private Browser browser;
29+
public BrowserHandler(Browser browser) {
30+
this.browser = browser;
31+
}
32+
33+
public void initialize() {
34+
new BrowserFunction(browser, "openInEditor") {
35+
@SuppressWarnings("restriction")
36+
@Override
37+
public Object function(Object[] arguments) {
38+
if (arguments.length != 5) {
39+
return null;
40+
}
41+
String filePath = (String) arguments[0];
42+
var fileUri = Paths.get(filePath).toUri().toASCIIString();
43+
int startLine = Integer.parseInt(arguments[1].toString());
44+
int endLine = Integer.parseInt(arguments[2].toString());
45+
int startCharacter = Integer.parseInt(arguments[3].toString());
46+
int endCharacter = Integer.parseInt(arguments[4].toString());
47+
48+
Display.getDefault().asyncExec(() -> {
49+
try {
50+
Position startPosition = new Position(startLine, startCharacter);
51+
Position endPosition = new Position(endLine, endCharacter);
52+
Range range = new Range(startPosition, endPosition);
53+
54+
var location = new Location(fileUri, range);
55+
LSPEclipseUtils.openInEditor(location);
56+
57+
} catch (Exception e) {
58+
e.printStackTrace();
59+
}
60+
});
61+
return null;
62+
}
63+
};
64+
65+
browser.addLocationListener(new LocationListener() {
66+
@Override
67+
public void changing(LocationEvent event) {
68+
String url = event.location;
69+
if(url.startsWith("http")) {
70+
event.doit = false;
71+
Program.launch(url);
72+
}
73+
}
74+
75+
@Override
76+
public void changed(LocationEvent event) {
77+
}
78+
});
79+
80+
initBrowserText();
81+
}
82+
83+
public void updateBrowserContent(String text) {
84+
String htmlContent = generateHtmlContent(text);
85+
browser.setText(htmlContent);
86+
}
87+
88+
public CompletableFuture<Void> updateBrowserContent(TreeNode node) {
89+
// Generate HTML content based on the selected node
90+
if (!(node instanceof IssueTreeNode)) return CompletableFuture.completedFuture(null);
91+
browser.setText("Loading...");
92+
93+
return CompletableFuture.supplyAsync(() -> {
94+
return generateHtmlContent(node);
95+
})
96+
.thenAccept(htmlContent -> {
97+
Display.getDefault().asyncExec(() -> {
98+
var product = ((ProductTreeNode) node.getParent().getParent()).getProduct();
99+
var htmlProvider = HtmlProviderFactory.GetHtmlProvider(product);
100+
var content = htmlProvider.replaceCssVariables(htmlContent);
101+
browser.setText(content);
102+
browser.execute(htmlProvider.getInitScript());
103+
});
104+
});
105+
106+
}
107+
108+
public String generateHtmlContent(TreeNode node) {
109+
if (node instanceof BaseTreeNode) {
110+
return ((BaseTreeNode) node).getDetails();
111+
}
112+
return "";
113+
}
114+
115+
public String generateHtmlContent(String text) {
116+
return "<html><body<p>" + text + "</p></body></html>";
117+
}
118+
119+
public void initBrowserText() {
120+
String snykWarningText = Platform.getResourceString(Platform.getBundle("io.snyk.eclipse.plugin"),
121+
"%snyk.trust.dialog.warning.text");
122+
123+
Bundle bundle = Platform.getBundle("io.snyk.eclipse.plugin");
124+
String base64Image = ResourceUtils.getBase64Image(bundle, "logo_snyk.png");
125+
126+
browser.setText("<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> "
127+
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> "
128+
+ "<title>Snyk for Eclipse</title> <style> .container { display: flex; align-items: center; } .logo { margin-right: 20px; } "
129+
+ "</style> </head> <body> <div class=\"container\"> " + "<img src='data:image/png;base64,"
130+
+ base64Image + "' alt='Snyk Logo'>" + "<div> <p><strong>Welcome to Snyk for Eclipse</strong></p>"
131+
+ " <p>\n" + snykWarningText + "</body>\n" + "</html>");
132+
}
133+
}

0 commit comments

Comments
 (0)