@@ -13,7 +13,6 @@ import com.github.adrienpessu.sarifviewer.models.Leaf
1313import com.github.adrienpessu.sarifviewer.models.View
1414import com.github.adrienpessu.sarifviewer.services.SarifService
1515import com.github.adrienpessu.sarifviewer.utils.GitHubInstance
16- import com.intellij.icons.AllIcons
1716import com.intellij.notification.NotificationGroupManager
1817import com.intellij.notification.NotificationType
1918import com.intellij.openapi.actionSystem.ActionManager
@@ -38,26 +37,29 @@ import com.intellij.ui.awt.RelativePoint
3837import com.intellij.ui.components.JBPanel
3938import com.intellij.ui.components.JBTabbedPane
4039import com.intellij.ui.content.ContentFactory
40+ import com.intellij.ui.table.JBTable
4141import git4idea.GitLocalBranch
4242import git4idea.repo.GitRepository
4343import git4idea.repo.GitRepositoryChangeListener
4444import git4idea.repo.GitRepositoryManager
4545import java.awt.Component
46+ import java.awt.Cursor
4647import java.awt.Desktop
4748import java.awt.Dimension
4849import java.awt.event.ActionListener
50+ import java.awt.event.MouseAdapter
51+ import java.awt.event.MouseEvent
4952import java.io.File
5053import java.net.URI
5154import java.nio.charset.Charset
5255import java.nio.file.Files
5356import java.nio.file.Path
5457import javax.swing.*
55- import javax.swing.event.HyperlinkEvent
56- import javax.swing.event.HyperlinkListener
5758import javax.swing.event.TreeSelectionEvent
5859import javax.swing.event.TreeSelectionListener
5960import javax.swing.filechooser.FileNameExtensionFilter
60- import javax.swing.text.html.HTMLEditorKit
61+ import javax.swing.table.DefaultTableCellRenderer
62+ import javax.swing.table.DefaultTableModel
6163import javax.swing.tree.DefaultMutableTreeNode
6264import javax.swing.tree.DefaultTreeModel
6365
@@ -86,37 +88,34 @@ class SarifViewerWindowFactory : ToolWindowFactory {
8688 (openLocalFileAction as OpenLocalAction ).myToolWindow = this
8789 val refreshAction = actionManager.getAction(" RefreshAction" )
8890 (refreshAction as RefreshAction ).myToolWindow = this
89- val actions = ArrayList <AnAction >();
91+ val actions = ArrayList <AnAction >()
9092 actions.add(openLocalFileAction)
9193 actions.add(refreshAction)
9294
9395 toolWindow.setTitleActions(actions)
9496 }
9597
9698 internal var github: GitHubInstance ? = null
97- get() = field
9899 internal var repositoryFullName: String? = null
99- get() = field
100100 internal var currentBranch: GitLocalBranch ? = null
101- get() = field
102101
103102 private var localMode = false
104103 private val service = toolWindow.project.service<SarifService >()
105104 private val project = toolWindow.project
106105 private var main = ScrollPaneFactory .createScrollPane()
107106 private val details = JBTabbedPane ()
108107 private val splitPane = JSplitPane (JSplitPane .VERTICAL_SPLIT , false , main, details)
109- private var myList = JTree ()
108+ private var myList = com.intellij.ui.treeStructure. Tree ()
110109 private var comboBranchPR = ComboBox (arrayOf(BranchItemComboBox (0 , " main" , " " , " " )))
111- private val infos = JEditorPane ()
112- private val steps = JEditorPane ()
110+ private val tableInfos = JBTable (DefaultTableModel (arrayOf<Any >(" Property" , " Value" ), 0 ))
111+ private val tableSteps = JBTable (DefaultTableModel (arrayOf<Any >(" Path" ), 0 ))
112+ private val steps = JPanel ()
113113 private val errorField = JLabel (" Error message here " )
114114 private val errorToolbar = JToolBar (" " , JToolBar .HORIZONTAL )
115115 private val loadingPanel = JPanel ()
116116 private var sarifGitHubRef = " "
117117 private var loading = false
118118 private var disableComboBoxEvent = false
119- private val views = View .views
120119 private var currentView = View .RULE
121120 private var cacheSarif: SarifSchema210 ? = null
122121
@@ -263,26 +262,14 @@ class SarifViewerWindowFactory : ToolWindowFactory {
263262 }
264263
265264 private fun JBPanel <JBPanel <* >>.buildSkeleton () {
266- infos.isEditable = false
267- infos.addHyperlinkListener(object : HyperlinkListener {
268- override fun hyperlinkUpdate (hle : HyperlinkEvent ? ) {
269- if (HyperlinkEvent .EventType .ACTIVATED == hle?.eventType && hle?.description != null ) {
270- Desktop .getDesktop().browse(URI (hle.description))
271- }
272- }
273- })
274- steps.isEditable = false
275- steps.addHyperlinkListener(object : HyperlinkListener {
276- override fun hyperlinkUpdate (hle : HyperlinkEvent ? ) {
277- if (HyperlinkEvent .EventType .ACTIVATED == hle?.eventType) {
278- hle?.description.toString().split(" :" ).let { location ->
279- openFile(project, location[0 ], location[1 ].toInt())
280- }
281- }
282- }
283- })
265+ steps.layout = BoxLayout (steps, BoxLayout .Y_AXIS )
266+ tableSteps.size = Dimension (steps.width, steps.height)
267+ steps.add(tableSteps)
284268
285- details.addTab(" Infos" , infos)
269+ // Add the table to a scroll pane
270+ val scrollPane = JScrollPane (tableInfos)
271+
272+ details.addTab(" Infos" , scrollPane)
286273 details.addTab(" Steps" , steps)
287274
288275 layout = BoxLayout (this , BoxLayout .Y_AXIS )
@@ -436,17 +423,18 @@ class SarifViewerWindowFactory : ToolWindowFactory {
436423 val root = DefaultMutableTreeNode (project.name)
437424
438425 map.forEach { (key, value) ->
439- val ruleNode = DefaultMutableTreeNode (key)
426+ val ruleNode = DefaultMutableTreeNode (" $ key ( ${value.size} ) " )
440427 value.forEach { location ->
441428 val locationNode = DefaultMutableTreeNode (location)
442429 ruleNode.add(locationNode)
443430 }
444431 root.add(ruleNode)
445432 }
446433
447- myList = JTree (root)
434+ myList = com.intellij.ui.treeStructure. Tree (root)
448435
449436 myList.isRootVisible = false
437+ myList.showsRootHandles = true
450438 main = ScrollPaneFactory .createScrollPane(myList)
451439
452440 details.isVisible = false
@@ -457,7 +445,7 @@ class SarifViewerWindowFactory : ToolWindowFactory {
457445 myList.addTreeSelectionListener(object : TreeSelectionListener {
458446 override fun valueChanged (e : TreeSelectionEvent ? ) {
459447 if (e != null && e.isAddedPath) {
460- val leaves = map[e.path.parentPath.lastPathComponent.toString()]
448+ val leaves = map[e.path.parentPath.lastPathComponent.toString().split( " " ).first() ]
461449 if (! leaves.isNullOrEmpty()) {
462450 val leaf = try {
463451 leaves.first { it.address == ((e.path.lastPathComponent as DefaultMutableTreeNode ).userObject as Leaf ).address }
@@ -470,18 +458,91 @@ class SarifViewerWindowFactory : ToolWindowFactory {
470458 .replace(" api/v3/" , " " )
471459 .replace(" repos/" , " " )
472460 .replace(" code-scanning/alerts" , " security/code-scanning" )
473- val githubURL = " <a target=\" _BLANK\" href=\" $githubAlertUrl \" >$githubAlertUrl </a>"
474461
475- infos.contentType = " text/html"
462+ tableInfos.clearSelection()
463+ // Create a table model with "Property" and "Value" columns
464+ val defaultTableModel: DefaultTableModel =
465+ object : DefaultTableModel (arrayOf<Any >(" Property" , " Value" ), 0 ) {
466+ override fun isCellEditable (row : Int , column : Int ): Boolean {
467+ return false
468+ }
469+ }
470+ tableInfos.model = defaultTableModel
471+
472+ // Add some data
473+ defaultTableModel.addRow(arrayOf<Any >(" Name" , leaf.leafName))
474+ defaultTableModel.addRow(arrayOf<Any >(" Level" , leaf.level))
475+ defaultTableModel.addRow(arrayOf<Any >(" Rule's name" , leaf.ruleName))
476+ defaultTableModel.addRow(arrayOf<Any >(" Rule's description" , leaf.ruleDescription))
477+ defaultTableModel.addRow(arrayOf<Any >(" Location" , leaf.location))
478+ defaultTableModel.addRow(arrayOf<Any >(" GitHub alert number" , leaf.githubAlertNumber))
479+ defaultTableModel.addRow(
480+ arrayOf<Any >(
481+ " GitHub alert url" ,
482+ " <a href=\" $githubAlertUrl \" >$githubAlertUrl </a"
483+ )
484+ )
485+
486+ tableInfos.setDefaultRenderer(Object ::class .java, object : DefaultTableCellRenderer () {
487+ override fun getTableCellRendererComponent (
488+ table : JTable ? ,
489+ value : Any? ,
490+ isSelected : Boolean ,
491+ hasFocus : Boolean ,
492+ row : Int ,
493+ column : Int
494+ ): Component {
495+ var c = super .getTableCellRendererComponent(
496+ table,
497+ value,
498+ isSelected,
499+ hasFocus,
500+ row,
501+ column
502+ )
503+ if (row == tableInfos.rowCount - 1 && column == tableInfos.columnCount - 1 ) {
504+ val url = tableInfos.getValueAt(row, column).toString()
505+ c = JLabel (" <html><a href='$url '>$url </a></html>" )
506+ c.cursor = Cursor .getPredefinedCursor(Cursor .HAND_CURSOR )
507+ }
508+ return c
509+ }
510+ })
511+
512+
513+ tableInfos.addMouseListener(object : MouseAdapter () {
514+ override fun mouseClicked (e : MouseEvent ) {
515+ val row = tableInfos.rowAtPoint(e.point)
516+ val column = tableInfos.columnAtPoint(e.point)
517+ if (row == tableInfos.rowCount - 1 ) {
518+ if (column == tableInfos.columnCount - 1 ) {
519+ if (Desktop .isDesktopSupported() && Desktop .getDesktop()
520+ .isSupported(Desktop .Action .BROWSE )
521+ ) {
522+ Desktop .getDesktop().browse(URI (githubAlertUrl))
523+ }
524+ }
525+ }
526+ }
527+ })
528+
529+ tableInfos.updateUI()
476530
477- infos.text =
478- " ${leaf.leafName} <br/> Level: ${leaf.level} <br/>Rule's name: ${leaf.ruleName} <br/>Rule's description ${leaf.ruleDescription} <br/>Location ${leaf.location} <br/>GitHub alert number: ${leaf.githubAlertNumber} <br/>GitHub alert url ${githubURL} \n "
531+ tableSteps.clearSelection()
479532
480- steps.read(leaf.steps.joinToString(" <br/>" ) { step ->
481- " <a href=\" $step \" >${step.split(" /" ).last()} </a>"
482- }.byteInputStream(Charset .defaultCharset()), HTMLEditorKit ::class .java)
533+ tableSteps.model = DefaultTableModel (arrayOf<Any >(" Path" ), 0 )
483534
484- steps.contentType = " text/html"
535+ leaf.steps.forEachIndexed { index, step ->
536+ (tableSteps.model as DefaultTableModel ).addRow(arrayOf<Any >(" $index $step " ))
537+ }
538+
539+ tableSteps.addMouseListener(object : MouseAdapter () {
540+ override fun mouseClicked (e : MouseEvent ) {
541+ val row = tableInfos.rowAtPoint(e.point)
542+ val path = leaf.steps[row].split(" :" )
543+ openFile(project, path[0 ], path[1 ].toInt())
544+ }
545+ })
485546
486547 details.isVisible = true
487548 openFile(
@@ -532,32 +593,33 @@ class SarifViewerWindowFactory : ToolWindowFactory {
532593 true // request focus to editor
533594 )
534595 FileDocumentManager .getInstance().getDocument(virtualFile)?.let { document ->
535- val lineStartOffset = document.getLineStartOffset(lineNumber - 1 )
536- val lineEndOffset = document.getLineEndOffset(lineNumber - 1 )
537- val editor = FileEditorManager .getInstance(project).selectedTextEditor ? : return
538- editor.caretModel.moveToOffset(lineStartOffset)
539- editor.scrollingModel.scrollToCaret(ScrollType .CENTER )
540- editor.selectionModel.setSelection(lineStartOffset, lineEndOffset)
541-
542- val messageType = when (level) {
543- " error" -> MessageType .ERROR
544- " warning" -> MessageType .WARNING
545- else -> MessageType .INFO
546- }
547-
548- // add a balloon on the selection
549- val balloon = JBPopupFactory .getInstance().createHtmlTextBalloonBuilder(
550- ruleDescription,
551- messageType,
552- null
553- ).createBalloon()
554- balloon.show(
555- RelativePoint (
556- editor.contentComponent,
557- editor.visualPositionToXY(editor.caretModel.visualPosition)
558- ), Balloon .Position .above
559- )
596+ if (ruleDescription.isNotEmpty()) {
597+ val lineStartOffset = document.getLineStartOffset(lineNumber - 1 )
598+ val lineEndOffset = document.getLineEndOffset(lineNumber - 1 )
599+ val editor = FileEditorManager .getInstance(project).selectedTextEditor ? : return
600+ editor.caretModel.moveToOffset(lineStartOffset)
601+ editor.scrollingModel.scrollToCaret(ScrollType .CENTER )
602+ editor.selectionModel.setSelection(lineStartOffset, lineEndOffset)
603+
604+ val messageType = when (level) {
605+ " error" -> MessageType .ERROR
606+ " warning" -> MessageType .WARNING
607+ else -> MessageType .INFO
608+ }
560609
610+ // add a balloon on the selection
611+ val balloon = JBPopupFactory .getInstance().createHtmlTextBalloonBuilder(
612+ ruleDescription,
613+ messageType,
614+ null
615+ ).createBalloon()
616+ balloon.show(
617+ RelativePoint (
618+ editor.contentComponent,
619+ editor.visualPositionToXY(editor.caretModel.visualPosition)
620+ ), Balloon .Position .above
621+ )
622+ }
561623 }
562624
563625 }
@@ -571,8 +633,10 @@ class SarifViewerWindowFactory : ToolWindowFactory {
571633 }
572634 }
573635
574- infos.text = " "
575- steps.text = " "
636+ tableInfos.clearSelection()
637+ tableInfos.updateUI()
638+ tableSteps.clearSelection()
639+ tableSteps.updateUI()
576640 details.isVisible = false
577641 errorToolbar.isVisible = false
578642 }
@@ -592,8 +656,10 @@ class SarifViewerWindowFactory : ToolWindowFactory {
592656 val mainResults: List <Result > = sarifMainBranch.flatMap { it.runs?.get(0 )?.results ? : emptyList() }
593657
594658 for (currentResult in results) {
595- if (mainResults.none { it.ruleId == currentResult.ruleId
596- && (" ${currentResult.locations[0 ].physicalLocation.artifactLocation.uri} :${currentResult.locations[0 ].physicalLocation.region.startLine} " == " ${it.locations[0 ].physicalLocation.artifactLocation.uri} :${it.locations[0 ].physicalLocation.region.startLine} " ) }) {
659+ if (mainResults.none {
660+ it.ruleId == currentResult.ruleId
661+ && (" ${currentResult.locations[0 ].physicalLocation.artifactLocation.uri} :${currentResult.locations[0 ].physicalLocation.region.startLine} " == " ${it.locations[0 ].physicalLocation.artifactLocation.uri} :${it.locations[0 ].physicalLocation.region.startLine} " )
662+ }) {
597663 resultsToDisplay.add(currentResult)
598664 }
599665 }
0 commit comments