-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: snyk code ls common functionality [IDE-283] (#528)
* refactor: extract common SnykCode annotator functionality * refactor: extract SnykCode panels from Suggestion Panel * chore: CHANGELOG
- Loading branch information
1 parent
ab094ed
commit 1915ccd
Showing
8 changed files
with
469 additions
and
395 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 153 additions & 0 deletions
153
src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SnykCodeDataflowPanel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package io.snyk.plugin.ui.toolwindow.panels | ||
|
||
import com.intellij.openapi.project.Project | ||
import com.intellij.ui.HyperlinkLabel | ||
import com.intellij.uiDesigner.core.GridConstraints | ||
import com.intellij.uiDesigner.core.GridLayoutManager | ||
import com.intellij.util.ui.JBUI | ||
import com.intellij.util.ui.UIUtil | ||
import io.snyk.plugin.getDocument | ||
import io.snyk.plugin.navigateToSource | ||
import io.snyk.plugin.toVirtualFile | ||
import io.snyk.plugin.ui.baseGridConstraintsAnchorWest | ||
import snyk.common.lsp.DataFlow | ||
import snyk.common.lsp.IssueData | ||
import java.awt.Font | ||
import javax.swing.JPanel | ||
import javax.swing.JTextArea | ||
import javax.swing.event.HyperlinkEvent | ||
import kotlin.math.max | ||
|
||
class SnykCodeDataflowPanel(private val project: Project, codeIssueData: IssueData): JPanel() { | ||
init { | ||
val dataFlow = codeIssueData.dataFlow | ||
|
||
this.layout = GridLayoutManager(1 + dataFlow.size, 1, JBUI.emptyInsets(), -1, 5) | ||
|
||
this.add( | ||
defaultFontLabel("Data Flow - ${dataFlow.size} step${if (dataFlow.size > 1) "s" else ""}", true), | ||
baseGridConstraintsAnchorWest(0) | ||
) | ||
|
||
this.add( | ||
stepsPanel(dataFlow), | ||
baseGridConstraintsAnchorWest(1) | ||
) | ||
} | ||
|
||
|
||
private fun stepsPanel(dataflow: List<DataFlow>): JPanel { | ||
val panel = JPanel() | ||
panel.layout = GridLayoutManager(dataflow.size, 1, JBUI.emptyInsets(), 0, 0) | ||
panel.background = UIUtil.getTextFieldBackground() | ||
|
||
val maxFilenameLength = dataflow.asSequence() | ||
.filter { it.filePath.isNotEmpty() } | ||
.map { it.filePath.substringAfterLast('/', "").length } | ||
.maxOrNull() ?: 0 | ||
|
||
val allStepPanels = mutableListOf<JPanel>() | ||
dataflow.forEach { flow -> | ||
val stepPanel = stepPanel( | ||
index = flow.position, | ||
flow = flow, | ||
maxFilenameLength = max(flow.filePath.toVirtualFile().name.length, maxFilenameLength), | ||
allStepPanels = allStepPanels | ||
) | ||
|
||
panel.add( | ||
stepPanel, | ||
baseGridConstraintsAnchorWest( | ||
row = flow.position, | ||
fill = GridConstraints.FILL_BOTH, | ||
indent = 0 | ||
) | ||
) | ||
allStepPanels.add(stepPanel) | ||
} | ||
|
||
return panel | ||
} | ||
|
||
private fun stepPanel( | ||
index: Int, | ||
flow: DataFlow, | ||
maxFilenameLength: Int, | ||
allStepPanels: MutableList<JPanel> | ||
): JPanel { | ||
val stepPanel = JPanel() | ||
stepPanel.layout = GridLayoutManager(1, 3, JBUI.insetsBottom(4), 0, 0) | ||
stepPanel.background = UIUtil.getTextFieldBackground() | ||
|
||
val paddedStepNumber = (index + 1).toString().padStart(2, ' ') | ||
|
||
val virtualFile = flow.filePath.toVirtualFile() | ||
val fileName = virtualFile.name | ||
|
||
val lineNumber = flow.flowRange.start.line + 1 | ||
val positionLinkText = "$fileName:$lineNumber".padEnd(maxFilenameLength + 5, ' ') | ||
|
||
val positionLabel = linkLabel( | ||
beforeLinkText = "$paddedStepNumber ", | ||
linkText = positionLinkText, | ||
afterLinkText = " |", | ||
toolTipText = "Click to show in the Editor", | ||
customFont = JTextArea().font | ||
) { | ||
if (!virtualFile.isValid) return@linkLabel | ||
|
||
val document = virtualFile.getDocument() | ||
val startLineStartOffset = document?.getLineStartOffset(flow.flowRange.start.line) ?: 0 | ||
val startOffset = startLineStartOffset + (flow.flowRange.start.character) | ||
val endLineStartOffset = document?.getLineStartOffset(flow.flowRange.end.line) ?: 0 | ||
val endOffset = endLineStartOffset + flow.flowRange.end.character - 1 | ||
|
||
navigateToSource(this.project, virtualFile, startOffset, endOffset) | ||
|
||
allStepPanels.forEach { | ||
it.background = UIUtil.getTextFieldBackground() | ||
} | ||
stepPanel.background = UIUtil.getTableSelectionBackground(false) | ||
} | ||
stepPanel.add(positionLabel, baseGridConstraintsAnchorWest(0, indent = 1)) | ||
|
||
val codeLine = codeLine(flow.content.trimStart()) | ||
codeLine.isOpaque = false | ||
stepPanel.add( | ||
codeLine, | ||
baseGridConstraintsAnchorWest( | ||
row = 0, | ||
// is needed to avoid center alignment when outer panel is filling horizontal space | ||
hSizePolicy = GridConstraints.SIZEPOLICY_CAN_GROW, | ||
column = 1 | ||
) | ||
) | ||
|
||
return stepPanel | ||
} | ||
|
||
private fun linkLabel( | ||
beforeLinkText: String = "", | ||
linkText: String, | ||
afterLinkText: String = "", | ||
toolTipText: String, | ||
customFont: Font? = null, | ||
onClick: (HyperlinkEvent) -> Unit | ||
): HyperlinkLabel { | ||
return HyperlinkLabel().apply { | ||
this.setTextWithHyperlink("$beforeLinkText<hyperlink>$linkText</hyperlink>$afterLinkText") | ||
this.toolTipText = toolTipText | ||
this.font = io.snyk.plugin.ui.getFont(-1, 14, customFont ?: font) | ||
addHyperlinkListener { | ||
onClick.invoke(it) | ||
} | ||
} | ||
} | ||
|
||
private fun codeLine(content: String): JTextArea { | ||
val component = JTextArea(content) | ||
component.font = io.snyk.plugin.ui.getFont(-1, 14, component.font) | ||
component.isEditable = false | ||
return component | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SnykCodeExampleFixesPanel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package io.snyk.plugin.ui.toolwindow.panels | ||
|
||
import com.intellij.icons.AllIcons | ||
import com.intellij.ui.ScrollPaneFactory | ||
import com.intellij.ui.components.JBTabbedPane | ||
import com.intellij.uiDesigner.core.GridConstraints | ||
import com.intellij.uiDesigner.core.GridLayoutManager | ||
import com.intellij.util.ui.JBInsets | ||
import com.intellij.util.ui.JBUI | ||
import com.intellij.util.ui.UIUtil | ||
import io.snyk.plugin.ui.baseGridConstraintsAnchorWest | ||
import io.snyk.plugin.ui.panelGridConstraints | ||
import snyk.common.lsp.IssueData | ||
import java.awt.Color | ||
import javax.swing.JComponent | ||
import javax.swing.JPanel | ||
import javax.swing.JTextArea | ||
import javax.swing.ScrollPaneConstants | ||
|
||
class SnykCodeExampleFixesPanel(codeIssueData: IssueData): JPanel() { | ||
init { | ||
val fixes = codeIssueData.exampleCommitFixes | ||
val examplesCount = fixes.size.coerceAtMost(3) | ||
|
||
this.layout = GridLayoutManager(3, 1, JBUI.emptyInsets(), -1, 5) | ||
|
||
this.add( | ||
defaultFontLabel("External example fixes", true), | ||
baseGridConstraintsAnchorWest(0) | ||
) | ||
|
||
val labelText = | ||
if (examplesCount == 0) { | ||
"No example fixes available." | ||
} else { | ||
"This issue was fixed by ${codeIssueData.repoDatasetSize} projects. Here are $examplesCount example fixes." | ||
} | ||
this.add( | ||
defaultFontLabel(labelText), | ||
baseGridConstraintsAnchorWest(1) | ||
) | ||
|
||
if (examplesCount != 0) { | ||
val tabbedPane = JBTabbedPane() | ||
// tabbedPane.tabLayoutPolicy = JTabbedPane.SCROLL_TAB_LAYOUT // tabs in one row | ||
tabbedPane.tabComponentInsets = JBInsets.create(0, 0) // no inner borders for tab content | ||
tabbedPane.font = io.snyk.plugin.ui.getFont(-1, 14, tabbedPane.font) | ||
|
||
val tabbedPanel = JPanel() | ||
tabbedPanel.layout = GridLayoutManager(1, 1, JBUI.insetsTop(10), -1, -1) | ||
tabbedPanel.add(tabbedPane, panelGridConstraints(0)) | ||
|
||
this.add(tabbedPanel, panelGridConstraints(2, indent = 1)) | ||
|
||
val maxRowCount = fixes.take(examplesCount).maxOfOrNull { it.lines.size } ?: 0 | ||
fixes.take(examplesCount).forEach { exampleCommitFix -> | ||
val shortURL = exampleCommitFix.commitURL | ||
.removePrefix("https://") | ||
.replace(Regex("/commit/.*"), "") | ||
|
||
val tabTitle = shortURL.removePrefix("github.com/").let { | ||
if (it.length > 50) it.take(50) + "..." else it | ||
} | ||
|
||
val icon = if (shortURL.startsWith("github.com")) AllIcons.Vcs.Vendors.Github else null | ||
|
||
tabbedPane.addTab( | ||
tabTitle, | ||
icon, | ||
diffPanel(exampleCommitFix, maxRowCount), | ||
shortURL | ||
) | ||
} | ||
} | ||
} | ||
|
||
private fun diffPanel(exampleCommitFix: snyk.common.lsp.ExampleCommitFix, rowCount: Int): JComponent { | ||
fun shift(colorComponent: Int, d: Double): Int { | ||
val n = (colorComponent * d).toInt() | ||
return n.coerceIn(0, 255) | ||
} | ||
|
||
val baseColor = UIUtil.getTextFieldBackground() | ||
val addedColor = Color( | ||
shift(baseColor.red, 0.75), | ||
baseColor.green, | ||
shift(baseColor.blue, 0.75) | ||
) | ||
val removedColor = Color( | ||
shift(baseColor.red, 1.25), | ||
shift(baseColor.green, 0.85), | ||
shift(baseColor.blue, 0.85) | ||
) | ||
|
||
val panel = JPanel() | ||
panel.layout = GridLayoutManager(rowCount, 1, JBUI.emptyInsets(), -1, 0) | ||
panel.background = baseColor | ||
|
||
exampleCommitFix.lines.forEachIndexed { index, exampleLine -> | ||
val lineText = "%6d %c %s".format( | ||
exampleLine.lineNumber, | ||
when (exampleLine.lineChange) { | ||
"added" -> '+' | ||
"removed" -> '-' | ||
"none" -> ' ' | ||
else -> '!' | ||
}, | ||
exampleLine.line | ||
) | ||
val codeLine = JTextArea(lineText) | ||
|
||
codeLine.background = when (exampleLine.lineChange) { | ||
"added" -> addedColor | ||
"removed" -> removedColor | ||
"none" -> baseColor | ||
else -> baseColor | ||
} | ||
codeLine.isOpaque = true | ||
codeLine.isEditable = false | ||
codeLine.font = io.snyk.plugin.ui.getFont(-1, 14, codeLine.font) | ||
|
||
panel.add( | ||
codeLine, | ||
baseGridConstraintsAnchorWest( | ||
row = index, | ||
fill = GridConstraints.FILL_BOTH, | ||
indent = 0 | ||
) | ||
) | ||
} | ||
|
||
// fill space with empty lines to avoid rows stretching | ||
for (i in exampleCommitFix.lines.size..rowCount) { | ||
val emptyLine = JTextArea("") | ||
panel.add( | ||
emptyLine, | ||
baseGridConstraintsAnchorWest( | ||
row = i - 1, | ||
fill = GridConstraints.FILL_BOTH, | ||
indent = 0 | ||
) | ||
) | ||
} | ||
|
||
return ScrollPaneFactory.createScrollPane( | ||
panel, | ||
ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, | ||
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED | ||
) | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SnykCodeOverviewPanel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package io.snyk.plugin.ui.toolwindow.panels | ||
|
||
import com.intellij.uiDesigner.core.GridLayoutManager | ||
import com.intellij.util.ui.JBUI | ||
import com.intellij.util.ui.UIUtil | ||
import io.snyk.plugin.ui.panelGridConstraints | ||
import snyk.common.lsp.IssueData | ||
import java.awt.Dimension | ||
import javax.swing.JComponent | ||
import javax.swing.JLabel | ||
|
||
class SnykCodeOverviewPanel(codeIssueData: IssueData) : JComponent() { | ||
init { | ||
this.layout = GridLayoutManager(2, 1, JBUI.emptyInsets(), -1, -1) | ||
val label = JLabel("<html>" + codeIssueData.message + "</html>").apply { | ||
this.isOpaque = false | ||
this.background = UIUtil.getPanelBackground() | ||
this.font = io.snyk.plugin.ui.getFont(-1, 14, this.font) | ||
this.preferredSize = Dimension() // this is the key part for shrink/grow. | ||
} | ||
this.add(label, panelGridConstraints(1, indent = 1)) | ||
|
||
} | ||
} |
Oops, something went wrong.