Skip to content

Commit 269c2ae

Browse files
committed
feat: select treenode on snyk showdocument WIP
1 parent 25d529a commit 269c2ae

File tree

4 files changed

+132
-10
lines changed

4 files changed

+132
-10
lines changed

plugin/src/main/java/io/snyk/eclipse/plugin/views/snyktoolview/ISnykToolView.java

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import org.eclipse.jface.viewers.TreeViewer;
44

5+
import io.snyk.languageserver.protocolextension.messageObjects.scanResults.Issue;
6+
57
/**
68
* This interface captures the externally used methods with the tool window.
79
* Having it, should allow for easier testing of the business logic apart from
@@ -125,4 +127,6 @@ static String getPlural(long count) {
125127
* @return
126128
*/
127129
abstract void disableDelta();
130+
131+
abstract void selectTreeNode(Issue issue, String product);
128132
}

plugin/src/main/java/io/snyk/eclipse/plugin/views/snyktoolview/SnykToolView.java

+29
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.eclipse.jface.viewers.ISelectionChangedListener;
2222
import org.eclipse.jface.viewers.IStructuredSelection;
2323
import org.eclipse.jface.viewers.SelectionChangedEvent;
24+
import org.eclipse.jface.viewers.StructuredSelection;
2425
import org.eclipse.jface.viewers.TreeNode;
2526
import org.eclipse.jface.viewers.TreeViewer;
2627
import org.eclipse.lsp4e.LSPEclipseUtils;
@@ -42,6 +43,7 @@
4243
import org.eclipse.ui.menus.CommandContributionItemParameter;
4344
import org.eclipse.ui.part.ViewPart;
4445

46+
import io.snyk.eclipse.plugin.domain.ProductConstants;
4547
import io.snyk.eclipse.plugin.preferences.Preferences;
4648
import io.snyk.eclipse.plugin.properties.FolderConfigs;
4749
import io.snyk.eclipse.plugin.utils.ResourceUtils;
@@ -477,4 +479,31 @@ protected void outputCommandResult(Object result) {
477479
}
478480
}
479481

482+
@Override
483+
public void selectTreeNode(Issue issue, String product) {
484+
ProductTreeNode productNode = getProductNode(ProductConstants.DISPLAYED_CODE_SECURITY, issue.filePath());
485+
drillDown((TreeNode) productNode, issue);
486+
}
487+
488+
private void drillDown(TreeNode currentParent, Issue issue) {
489+
for (Object child : currentParent.getChildren()) {
490+
TreeNode childNode = (TreeNode) child;
491+
492+
if (childNode instanceof IssueTreeNode && ((IssueTreeNode) childNode).getIssue().id().equals(issue.id())) {
493+
updateSelection((IssueTreeNode) childNode);
494+
return; // Exit the function as we've found a match
495+
}
496+
497+
if (childNode.getChildren() != null && childNode.getChildren().length != 0) {
498+
drillDown(childNode, issue);
499+
}
500+
}
501+
}
502+
503+
private void updateSelection(IssueTreeNode issueTreeNode) {
504+
Display.getDefault().asyncExec(() -> {
505+
IStructuredSelection selection = new StructuredSelection(issueTreeNode);
506+
treeViewer.setSelection(selection, true);
507+
});
508+
}
480509
}

plugin/src/main/java/io/snyk/languageserver/LsConstants.java

+1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ private LsConstants() {
2626
public static final String SNYK_PUBLISH_DIAGNOSTICS_316 = "$/snyk.publishDiagnostics316";
2727
public static final String SNYK_FOLDER_CONFIG = "$/snyk.folderConfigs";
2828
public static final String SNYK_SCAN_SUMMARY = "$/snyk.scanSummary";
29+
public static final String SNYK_SHOW_DOCUMENT = "$/snyk.showDocument";
2930
}

plugin/src/main/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClient.java

+98-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.snyk.languageserver.protocolextension;
22

