Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: style description panel #220

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package io.snyk.eclipse.plugin.html;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;

public class BaseHtmlProvider {
private final Random random = new Random();
private final Map<String, String> colorCache = new HashMap<>();
private String nonce = "";

public String getCss() {
return "";
}

public String getJs() {
return "";
}

public String getInitScript() {
return "";
}

public String getNonce() {
if(!nonce.isEmpty()) {
return nonce;
}
String allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder nonceBuilder = new StringBuilder(32);
for (int i = 0; i < 32; i++) {
nonceBuilder.append(allowedChars.charAt(random.nextInt(allowedChars.length())));
}
nonce = nonceBuilder.toString();
return nonce;
}

public String replaceCssVariables(String html) {
// Build the CSS with the nonce
String nonce = getNonce();
String css = "<style nonce=\"" + nonce + "\">" + getCss() + "</style>";
html = html.replace("${ideStyle}", css);
html = html.replace("<style nonce=\"ideNonce\" data-ide-style></style>", css);
html = html.replace("var(--default-font)", " ui-sans-serif, \"SF Pro Text\", \"Segoe UI\", \"Ubuntu\", Tahoma, Geneva, Verdana, sans-serif;");


// Replace CSS variables with actual color values
html = html.replace("var(--text-color)", getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_TEXT_COLOR", "#000000"));
html = html.replace("var(--background-color)", getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_BG_START", "#FFFFFF"));
html = html.replace("var(--border-color)", getColorAsHex( "org.eclipse.ui.workbench.ACTIVE_TAB_BORDER_COLOR", "#CCCCCC"));
html = html.replace("var(--link-color)", getColorAsHex("org.eclipse.ui.workbench.HYPERLINK_COLOR", "#0000FF"));
html = html.replace("var(--horizontal-border-color)", getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_HIGHLIGHT_BORDER_COLOR", "#CCCCCC"));
html = html.replace("var(--code-background-color)", getColorAsHex("org.eclipse.ui.workbench.CODE_BACKGROUND_COLOR", "#F0F0F0"));

html = html.replace("${headerEnd}", "");
html = html.replace("${nonce}", nonce);
html = html.replace("ideNonce", nonce);
html = html.replace("${ideScript}", "");

return html;
}

public String getColorAsHex(String colorKey, String defaultColor) {
return colorCache.computeIfAbsent(colorKey, key -> {
ColorRegistry colorRegistry = getColorRegistry();
Color color = colorRegistry.get(colorKey);
if (color == null) {
return defaultColor;
} else {
RGB rgb = color.getRGB();
return String.format("#%02x%02x%02x", rgb.red, rgb.green, rgb.blue);
}
});
}

private ColorRegistry colorRegistry;
private ColorRegistry getColorRegistry() {
if(colorRegistry != null) {
return colorRegistry;
}
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
ITheme currentTheme = themeManager.getCurrentTheme();
colorRegistry = currentTheme.getColorRegistry();
return colorRegistry;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.snyk.eclipse.plugin.html;

import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;

public class CodeHtmlProvider extends BaseHtmlProvider {
private static CodeHtmlProvider instance = new CodeHtmlProvider();

public static CodeHtmlProvider getInstance() {
if (instance == null) {
synchronized (CodeHtmlProvider.class) {
if (instance == null) {
instance = new CodeHtmlProvider();
}
}
}
return instance;
}

@Override
public String getInitScript() {
String themeScript = getThemeScript();
String initScript = super.getInitScript();
return initScript + "\n" + """
function navigateToIssue(e, target) {
e.preventDefault();
var filePath = target.getAttribute('file-path');
var startLine = target.getAttribute('start-line');
var endLine = target.getAttribute('end-line');
var startCharacter = target.getAttribute('start-character');
var endCharacter = target.getAttribute('end-character');
window.openInEditor(filePath, startLine, endLine, startCharacter, endCharacter);
}
var navigatableLines = document.getElementsByClassName('data-flow-clickable-row');
for(var i = 0; i < navigatableLines.length; i++) {
navigatableLines[i].onclick = function(e) {
navigateToIssue(e, this);
return false;
};
}
if(document.getElementById('position-line')) {
document.getElementById('position-line').onclick = function(e) {
var target = navigatableLines[0];
if(target) {
navigateToIssue(e, target);
}
}
}
// Disable AIfix
if(document.getElementById('ai-fix-wrapper') && document.getElementById('no-ai-fix-wrapper')){
document.getElementById('ai-fix-wrapper').className = 'hidden';
document.getElementById('no-ai-fix-wrapper').className = '';
}
""" + themeScript;
}

private ITheme currentTheme;
private ITheme getCurrentTheme() {
if(currentTheme != null) {
return currentTheme;
}
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
currentTheme = themeManager.getCurrentTheme();
return currentTheme;
}
private String getThemeScript() {
ITheme currentTheme = getCurrentTheme();
String themeId = currentTheme.getId().toLowerCase();

boolean isDarkTheme = themeId.contains("dark");
boolean isHighContrast = themeId.contains("highcontrast") || themeId.contains("high-contrast");

String themeScript = "var isDarkTheme = " + isDarkTheme + ";\n" +
"var isHighContrast = " + isHighContrast + ";\n" +
"document.body.classList.add(isHighContrast ? 'high-contrast' : (isDarkTheme ? 'dark' : 'light'));";
return themeScript;
}

@Override
public String replaceCssVariables(String html) {
html = super.replaceCssVariables(html);

// Replace CSS variables with actual color values
html = html.replace("var(--example-line-removed-color)", super.getColorAsHex("org.eclipse.ui.workbench.lineRemovedColor", "#ff0000"));
html = html.replace("var(--example-line-added-color)", super.getColorAsHex("org.eclipse.ui.workbench.lineAddedColor", "#00ff00"));

return html;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.snyk.eclipse.plugin.html;

import io.snyk.eclipse.plugin.domain.ProductConstants;

public class HtmlProviderFactory {

public static BaseHtmlProvider GetHtmlProvider(String product)
{
switch (product) {
case ProductConstants.DISPLAYED_CODE_SECURITY:
case ProductConstants.DISPLAYED_CODE_QUALITY:
return CodeHtmlProvider.getInstance();
case ProductConstants.DISPLAYED_OSS:
return OssHtmlProvider.getInstance();
case ProductConstants.DISPLAYED_IAC:
return IacHtmlProvider.getInstance();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.snyk.eclipse.plugin.html;

public class IacHtmlProvider extends BaseHtmlProvider {
private static IacHtmlProvider instance = new IacHtmlProvider();
public static IacHtmlProvider getInstance() {
if (instance == null) {
synchronized (IacHtmlProvider.class) {
if (instance == null) {
instance = new IacHtmlProvider();
}
}
}
return instance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.snyk.eclipse.plugin.html;

public class OssHtmlProvider extends BaseHtmlProvider {
private static OssHtmlProvider instance = new OssHtmlProvider();
public static OssHtmlProvider getInstance() {
if (instance == null) {
synchronized (OssHtmlProvider.class) {
if (instance == null) {
instance = new OssHtmlProvider();
}
}
}
return instance;
}
@Override
public String replaceCssVariables(String html) {
html = super.replaceCssVariables(html);
html = html.replace("var(--container-background-color)", super.getColorAsHex("org.eclipse.ui.workbench.CODE_BACKGROUND_COLOR", "#F0F0F0"));

return html;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package io.snyk.eclipse.plugin.views.snyktoolview;

import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;

import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeNode;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.BrowserFunction;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.Bundle;

import io.snyk.eclipse.plugin.html.HtmlProviderFactory;
import io.snyk.eclipse.plugin.utils.ResourceUtils;

public class BrowserHandler {
private Browser browser;
public BrowserHandler(Browser browser) {
this.browser = browser;
}

public void initialize() {
new BrowserFunction(browser, "openInEditor") {
@SuppressWarnings("restriction")
@Override
public Object function(Object[] arguments) {
if (arguments.length != 5) {
return null;
}
String filePath = (String) arguments[0];
var fileUri = Paths.get(filePath).toUri().toASCIIString();
int startLine = Integer.parseInt(arguments[1].toString());
int endLine = Integer.parseInt(arguments[2].toString());
int startCharacter = Integer.parseInt(arguments[3].toString());
int endCharacter = Integer.parseInt(arguments[4].toString());

Display.getDefault().asyncExec(() -> {
try {
Position startPosition = new Position(startLine, startCharacter);
Position endPosition = new Position(endLine, endCharacter);
Range range = new Range(startPosition, endPosition);

var location = new Location(fileUri, range);
LSPEclipseUtils.openInEditor(location);

} catch (Exception e) {
e.printStackTrace();
}
});
return null;
}
};

browser.addLocationListener(new LocationListener() {
@Override
public void changing(LocationEvent event) {
String url = event.location;
if(url.startsWith("http")) {
event.doit = false;
Program.launch(url);
}
}

@Override
public void changed(LocationEvent event) {
}
});

initBrowserText();
}

public void updateBrowserContent(String text) {
String htmlContent = generateHtmlContent(text);
browser.setText(htmlContent);
}

public CompletableFuture<Void> updateBrowserContent(TreeNode node) {
// Generate HTML content based on the selected node
if (!(node instanceof IssueTreeNode)) return CompletableFuture.completedFuture(null);
browser.setText("Loading...");

return CompletableFuture.supplyAsync(() -> {
return generateHtmlContent(node);
})
.thenAccept(htmlContent -> {
Display.getDefault().asyncExec(() -> {
var product = ((ProductTreeNode) node.getParent().getParent()).getProduct();
var htmlProvider = HtmlProviderFactory.GetHtmlProvider(product);
var content = htmlProvider.replaceCssVariables(htmlContent);
browser.setText(content);
browser.execute(htmlProvider.getInitScript());
});
});

}

public String generateHtmlContent(TreeNode node) {
if (node instanceof BaseTreeNode) {
return ((BaseTreeNode) node).getDetails();
}
return "";
}

public String generateHtmlContent(String text) {
return "<html><body<p>" + text + "</p></body></html>";
}

public void initBrowserText() {
String snykWarningText = Platform.getResourceString(Platform.getBundle("io.snyk.eclipse.plugin"),
"%snyk.trust.dialog.warning.text");

Bundle bundle = Platform.getBundle("io.snyk.eclipse.plugin");
String base64Image = ResourceUtils.getBase64Image(bundle, "logo_snyk.png");

browser.setText("<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> "
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> "
+ "<title>Snyk for Eclipse</title> <style> .container { display: flex; align-items: center; } .logo { margin-right: 20px; } "
+ "</style> </head> <body> <div class=\"container\"> " + "<img src='data:image/png;base64,"
+ base64Image + "' alt='Snyk Logo'>" + "<div> <p><strong>Welcome to Snyk for Eclipse</strong></p>"
+ " <p>\n" + snykWarningText + "</body>\n" + "</html>");
}
}
Loading