Skip to content

Commit 6309e7e

Browse files
add hyperlinks to source for failed expectations, errors, warnings, info
1 parent dd2754e commit 6309e7e

File tree

1 file changed

+119
-39
lines changed

1 file changed

+119
-39
lines changed

sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.xtend

Lines changed: 119 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import java.awt.event.MouseEvent
2828
import java.awt.event.MouseListener
2929
import java.text.DecimalFormat
3030
import java.util.ArrayList
31+
import java.util.regex.Pattern
3132
import javax.swing.Box
3233
import javax.swing.DefaultComboBoxModel
3334
import javax.swing.JCheckBoxMenuItem
@@ -46,6 +47,8 @@ import javax.swing.JTable
4647
import javax.swing.SwingConstants
4748
import javax.swing.UIManager
4849
import javax.swing.border.EmptyBorder
50+
import javax.swing.event.HyperlinkEvent
51+
import javax.swing.event.HyperlinkListener
4952
import javax.swing.event.ListSelectionEvent
5053
import javax.swing.event.ListSelectionListener
5154
import javax.swing.plaf.basic.BasicProgressBarUI
@@ -63,7 +66,7 @@ import org.utplsql.sqldev.resources.UtplsqlResources
6366
import org.utplsql.sqldev.runner.UtplsqlRunner
6467
import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner
6568