3+
import static io.snyk.eclipse.plugin.domain.ProductConstants.DIAGNOSTIC_SOURCE_SNYK_CODE;
34
import static io.snyk.eclipse.plugin.domain.ProductConstants.DISPLAYED_CODE_QUALITY;
45
import static io.snyk.eclipse.plugin.domain.ProductConstants.DISPLAYED_CODE_SECURITY;
56
import static io.snyk.eclipse.plugin.domain.ProductConstants.LSP_SOURCE_TO_SCAN_PARAMS;
@@ -22,15 +23,20 @@
2223
import static io.snyk.eclipse.plugin.views.snyktoolview.ISnykToolView.getPlural;
2324

2425
import java.io.File;
26+
import java.io.UnsupportedEncodingException;
2527
import java.net.URI;
28+
import java.net.URISyntaxException;
29+
import java.net.URLDecoder;
2630
import java.nio.file.InvalidPathException;
2731
import java.nio.file.Path;
2832
import java.nio.file.Paths;
2933
import java.util.ArrayList;
3034
import java.util.Arrays;
3135
import java.util.Collections;
36+
import java.util.HashMap;
3237
import java.util.HashSet;
3338
import java.util.List;
39+
import java.util.Map;
3440
import java.util.Set;
3541
import java.util.concurrent.CompletableFuture;
3642
import java.util.concurrent.ExecutionException;
@@ -47,6 +53,8 @@
4753
import org.eclipse.lsp4e.LSPEclipseUtils;
4854
import org.eclipse.lsp4e.LanguageClientImpl;
4955
import org.eclipse.lsp4j.ProgressParams;
56+
import org.eclipse.lsp4j.ShowDocumentParams;
57+
import org.eclipse.lsp4j.ShowDocumentResult;
5058
import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
5159
import org.eclipse.lsp4j.WorkDoneProgressKind;
5260
import org.eclipse.lsp4j.WorkDoneProgressNotification;
@@ -116,8 +124,8 @@ public class SnykExtendedLanguageClient extends LanguageClientImpl {
116124

117125
public SnykExtendedLanguageClient() {
118126
super();
119-
//TODO, fix this; Identifies a possible unsafe usage of a static field.
120-
instance = this; //NOPMD
127+
// TODO, fix this; Identifies a possible unsafe usage of a static field.
128+
instance = this; // NOPMD
121129
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
122130
registerPluginInstalledEventTask();
123131
registerRefreshFeatureFlagsTask();
@@ -384,16 +392,96 @@ public void snykScan(SnykScanParam param) {
384392
this.toolView.refreshBrowser(param.getStatus());
385393
}
386394

395+
@JsonNotification(value = LsConstants.SNYK_FOLDER_CONFIG)
396+
public void folderConfig(FolderConfigsParam folderConfigParam) {
397+
List<FolderConfig> folderConfigs = folderConfigParam != null ? folderConfigParam.getFolderConfigs() : List.of();
398+
CompletableFuture.runAsync(() -> FolderConfigs.getInstance().addAll(folderConfigs));
399+
}
400+
387401
@JsonNotification(value = LsConstants.SNYK_SCAN_SUMMARY)
388402
public void updateSummaryPanel(SummaryPanelParams summary) {
389403
openToolView();
390404
this.toolView.updateSummary(summary.getSummary());
391405
}
392406

393-
@JsonNotification(value = LsConstants.SNYK_FOLDER_CONFIG)
394-
public void folderConfig(FolderConfigsParam folderConfigParam) {
395-
List<FolderConfig> folderConfigs = folderConfigParam != null ? folderConfigParam.getFolderConfigs() : List.of();
396-
CompletableFuture.runAsync(() -> FolderConfigs.getInstance().addAll(folderConfigs));
407+
@Override
408+
public CompletableFuture<ShowDocumentResult> showDocument(ShowDocumentParams params) {
409+
URI uri;
410+
try {
411+
uri = new URI(params.getUri());
412+
} catch (URISyntaxException e) {
413+
SnykLogger.logError(e);
414+
return null;
415+
}
416+
417+
String scheme = uri.getScheme();
418+
String product = getDecodedParam(uri, "product");
419+
String action = getDecodedParam(uri, "action");
420+
String issueId = getDecodedParam(uri, "issueId");
421+
422+
if (!"snyk".equals(scheme)) {
423+
SnykLogger.logInfo(String.format("Invalid URI: expected 'snyk' scheme but got %s", scheme));
424+
} else if (!DIAGNOSTIC_SOURCE_SNYK_CODE.equals(product)) {
425+
SnykLogger.logInfo(String.format("Invalid URI: expected '{}' product but got %s",
426+
DIAGNOSTIC_SOURCE_SNYK_CODE, product));
427+
} else if (!"showInDetailPanel".equals(action)) {
428+
SnykLogger.logInfo(String.format("Invalid URI: expected 'showInDetailPanel' action but got %s", action));
429+
} else if (issueId.isEmpty()) {
430+
SnykLogger.logInfo(String.format("Invalid URI: 'issueId' empty"));
431+
}
432+
433+
if (scheme.equals("snyk") && product.equals(DIAGNOSTIC_SOURCE_SNYK_CODE)
434+
&& action.equals("showInDetailPanel")) {
435+
return CompletableFuture.supplyAsync(() -> {
436+
437+
Issue issue = getIssueFromCache(uri);
438+
this.toolView.selectTreeNode(issue, product);
439+
return new ShowDocumentResult(true);
440+
});
441+
} else {
442+
SnykLogger.logInfo(String.format("Invalid URI: scheme=%s, product=%s, action=%s", scheme, product, action));
443+
return super.showDocument(params);
444+
}
445+
}
446+
447+
public Issue getIssueFromCache(URI uri) {
448+
String filePath = uri.getPath();
449+
String issueId = getDecodedParam(uri, "issueId");
450+
451+
SnykIssueCache issueCache = getIssueCache(filePath);
452+
return issueCache.getCodeSecurityIssuesForPath(filePath).stream().filter(i -> i.id().equals(issueId))
453+
.findFirst().orElse(null);
454+
}
455+
456+
public String getDecodedParam(URI uri, String paramName) {
457+
Map<String, String> paramMap = parseQueryString(uri.getQuery());
458+
459+
try {
460+
return URLDecoder.decode(paramMap.get(paramName), "UTF-8");
461+
} catch (UnsupportedEncodingException e) {
462+
SnykLogger.logError(e);
463+
}
464+
return null;
465+
}
466+
467+
private static Map<String, String> parseQueryString(String queryString) {
468+
Map<String, String> paramMap = new HashMap<>();
469+
470+
for (String param : queryString.split("&")) {
471+
if (!param.isEmpty()) {
472+
String[] keyValue = param.split("=");
473+
474+
if (keyValue.length == 2) {
475+
try {
476+
paramMap.put(keyValue[0], URLDecoder.decode(keyValue[1], "UTF-8"));
477+
} catch (UnsupportedEncodingException e) {
478+
SnykLogger.logError(e);
479+
}
480+
}
481+
}
482+
}
483+
484+
return paramMap;
397485
}
398486

399487
private void openToolView() {
@@ -473,7 +561,7 @@ public String getCountsSuffix(ProductTreeNode productTreeNode, SnykIssueCache is
473561
high, medium, low);
474562
}
475563

476-
private SnykIssueCache getIssueCache(String filePath) {
564+
public SnykIssueCache getIssueCache(String filePath) {
477565
var issueCache = IssueCacheHolder.getInstance().getCacheInstance(Paths.get(filePath));
478566
if (issueCache == null) {
479567
throw new IllegalArgumentException("No issue cache for param possible");
@@ -548,11 +636,11 @@ private void populateFileAndIssueNodes(ProductTreeNode productTreeNode, SnykIssu
548636

549637
if (issuesList.isEmpty())
550638
continue;
551-
552-
FileTreeNode fileNode = new FileTreeNode(fileName); //NOPMD
639+
640+
FileTreeNode fileNode = new FileTreeNode(fileName); // NOPMD
553641
toolView.addFileNode(productTreeNode, fileNode);
554642
for (Issue issue : issuesList) {
555-
toolView.addIssueNode(fileNode, new IssueTreeNode(issue)); //NOPMD
643+
toolView.addIssueNode(fileNode, new IssueTreeNode(issue)); // NOPMD
556644
}
557645
}
558646
}

0 commit comments

Comments
 (0)