@@ -28,6 +28,7 @@ import java.awt.event.MouseEvent
2828import java.awt.event.MouseListener
2929import java.text.DecimalFormat
3030import java.util.ArrayList
31+ import java.util.regex.Pattern
3132import javax.swing.Box
3233import javax.swing.DefaultComboBoxModel
3334import javax.swing.JCheckBoxMenuItem
@@ -46,6 +47,8 @@ import javax.swing.JTable
4647import javax.swing.SwingConstants
4748import javax.swing.UIManager
4849import javax.swing.border.EmptyBorder
50+ import javax.swing.event.HyperlinkEvent
51+ import javax.swing.event.HyperlinkListener
4952import javax.swing.event.ListSelectionEvent
5053import javax.swing.event.ListSelectionListener
5154import javax.swing.plaf.basic.BasicProgressBarUI
@@ -63,7 +66,7 @@ import org.utplsql.sqldev.resources.UtplsqlResources
6366import org.utplsql.sqldev.runner.UtplsqlRunner
6467import 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 = ' ' ' <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