66-
class RunnerPanel implements ActionListener, MouseListener {
69+
class RunnerPanel implements ActionListener, MouseListener, HyperlinkListener {
6770
static val GREEN = new Color(0, 153, 0)
6871
static val RED = new Color(153, 0, 0)
6972
static val INDICATOR_WIDTH = 20
@@ -103,10 +106,10 @@ class RunnerPanel implements ActionListener, MouseListener {
103106
RunnerTextField testStartTextField
104107
FailuresTableModel failuresTableModel
105108
JTable failuresTable
106-
RunnerTextArea testFailureMessageTextArea
107-
RunnerTextArea testErrorStackTextArea
108-
RunnerTextArea testWarningsTextArea
109-
RunnerTextArea testServerOutputTextArea
109+
RunnerTextPane testFailureMessageTextPane
110+
RunnerTextPane testErrorStackTextPane
111+
RunnerTextPane testWarningsTextPane
112+
RunnerTextPane testServerOutputTextPane
110113
JTabbedPane testDetailTabbedPane
111114

112115
def Component getGUI() {
@@ -131,10 +134,10 @@ class RunnerPanel implements ActionListener, MouseListener {
131134
testStartTextField.text = null
132135
failuresTableModel.model = null
133136
failuresTableModel.fireTableDataChanged
134-
testFailureMessageTextArea.text = null
135-
testErrorStackTextArea.text = null
136-
testWarningsTextArea.text = null
137-
testServerOutputTextArea.text = null
137+
testFailureMessageTextPane.text = null
138+
testErrorStackTextPane.text = null
139+
testWarningsTextPane.text = null
140+
testServerOutputTextPane.text = null
138141
}
139142

140143
private def refreshRunsComboBox() {
@@ -216,6 +219,39 @@ class RunnerPanel implements ActionListener, MouseListener {
216219
}
217220
}
218221

222+
private def getHtml(String text) {
223+
val html = '''
224+
<html>
225+
<head>
226+
<style type="text/css">
227+
body, p {font-family: «testOwnerTextField.font.family»; font-size: 1.0em; line-height: 1.1em; margin-top: 0px; margin-bottom: 0px;}
228+
</style>
229+
</head>
230+
<body>
231+
«getLinkedText(text)»
232+
</body>
233+
</html>
234+
'''
235+
return html
236+
}
237+
238+
private def openLink(String link) {
239+
val parts = link.split("/")
240+
val ownerName = parts.get(0)
241+
val objectName = parts.get(1)
242+
var line = Integer.parseInt(parts.get(2))
243+
val dao = new UtplsqlDao(Connections.instance.getConnection(currentRun.connectionName))
244+
val objectType = dao.getObjectType(ownerName, objectName)
245+
val fixedObjectType = '''«objectType»«IF objectType == "PACKAGE" || objectType == "TYPE"» BODY«ENDIF»'''
246+
if (parts.size == 4) {
247+
val procedureName = parts.get(3)
248+
val source = dao.getSource(ownerName, fixedObjectType, objectName).trim
249+
val parser = new UtplsqlParser(source)
250+
line = parser.getLineOf(procedureName)
251+
}
252+
openEditor(ownerName, '''«objectType»«IF objectType == "PACKAGE" || objectType == "TYPE"» BODY«ENDIF»''', objectName.toUpperCase, line, 1)
253+
}
254+
219255
private def openEditor(String owner, String type, String name, int line, int col) {
220256
var drillLink = new DefaultDrillLink
221257
drillLink.connName = currentRun.connectionName
@@ -442,6 +478,13 @@ class RunnerPanel implements ActionListener, MouseListener {
442478
override mouseReleased(MouseEvent e) {
443479
}
444480

481+
override hyperlinkUpdate(HyperlinkEvent e) {
482+
if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) {
483+
val link = e.description
484+
openLink(link)
485+
}
486+
}
487+
445488
private static def formatDateTime(String dateTime) {
446489
if (dateTime === null) {
447490
return null
@@ -474,19 +517,53 @@ class RunnerPanel implements ActionListener, MouseListener {
474517
p.testStartTextField.text = formatDateTime(test.startTime)
475518
p.failuresTableModel.model = test.failedExpectations
476519
p.failuresTableModel.fireTableDataChanged
477-
p.testFailureMessageTextArea.text = null
520+
p.testFailureMessageTextPane.text = null
478521
if (test.failedExpectations !== null && test.failedExpectations.size > 0) {
479522
p.failuresTable.setRowSelectionInterval(0, 0)
480523
}
481-
p.testErrorStackTextArea.text = test.errorStack?.trim
482-
p.testWarningsTextArea.text = test.warnings?.trim
483-
p.testServerOutputTextArea.text = test.serverOutput?.trim
524+
p.testErrorStackTextPane.text = p.getHtml(test.errorStack?.trim)
525+
p.testWarningsTextPane.text = p.getHtml(test.warnings?.trim)
526+
p.testServerOutputTextPane.text = p.getHtml(test.serverOutput?.trim)
484527
p.syncDetailTab
485528
p.testOverviewRunMenuItem.enabled = true
486529
p.testOverviewRunWorksheetMenuItem.enabled = true
487530
}
488531
}
489532
}
533+
534+
private def getLinkedText(String text) {
535+
if (text === null) {
536+
return ""
537+
}
538+
// Patterns (primarily Asserts, Errors, ServerOutput):
539+
// at "OWNER.PACKAGE.PROCEDURE", line 42
540+
// at "OWNER.PROCEDURE", line 42
541+
val p1 = Pattern.compile('''\s+("([^\.]+)\.([^\."]+)(?:\.([^\"]+))?",\s+line\s+([0-9]+))''')
542+
var localText = text
543+
var m = p1.matcher(localText)
544+
while(m.find) {
545+
val link = ''' <a href="«m.group(2)»/«m.group(3)»/«m.group(5)»">«m.group(1)»</a>'''
546+
localText = localText.replaceFirst(p1.pattern, link)
547+
m = p1.matcher(localText)
548+
}
549+
// Patterns (primarily Warnings, without line reference, calculate when opening link):
550+
// owner.package.procedure
551+
val p2 = Pattern.compile('''^\s{2}(([^\.]+)\.([^\.]+)\.(.+))$''', Pattern.MULTILINE)
552+
m = p2.matcher(localText)
553+
while(m.find) {
554+
val link = '''&nbsp;&nbsp;<a href="«m.group(2).toUpperCase»/«m.group(3).toUpperCase»/1/«m.group(4).toUpperCase»">«m.group(1)»</a>'''
555+
val start = m.start(0)
556+
val end = m.end(0)
557+
localText = '''«localText.substring(0, start)»«link»«localText.substring(end)»'''
558+
m = p2.matcher(localText)
559+
}
560+
val result = '''
561+
«FOR p : localText.split(System.lineSeparator)»
562+
<p>«p»</p>
563+
«ENDFOR»
564+
'''
565+
return result
566+
}
490567

491568
static class FailuresRowListener implements ListSelectionListener {
492569
RunnerPanel p
@@ -500,7 +577,9 @@ class RunnerPanel implements ActionListener, MouseListener {
500577
if (rowIndex != -1) {
501578
val row = p.failuresTable.convertRowIndexToModel(rowIndex)
502579
val expectation = p.failuresTableModel.getExpectation(row)
503-
p.testFailureMessageTextArea.text = expectation.failureText
580+
val html = p.getHtml(expectation.failureText)
581+
p.testFailureMessageTextPane.text = html
582+
504583
}
505584
}
506585
}
@@ -958,12 +1037,12 @@ class RunnerPanel implements ActionListener, MouseListener {
9581037
failuresDescription.headerRenderer = failuresTableHeaderRenderer
9591038
val failuresTableScrollPane = new JScrollPane(failuresTable)
9601039
// - failures details
961-
testFailureMessageTextArea = new RunnerTextArea
962-
testFailureMessageTextArea.editable = false
963-
testFailureMessageTextArea.enabled = true
964-
testFailureMessageTextArea.lineWrap = true
965-
testFailureMessageTextArea.wrapStyleWord = true
966-
val testFailureMessageScrollPane = new JScrollPane(testFailureMessageTextArea)
1040+
testFailureMessageTextPane = new RunnerTextPane
1041+
testFailureMessageTextPane.editable = false
1042+
testFailureMessageTextPane.enabled = true
1043+
testFailureMessageTextPane.contentType = "text/html"
1044+
testFailureMessageTextPane.addHyperlinkListener(this)
1045+
val testFailureMessageScrollPane = new JScrollPane(testFailureMessageTextPane)
9671046
c.gridx = 1
9681047
c.gridy = 0
9691048
c.gridwidth = 1
@@ -981,12 +1060,12 @@ class RunnerPanel implements ActionListener, MouseListener {
9811060
// Errors tabbed pane (Error Stack)
9821061
val testErrorStackPanel = new JPanel
9831062
testErrorStackPanel.setLayout(new GridBagLayout())
984-
testErrorStackTextArea = new RunnerTextArea
985-
testErrorStackTextArea.editable = false
986-
testErrorStackTextArea.enabled = true
987-
testErrorStackTextArea.lineWrap = true
988-
testErrorStackTextArea.wrapStyleWord = true
989-
val testErrorStackScrollPane = new JScrollPane(testErrorStackTextArea)
1063+
testErrorStackTextPane = new RunnerTextPane
1064+
testErrorStackTextPane.editable = false
1065+
testErrorStackTextPane.enabled = true
1066+
testErrorStackTextPane.contentType = "text/html"
1067+
testErrorStackTextPane.addHyperlinkListener(this)
1068+
val testErrorStackScrollPane = new JScrollPane(testErrorStackTextPane)
9901069
c.gridx = 0
9911070
c.gridy = 0
9921071
c.gridwidth = 1
@@ -1001,12 +1080,12 @@ class RunnerPanel implements ActionListener, MouseListener {
10011080
// Warnings tabbed pane
10021081
val testWarningsPanel = new JPanel
10031082
testWarningsPanel.setLayout(new GridBagLayout())
1004-
testWarningsTextArea = new RunnerTextArea
1005-
testWarningsTextArea.editable = false
1006-
testWarningsTextArea.enabled = true
1007-
testWarningsTextArea.lineWrap = true
1008-
testWarningsTextArea.wrapStyleWord = true
1009-
val testWarningsScrollPane = new JScrollPane(testWarningsTextArea)
1083+
testWarningsTextPane = new RunnerTextPane
1084+
testWarningsTextPane.editable = false
1085+
testWarningsTextPane.enabled = true
1086+
testWarningsTextPane.contentType = "text/html"
1087+
testWarningsTextPane.addHyperlinkListener(this)
1088+
val testWarningsScrollPane = new JScrollPane(testWarningsTextPane)
10101089
c.gridx = 0
10111090
c.gridy = 0
10121091
c.gridwidth = 1
@@ -1021,12 +1100,12 @@ class RunnerPanel implements ActionListener, MouseListener {
10211100
// Info tabbed pane (Server Output)
10221101
val testServerOutputPanel = new JPanel
10231102
testServerOutputPanel.setLayout(new GridBagLayout())
1024-
testServerOutputTextArea = new RunnerTextArea
1025-
testServerOutputTextArea.editable = false
1026-
testServerOutputTextArea.enabled = true
1027-
testServerOutputTextArea.lineWrap = true
1028-
testServerOutputTextArea.wrapStyleWord = true
1029-
val testServerOutputScrollPane = new JScrollPane(testServerOutputTextArea)
1103+
testServerOutputTextPane = new RunnerTextPane
1104+
testServerOutputTextPane.editable = false
1105+
testServerOutputTextPane.enabled = true
1106+
testServerOutputTextPane.contentType = "text/html"
1107+
testServerOutputTextPane.addHyperlinkListener(this)
1108+
val testServerOutputScrollPane = new JScrollPane(testServerOutputTextPane)
10301109
c.gridx = 0
10311110
c.gridy = 0
10321111
c.gridwidth = 1
@@ -1062,5 +1141,6 @@ class RunnerPanel implements ActionListener, MouseListener {
10621141
val referenceBorder = testOwnerTextField.border
10631142
testDescriptionTextArea.border = referenceBorder
10641143
testIdTextArea.border = referenceBorder
1065-
}
1144+
}
1145+
10661146
}

0 commit comments

Comments
 (0)