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 5 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,85 @@
package io.snyk.eclipse.plugin.html;

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 {
public String getCss() {
return "";
}

public String getJs() {
return "";
}

public String getInitScript() {
return "";
}

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

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"));

// Update the HTML head
ShawkyZ marked this conversation as resolved.
Show resolved Hide resolved
String ideHeaders = """
<head>
<meta http-equiv='Content-Type' content='text/html; charset=unicode' />
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
""";
html = html.replace("<head>", ideHeaders);
html = html.replace("${headerEnd}", "");

// Replace nonce placeholders
html = html.replace("${nonce}", nonce);
html = html.replace("ideNonce", nonce);
html = html.replace("${ideScript}", "");

return html;
}

public String getColorAsHex(String colorKey, String defaultColor) {
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 getColorRegistry() {
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
ITheme currentTheme = themeManager.getCurrentTheme();
return currentTheme.getColorRegistry();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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 Autofix and ignores
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 = '';
}
if(document.getElementsByClassName('ignore-action-container') && document.getElementsByClassName('ignore-action-container')[0]){
ShawkyZ marked this conversation as resolved.
Show resolved Hide resolved
document.getElementsByClassName('ignore-action-container')[0].className = 'hidden';
}
""" + themeScript;
}

private String getThemeScript() {
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
ITheme currentTheme = themeManager.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
Expand Up @@ -4,10 +4,6 @@
package io.snyk.eclipse.plugin.views.snyktoolview;

import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.IMenuManager;
Expand All @@ -18,12 +14,19 @@
import org.eclipse.jface.viewers.TreeNode;
import org.eclipse.jface.viewers.TreeViewer;
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.custom.SashForm;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
Expand All @@ -35,6 +38,7 @@
import org.eclipse.ui.part.ViewPart;
import org.osgi.framework.Bundle;

import io.snyk.eclipse.plugin.html.HtmlProviderFactory;
import io.snyk.eclipse.plugin.properties.preferences.Preferences;
import io.snyk.eclipse.plugin.utils.ResourceUtils;
import io.snyk.eclipse.plugin.views.snyktoolview.providers.TreeContentProvider;
Expand Down Expand Up @@ -86,6 +90,53 @@ public void createPartControl(Composite parent) {
// Create Browser
// SWT.EDGE will be ignored if OS not windows and will be set to SWT.NONE.
browser = new Browser(sashForm, SWT.EDGE);
// Register the Java function as an anonymous class

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();

// Set sash weights
Expand Down Expand Up @@ -121,7 +172,13 @@ private void registerTreeContextMeny(Composite parent) {
private void updateBrowserContent(TreeNode node) {
// Generate HTML content based on the selected node
String htmlContent = generateHtmlContent(node);
browser.setText(htmlContent);
if (node instanceof IssueTreeNode) {
var product = ((ProductTreeNode) node.getParent().getParent()).getProduct();
var htmlProvider = HtmlProviderFactory.GetHtmlProvider(product);
htmlContent = htmlProvider.replaceCssVariables(htmlContent);
browser.setText(htmlContent);
browser.execute(htmlProvider.getInitScript());
}
}

private void updateBrowserContent(String text) {
Expand Down Expand Up @@ -190,34 +247,14 @@ public void addFileNode(ProductTreeNode parent, FileTreeNode toBeAdded) {

@Override
public void addInfoNode(ProductTreeNode parent, InfoTreeNode toBeAdded) {
List<BaseTreeNode> list = new ArrayList<>();
var children = parent.getChildren();
if (children != null) {
list = Arrays.stream(children).map(it -> (BaseTreeNode) it).collect(Collectors.toList());
}

toBeAdded.setParent(parent);
int insertIndex = GetLastInfoNodeIndex(list);
list.add(insertIndex, toBeAdded);
parent.setChildren(list.toArray(new BaseTreeNode[0]));

parent.addChild(toBeAdded);

Display.getDefault().asyncExec(() -> {
this.treeViewer.refresh(parent, true);
});
}

private int GetLastInfoNodeIndex(List<BaseTreeNode> list) {
int insertIndex = 0;
for (int i = 0; i < list.size(); i++) {
if (list.get(i) instanceof InfoTreeNode) {
insertIndex += 1;
} else {
break;
}
}
return insertIndex;
}

@Override
public ProductTreeNode getProductNode(String product, String folderPath) {
if (product == null || folderPath == null) {
Expand Down Expand Up @@ -327,4 +364,4 @@ private void addCommandIfNotPresent(IMenuManager menu, String commandId) {
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public long getTotalCount(String product) {
return getCacheByDisplayProduct(product).values().stream().flatMap(Collection::stream).count();
}

private Map<String, Collection<Issue>> getCacheByDisplayProduct(String displayProduct) {
public Map<String, Collection<Issue>> getCacheByDisplayProduct(String displayProduct) {
switch (displayProduct) {
case ProductConstants.DISPLAYED_OSS:
return ossIssues;
Expand Down
Loading