PageRenderTime 68ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/groovy/ui/Console.groovy

https://github.com/hegjon/groovy-core
Groovy | 1251 lines | 972 code | 182 blank | 97 comment | 126 complexity | e40e8f2d55d03853ce83bd7eaf800eb0 MD5 | raw file
Possible License(s): BSD-3-Clause-No-Nuclear-License-2014, Apache-2.0, BSD-3-Clause
  1. /*
  2. * Copyright 2003-2011 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package groovy.ui
  17. import groovy.inspect.swingui.ObjectBrowser
  18. import groovy.inspect.swingui.AstBrowser
  19. import groovy.swing.SwingBuilder
  20. import groovy.ui.text.FindReplaceUtility
  21. import java.awt.Component
  22. import java.awt.EventQueue
  23. import java.awt.Font
  24. import java.awt.Toolkit
  25. import java.awt.Window
  26. import java.awt.event.ActionEvent
  27. import java.awt.event.ComponentEvent
  28. import java.awt.event.ComponentListener
  29. import java.awt.event.FocusListener
  30. import java.awt.event.FocusEvent
  31. import java.util.prefs.Preferences
  32. import javax.swing.*
  33. import javax.swing.event.CaretEvent
  34. import javax.swing.event.CaretListener
  35. import javax.swing.event.HyperlinkListener
  36. import javax.swing.event.HyperlinkEvent
  37. import javax.swing.text.AttributeSet
  38. import javax.swing.text.Element
  39. import javax.swing.text.SimpleAttributeSet
  40. import javax.swing.text.Style
  41. import javax.swing.text.StyleConstants
  42. import javax.swing.text.html.HTML
  43. import javax.swing.filechooser.FileFilter
  44. import org.codehaus.groovy.runtime.StackTraceUtils
  45. import org.codehaus.groovy.control.ErrorCollector
  46. import org.codehaus.groovy.control.MultipleCompilationErrorsException
  47. import org.codehaus.groovy.control.messages.SyntaxErrorMessage
  48. import org.codehaus.groovy.syntax.SyntaxException
  49. import org.codehaus.groovy.control.messages.ExceptionMessage
  50. import java.awt.Dimension
  51. import java.awt.BorderLayout
  52. import org.codehaus.groovy.control.CompilerConfiguration
  53. import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
  54. import groovy.transform.ThreadInterrupt
  55. /**
  56. * Groovy Swing console.
  57. *
  58. * Allows user to interactively enter and execute Groovy.
  59. *
  60. * @version $Id$
  61. * @author Danno Ferrin
  62. * @author Dierk Koenig, changed Layout, included Selection sensitivity, included ObjectBrowser
  63. * @author Alan Green more features: history, System.out capture, bind result to _
  64. * @author Guillaume Laforge, stacktrace hyperlinking to the current script line
  65. * @author Hamlet D'Arcy, AST browser
  66. * @author Roshan Dawrani
  67. * @author Paul King
  68. */
  69. class Console implements CaretListener, HyperlinkListener, ComponentListener, FocusListener {
  70. static final String DEFAULT_SCRIPT_NAME_START = "ConsoleScript"
  71. static private prefs = Preferences.userNodeForPackage(Console)
  72. // Whether or not std output should be captured to the console
  73. static boolean captureStdOut = prefs.getBoolean('captureStdOut', true)
  74. static boolean captureStdErr = prefs.getBoolean('captureStdErr', true)
  75. static consoleControllers = []
  76. boolean fullStackTraces = prefs.getBoolean('fullStackTraces',
  77. Boolean.valueOf(System.getProperty("groovy.full.stacktrace", "false")))
  78. Action fullStackTracesAction
  79. boolean showScriptInOutput = prefs.getBoolean('showScriptInOutput', true)
  80. Action showScriptInOutputAction
  81. boolean visualizeScriptResults = prefs.getBoolean('visualizeScriptResults', false)
  82. Action visualizeScriptResultsAction
  83. boolean showToolbar = prefs.getBoolean('showToolbar', true)
  84. Component toolbar
  85. Action showToolbarAction
  86. boolean detachedOutput = prefs.getBoolean('detachedOutput', false)
  87. Action detachedOutputAction
  88. Action showOutputWindowAction
  89. Action hideOutputWindowAction1
  90. Action hideOutputWindowAction2
  91. Action hideOutputWindowAction3
  92. Action hideOutputWindowAction4
  93. int origDividerSize
  94. Component outputWindow
  95. Component copyFromComponent
  96. Component blank
  97. Component scrollArea
  98. boolean autoClearOutput = prefs.getBoolean('autoClearOutput', false)
  99. Action autoClearOutputAction
  100. // Safer thread interruption
  101. boolean threadInterrupt = prefs.getBoolean('threadInterrupt', false)
  102. Action threadInterruptAction
  103. // Maximum size of history
  104. int maxHistory = 10
  105. // Maximum number of characters to show on console at any time
  106. int maxOutputChars = System.getProperty('groovy.console.output.limit','20000') as int
  107. // UI
  108. SwingBuilder swing
  109. RootPaneContainer frame
  110. ConsoleTextEditor inputEditor
  111. JSplitPane splitPane
  112. JTextPane inputArea
  113. JTextPane outputArea
  114. JLabel statusLabel
  115. JLabel rowNumAndColNum
  116. // row info
  117. Element rootElement
  118. int cursorPos
  119. int rowNum
  120. int colNum
  121. // Styles for output area
  122. Style promptStyle
  123. Style commandStyle
  124. Style outputStyle
  125. Style stacktraceStyle
  126. Style hyperlinkStyle
  127. Style resultStyle
  128. // Internal history
  129. List history = []
  130. int historyIndex = 1 // valid values are 0..history.length()
  131. HistoryRecord pendingRecord = new HistoryRecord( allText: "", selectionStart: 0, selectionEnd: 0)
  132. Action prevHistoryAction
  133. Action nextHistoryAction
  134. // Current editor state
  135. boolean dirty
  136. Action saveAction
  137. int textSelectionStart // keep track of selections in inputArea
  138. int textSelectionEnd
  139. def scriptFile
  140. File currentFileChooserDir = new File(Preferences.userNodeForPackage(Console).get('currentFileChooserDir', '.'))
  141. File currentClasspathJarDir = new File(Preferences.userNodeForPackage(Console).get('currentClasspathJarDir', '.'))
  142. File currentClasspathDir = new File(Preferences.userNodeForPackage(Console).get('currentClasspathDir', '.'))
  143. // Running scripts
  144. CompilerConfiguration config
  145. GroovyShell shell
  146. int scriptNameCounter = 0
  147. SystemOutputInterceptor systemOutInterceptor
  148. SystemOutputInterceptor systemErrorInterceptor
  149. Thread runThread = null
  150. Closure beforeExecution
  151. Closure afterExecution
  152. public static String ICON_PATH = '/groovy/ui/ConsoleIcon.png' // used by ObjectBrowser and AST Viewer
  153. public static String NODE_ICON_PATH = '/groovy/ui/icons/bullet_green.png' // used by AST Viewer
  154. private static groovyFileFilter = new GroovyFileFilter()
  155. private boolean scriptRunning = false
  156. private boolean stackOverFlowError = false
  157. Action interruptAction
  158. static void main(args) {
  159. if (args.length == 1 && args[0] == '--help') {
  160. println '''usage: groovyConsole [options] [filename]
  161. options:
  162. --help This Help message
  163. -cp,-classpath,--classpath <path> Specify classpath'''
  164. return
  165. }
  166. // allow the full stack traces to bubble up to the root logger
  167. java.util.logging.Logger.getLogger(StackTraceUtils.STACK_LOG_NAME).useParentHandlers = true
  168. //when starting via main set the look and feel to system
  169. UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
  170. def console = new Console(Console.class.classLoader?.getRootLoader())
  171. console.run()
  172. if (args.length == 1) console.loadScriptFile(args[0] as File)
  173. }
  174. Console() {
  175. this(new Binding())
  176. }
  177. Console(Binding binding) {
  178. this(null, binding)
  179. }
  180. Console(ClassLoader parent) {
  181. this(parent, new Binding())
  182. }
  183. Console(ClassLoader parent, Binding binding) {
  184. newScript(parent, binding);
  185. try {
  186. System.setProperty("groovy.full.stacktrace", System.getProperty("groovy.full.stacktrace",
  187. Boolean.toString(prefs.getBoolean('fullStackTraces', false))))
  188. } catch (SecurityException se) {
  189. fullStackTracesAction.enabled = false;
  190. }
  191. consoleControllers += this
  192. // listen for Ivy events if Ivy is on the Classpath
  193. try {
  194. if (Class.forName('org.apache.ivy.core.event.IvyListener')) {
  195. def ivyPluginClass = Class.forName('groovy.ui.ConsoleIvyPlugin')
  196. ivyPluginClass.newInstance().addListener(this)
  197. }
  198. } catch(ClassNotFoundException ignore) { }
  199. binding.variables._outputTransforms = OutputTransforms.loadOutputTransforms()
  200. }
  201. void newScript(ClassLoader parent, Binding binding) {
  202. config = new CompilerConfiguration()
  203. if (threadInterrupt) config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt))
  204. shell = new GroovyShell(parent, binding, config)
  205. }
  206. static def frameConsoleDelegates = [
  207. rootContainerDelegate:{
  208. frame(
  209. title: 'GroovyConsole',
  210. //location: [100,100], // in groovy 2.0 use platform default location
  211. iconImage: imageIcon("/groovy/ui/ConsoleIcon.png").image,
  212. defaultCloseOperation: JFrame.DO_NOTHING_ON_CLOSE,
  213. ) {
  214. try {
  215. current.locationByPlatform = true
  216. } catch (Exception e) {
  217. current.location = [100, 100] // for 1.4 compatibility
  218. }
  219. containingWindows += current
  220. }
  221. },
  222. menuBarDelegate: {arg->
  223. current.JMenuBar = build(arg)}
  224. ];
  225. void run() {
  226. run(frameConsoleDelegates)
  227. }
  228. void run(JApplet applet) {
  229. run([
  230. rootContainerDelegate:{
  231. containingWindows += SwingUtilities.getRoot(applet.getParent())
  232. applet
  233. },
  234. menuBarDelegate: {arg->
  235. current.JMenuBar = build(arg)}
  236. ])
  237. }
  238. void run(Map defaults) {
  239. swing = new SwingBuilder()
  240. defaults.each{k, v -> swing[k] = v}
  241. // tweak what the stack traces filter out to be fairly broad
  242. System.setProperty("groovy.sanitized.stacktraces", """org.codehaus.groovy.runtime.
  243. org.codehaus.groovy.
  244. groovy.lang.
  245. gjdk.groovy.lang.
  246. sun.
  247. java.lang.reflect.
  248. java.lang.Thread
  249. groovy.ui.Console""")
  250. // add controller to the swingBuilder bindings
  251. swing.controller = this
  252. // create the actions
  253. swing.build(ConsoleActions)
  254. // create the view
  255. swing.build(ConsoleView)
  256. bindResults()
  257. // stitch some actions together
  258. swing.bind(source:swing.inputEditor.undoAction, sourceProperty:'enabled', target:swing.undoAction, targetProperty:'enabled')
  259. swing.bind(source:swing.inputEditor.redoAction, sourceProperty:'enabled', target:swing.redoAction, targetProperty:'enabled')
  260. if (swing.consoleFrame instanceof java.awt.Window) {
  261. swing.consoleFrame.pack()
  262. swing.consoleFrame.show()
  263. }
  264. installInterceptor()
  265. swing.doLater inputArea.&requestFocus
  266. }
  267. public void installInterceptor() {
  268. systemOutInterceptor = new SystemOutputInterceptor(this.&notifySystemOut, true)
  269. systemOutInterceptor.start()
  270. systemErrorInterceptor = new SystemOutputInterceptor(this.&notifySystemErr, false)
  271. systemErrorInterceptor.start()
  272. }
  273. void addToHistory(record) {
  274. history.add(record)
  275. // history.size here just retrieves method closure
  276. if (history.size() > maxHistory) {
  277. history.remove(0)
  278. }
  279. // history.size doesn't work here either
  280. historyIndex = history.size()
  281. updateHistoryActions()
  282. }
  283. // Ensure we don't have too much in console (takes too much memory)
  284. private ensureNoDocLengthOverflow(doc) {
  285. // if it is a case of stackOverFlowError, show the exception details from the front
  286. // as there is no point in showing the repeating details at the back
  287. int offset = stackOverFlowError ? maxOutputChars : 0
  288. if (doc.length > maxOutputChars) {
  289. doc.remove(offset, doc.length - maxOutputChars)
  290. }
  291. }
  292. // Append a string to the output area
  293. void appendOutput(String text, AttributeSet style){
  294. def doc = outputArea.styledDocument
  295. doc.insertString(doc.length, text, style)
  296. ensureNoDocLengthOverflow(doc)
  297. }
  298. void appendOutput(Window window, AttributeSet style) {
  299. appendOutput(window.toString(), style)
  300. }
  301. void appendOutput(Object object, AttributeSet style) {
  302. appendOutput(object.toString(), style)
  303. }
  304. void appendOutput(Component component, AttributeSet style) {
  305. SimpleAttributeSet sas = new SimpleAttributeSet();
  306. sas.addAttribute(StyleConstants.NameAttribute, "component")
  307. StyleConstants.setComponent(sas, component)
  308. appendOutput(component.toString(), sas)
  309. }
  310. void appendOutput(Icon icon, AttributeSet style) {
  311. SimpleAttributeSet sas = new SimpleAttributeSet();
  312. sas.addAttribute(StyleConstants.NameAttribute, "icon")
  313. StyleConstants.setIcon(sas, icon)
  314. appendOutput(icon.toString(), sas)
  315. }
  316. void appendStacktrace(text) {
  317. def doc = outputArea.styledDocument
  318. // split lines by new line separator
  319. def lines = text.split(/(\n|\r|\r\n|\u0085|\u2028|\u2029)/)
  320. // Java Identifier regex
  321. def ji = /([\p{Alnum}_\$][\p{Alnum}_\$]*)/
  322. // stacktrace line regex
  323. def stacktracePattern = /\tat $ji(\.$ji)+\((($ji(\.(java|groovy))?):(\d+))\)/
  324. lines.each { line ->
  325. int initialLength = doc.length
  326. def matcher = line =~ stacktracePattern
  327. def fileName = matcher.matches() ? matcher[0][-5] : ""
  328. if (fileName == scriptFile?.name || fileName.startsWith(DEFAULT_SCRIPT_NAME_START)) {
  329. def fileNameAndLineNumber = matcher[0][-6]
  330. def length = fileNameAndLineNumber.length()
  331. def index = line.indexOf(fileNameAndLineNumber)
  332. def style = hyperlinkStyle
  333. def hrefAttr = new SimpleAttributeSet()
  334. // don't pass a GString as it won't be coerced to String as addAttribute takes an Object
  335. hrefAttr.addAttribute(HTML.Attribute.HREF, "file://" + fileNameAndLineNumber)
  336. style.addAttribute(HTML.Tag.A, hrefAttr);
  337. doc.insertString(initialLength, line[0..<index], stacktraceStyle)
  338. doc.insertString(initialLength + index, line[index..<(index + length)], style)
  339. doc.insertString(initialLength + index + length, line[(index + length)..-1] + '\n', stacktraceStyle)
  340. } else {
  341. doc.insertString(initialLength, line + '\n', stacktraceStyle)
  342. }
  343. }
  344. ensureNoDocLengthOverflow(doc)
  345. }
  346. // Append a string to the output area on a new line
  347. void appendOutputNl(text, style) {
  348. def doc = outputArea.styledDocument
  349. def len = doc.length
  350. def alreadyNewLine = (len == 0 || doc.getText(len - 1, 1) == "\n")
  351. doc.insertString(doc.length, " \n", style)
  352. if (alreadyNewLine) {
  353. doc.remove(len, 2) // windows hack to fix (improve?) line spacing
  354. }
  355. appendOutput(text, style)
  356. }
  357. void appendOutputLines(text, style) {
  358. appendOutput(text, style)
  359. def doc = outputArea.styledDocument
  360. def len = doc.length
  361. doc.insertString(len, " \n", style)
  362. doc.remove(len, 2) // windows hack to fix (improve?) line spacing
  363. }
  364. // Return false if use elected to cancel
  365. boolean askToSaveFile() {
  366. if (scriptFile == null || !dirty) {
  367. return true
  368. }
  369. switch (JOptionPane.showConfirmDialog(frame,
  370. "Save changes to " + scriptFile.name + "?",
  371. "GroovyConsole", JOptionPane.YES_NO_CANCEL_OPTION))
  372. {
  373. case JOptionPane.YES_OPTION:
  374. return fileSave()
  375. case JOptionPane.NO_OPTION:
  376. return true
  377. default:
  378. return false
  379. }
  380. }
  381. void beep() {
  382. Toolkit.defaultToolkit.beep()
  383. }
  384. // Binds the "_" and "__" variables in the shell
  385. void bindResults() {
  386. shell.setVariable("_", getLastResult()) // lastResult doesn't seem to work
  387. shell.setVariable("__", history.collect {it.result})
  388. }
  389. // Handles menu event
  390. static void captureStdOut(EventObject evt) {
  391. captureStdOut = evt.source.selected
  392. prefs.putBoolean('captureStdOut', captureStdOut)
  393. }
  394. static void captureStdErr(EventObject evt) {
  395. captureStdErr = evt.source.selected
  396. prefs.putBoolean('captureStdErr', captureStdErr)
  397. }
  398. void fullStackTraces(EventObject evt) {
  399. fullStackTraces = evt.source.selected
  400. System.setProperty("groovy.full.stacktrace",
  401. Boolean.toString(fullStackTraces))
  402. prefs.putBoolean('fullStackTraces', fullStackTraces)
  403. }
  404. void showScriptInOutput(EventObject evt) {
  405. showScriptInOutput = evt.source.selected
  406. prefs.putBoolean('showScriptInOutput', showScriptInOutput)
  407. }
  408. void visualizeScriptResults(EventObject evt) {
  409. visualizeScriptResults = evt.source.selected
  410. prefs.putBoolean('visualizeScriptResults', visualizeScriptResults)
  411. }
  412. void showToolbar(EventObject evt) {
  413. showToolbar = evt.source.selected
  414. prefs.putBoolean('showToolbar', showToolbar)
  415. toolbar.visible = showToolbar
  416. }
  417. void detachedOutput(EventObject evt) {
  418. def oldDetachedOutput = detachedOutput
  419. detachedOutput = evt.source.selected
  420. prefs.putBoolean('detachedOutput', detachedOutput)
  421. if (oldDetachedOutput != detachedOutput) {
  422. if (detachedOutput) {
  423. splitPane.add(blank, JSplitPane.BOTTOM)
  424. origDividerSize = splitPane.dividerSize
  425. splitPane.dividerSize = 0
  426. splitPane.resizeWeight = 1.0
  427. outputWindow.add(scrollArea, BorderLayout.CENTER)
  428. prepareOutputWindow()
  429. } else {
  430. splitPane.add(scrollArea, JSplitPane.BOTTOM)
  431. splitPane.dividerSize = origDividerSize
  432. outputWindow.add(blank, BorderLayout.CENTER)
  433. outputWindow.visible = false
  434. splitPane.resizeWeight = 0.5
  435. }
  436. }
  437. }
  438. void autoClearOutput(EventObject evt) {
  439. autoClearOutput = evt.source.selected
  440. prefs.putBoolean('autoClearOutput', autoClearOutput)
  441. }
  442. void threadInterruption(EventObject evt) {
  443. threadInterrupt = evt.source.selected
  444. prefs.putBoolean('threadInterrupt', threadInterrupt)
  445. def customizers = config.compilationCustomizers
  446. customizers.clear()
  447. if (threadInterrupt) {
  448. config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt))
  449. }
  450. }
  451. void caretUpdate(CaretEvent e){
  452. textSelectionStart = Math.min(e.dot,e.mark)
  453. textSelectionEnd = Math.max(e.dot,e.mark)
  454. setRowNumAndColNum()
  455. }
  456. void clearOutput(EventObject evt = null) {
  457. outputArea.text = ''
  458. }
  459. // If at exit time, a script is running, the user is given an option to interrupt it first
  460. def askToInterruptScript() {
  461. if(!scriptRunning) return true
  462. def rc = JOptionPane.showConfirmDialog(frame, "Script executing. Press 'OK' to attempt to interrupt it before exiting.",
  463. "GroovyConsole", JOptionPane.OK_CANCEL_OPTION)
  464. if (rc == JOptionPane.OK_OPTION) {
  465. doInterrupt()
  466. return true
  467. } else {
  468. return false
  469. }
  470. }
  471. void doInterrupt(EventObject evt = null) {
  472. runThread?.interrupt()
  473. }
  474. void exit(EventObject evt = null) {
  475. if(askToInterruptScript()) {
  476. if (askToSaveFile()) {
  477. if (frame instanceof java.awt.Window) {
  478. frame.hide()
  479. frame.dispose()
  480. outputWindow?.dispose()
  481. }
  482. FindReplaceUtility.dispose()
  483. consoleControllers.remove(this)
  484. if (!consoleControllers) {
  485. systemOutInterceptor.stop()
  486. systemErrorInterceptor.stop()
  487. }
  488. }
  489. }
  490. }
  491. void fileNewFile(EventObject evt = null) {
  492. if (askToSaveFile()) {
  493. scriptFile = null
  494. setDirty(false)
  495. inputArea.text = ''
  496. }
  497. }
  498. // Start a new window with a copy of current variables
  499. void fileNewWindow(EventObject evt = null) {
  500. Console consoleController = new Console(
  501. new Binding(
  502. new HashMap(shell.context.variables)))
  503. consoleController.systemOutInterceptor = systemOutInterceptor
  504. consoleController.systemErrorInterceptor = systemErrorInterceptor
  505. SwingBuilder swing = new SwingBuilder()
  506. consoleController.swing = swing
  507. frameConsoleDelegates.each {k, v -> swing[k] = v}
  508. swing.controller = consoleController
  509. swing.build(ConsoleActions)
  510. swing.build(ConsoleView)
  511. installInterceptor()
  512. swing.consoleFrame.pack()
  513. swing.consoleFrame.show()
  514. swing.doLater swing.inputArea.&requestFocus
  515. }
  516. void fileOpen(EventObject evt = null) {
  517. def scriptName = selectFilename()
  518. if (scriptName != null) {
  519. loadScriptFile(scriptName)
  520. }
  521. }
  522. void loadScriptFile(File file) {
  523. swing.edt {
  524. inputArea.editable = false
  525. }
  526. swing.doOutside {
  527. try {
  528. consoleText = file.readLines().join('\n')
  529. scriptFile = file
  530. swing.edt {
  531. updateTitle()
  532. inputArea.document.remove 0, inputArea.document.length
  533. inputArea.document.insertString 0, consoleText, null
  534. setDirty(false)
  535. inputArea.caretPosition = 0
  536. }
  537. } finally {
  538. swing.edt { inputArea.editable = true }
  539. // GROOVY-3684: focus away and then back to inputArea ensures caret blinks
  540. swing.doLater outputArea.&requestFocusInWindow
  541. swing.doLater inputArea.&requestFocusInWindow
  542. }
  543. }
  544. }
  545. // Save file - return false if user cancelled save
  546. boolean fileSave(EventObject evt = null) {
  547. if (scriptFile == null) {
  548. return fileSaveAs(evt)
  549. } else {
  550. scriptFile.write(inputArea.text)
  551. setDirty(false)
  552. return true
  553. }
  554. }
  555. // Save file - return false if user cancelled save
  556. boolean fileSaveAs(EventObject evt = null) {
  557. scriptFile = selectFilename("Save")
  558. if (scriptFile != null) {
  559. scriptFile.write(inputArea.text)
  560. setDirty(false)
  561. return true
  562. } else {
  563. return false
  564. }
  565. }
  566. def finishException(Throwable t, boolean executing) {
  567. if(executing) {
  568. statusLabel.text = 'Execution terminated with exception.'
  569. history[-1].exception = t
  570. } else {
  571. statusLabel.text = 'Compilation failed.'
  572. }
  573. if (t instanceof MultipleCompilationErrorsException) {
  574. MultipleCompilationErrorsException mcee = t
  575. ErrorCollector collector = mcee.errorCollector
  576. int count = collector.errorCount
  577. appendOutputNl("${count} compilation error${count > 1 ? 's' : ''}:\n\n", commandStyle)
  578. collector.errors.each { error ->
  579. if (error instanceof SyntaxErrorMessage) {
  580. SyntaxException se = error.cause
  581. int errorLine = se.line
  582. String message = se.originalMessage
  583. String scriptFileName = scriptFile?.name ?: DEFAULT_SCRIPT_NAME_START
  584. def doc = outputArea.styledDocument
  585. def style = hyperlinkStyle
  586. def hrefAttr = new SimpleAttributeSet()
  587. // don't pass a GString as it won't be coerced to String as addAttribute takes an Object
  588. hrefAttr.addAttribute(HTML.Attribute.HREF, "file://" + scriptFileName + ":" + errorLine)
  589. style.addAttribute(HTML.Tag.A, hrefAttr);
  590. doc.insertString(doc.length, message + " at ", stacktraceStyle)
  591. doc.insertString(doc.length, "line: ${se.line}, column: ${se.startColumn}\n\n", style)
  592. } else if (error instanceof Throwable) {
  593. reportException(error)
  594. } else if (error instanceof ExceptionMessage) {
  595. reportException(error.cause)
  596. }
  597. }
  598. } else {
  599. reportException(t)
  600. }
  601. if(!executing) {
  602. bindResults()
  603. }
  604. // GROOVY-4496: set the output window position to the top-left so the exception details are visible from the start
  605. outputArea.caretPosition = 0
  606. if (detachedOutput) {
  607. prepareOutputWindow()
  608. showOutputWindow()
  609. }
  610. }
  611. private calcPreferredSize(a, b, c) {
  612. [c, [a, b].min()].max()
  613. }
  614. private reportException(Throwable t) {
  615. appendOutputNl("Exception thrown\n", commandStyle)
  616. StringWriter sw = new StringWriter()
  617. new PrintWriter(sw).withWriter {pw -> StackTraceUtils.deepSanitize(t).printStackTrace(pw) }
  618. appendStacktrace("\n${sw.buffer}\n")
  619. }
  620. def finishNormal(Object result) {
  621. // Take down the wait/cancel dialog
  622. history[-1].result = result
  623. if (result != null) {
  624. statusLabel.text = 'Execution complete.'
  625. appendOutputNl("Result: ", promptStyle)
  626. def obj = (visualizeScriptResults
  627. ? OutputTransforms.transformResult(result, shell.context._outputTransforms)
  628. : result.toString())
  629. // multi-methods are magical!
  630. appendOutput(obj, resultStyle)
  631. } else {
  632. statusLabel.text = 'Execution complete. Result was null.'
  633. }
  634. bindResults()
  635. if (detachedOutput) {
  636. prepareOutputWindow()
  637. showOutputWindow()
  638. }
  639. }
  640. def compileFinishNormal() {
  641. statusLabel.text = 'Compilation complete.'
  642. }
  643. private def prepareOutputWindow() {
  644. outputArea.setPreferredSize(null)
  645. outputWindow.pack()
  646. outputArea.setPreferredSize([calcPreferredSize(outputWindow.getWidth(), inputEditor.getWidth(), 120),
  647. calcPreferredSize(outputWindow.getHeight(), inputEditor.getHeight(), 60)] as Dimension)
  648. outputWindow.pack()
  649. }
  650. // Gets the last, non-null result
  651. def getLastResult() {
  652. // runtime bugs in here history.reverse produces odd lookup
  653. // return history.reverse.find {it != null}
  654. if (!history) {
  655. return
  656. }
  657. for (i in (history.size() - 1)..0) {
  658. if (history[i].result != null) {
  659. return history[i].result
  660. }
  661. }
  662. return null
  663. }
  664. void historyNext(EventObject evt = null) {
  665. if (historyIndex < history.size()) {
  666. setInputTextFromHistory(historyIndex + 1)
  667. } else {
  668. statusLabel.text = "Can't go past end of history (time travel not allowed)"
  669. beep()
  670. }
  671. }
  672. void historyPrev(EventObject evt = null) {
  673. if (historyIndex > 0) {
  674. setInputTextFromHistory(historyIndex - 1)
  675. } else {
  676. statusLabel.text = "Can't go past start of history"
  677. beep()
  678. }
  679. }
  680. void inspectLast(EventObject evt = null){
  681. if (null == lastResult) {
  682. JOptionPane.showMessageDialog(frame, "The last result is null.",
  683. "Cannot Inspect", JOptionPane.INFORMATION_MESSAGE)
  684. return
  685. }
  686. ObjectBrowser.inspect(lastResult)
  687. }
  688. void inspectVariables(EventObject evt = null) {
  689. ObjectBrowser.inspect(shell.context.variables)
  690. }
  691. void inspectAst(EventObject evt = null) {
  692. new AstBrowser(inputArea, rootElement, shell.getClassLoader()).run({ inputArea.getText() } )
  693. }
  694. void largerFont(EventObject evt = null) {
  695. updateFontSize(inputArea.font.size + 2)
  696. }
  697. static boolean notifySystemOut(String str) {
  698. if (!captureStdOut) {
  699. // Output as normal
  700. return true
  701. }
  702. // Put onto GUI
  703. if (EventQueue.isDispatchThread()) {
  704. consoleControllers.each {it.appendOutputLines(str, it.outputStyle)}
  705. }
  706. else {
  707. SwingUtilities.invokeLater {
  708. consoleControllers.each {it.appendOutputLines(str, it.outputStyle)}
  709. }
  710. }
  711. return false
  712. }
  713. static boolean notifySystemErr(String str) {
  714. if (!captureStdErr) {
  715. // Output as normal
  716. return true
  717. }
  718. // Put onto GUI
  719. if (EventQueue.isDispatchThread()) {
  720. consoleControllers.each {it.appendStacktrace(str)}
  721. }
  722. else {
  723. SwingUtilities.invokeLater {
  724. consoleControllers.each {it.appendStacktrace(str)}
  725. }
  726. }
  727. return false
  728. }
  729. // actually run the script
  730. void runScript(EventObject evt = null) {
  731. runScriptImpl(false)
  732. }
  733. void runSelectedScript(EventObject evt = null) {
  734. runScriptImpl(true)
  735. }
  736. void addClasspathJar(EventObject evt = null) {
  737. def fc = new JFileChooser(currentClasspathJarDir)
  738. fc.fileSelectionMode = JFileChooser.FILES_ONLY
  739. fc.acceptAllFileFilterUsed = true
  740. if (fc.showDialog(frame, "Add") == JFileChooser.APPROVE_OPTION) {
  741. currentClasspathJarDir = fc.currentDirectory
  742. Preferences.userNodeForPackage(Console).put('currentClasspathJarDir', currentClasspathJarDir.path)
  743. shell.getClassLoader().addURL(fc.selectedFile.toURL())
  744. }
  745. }
  746. void addClasspathDir(EventObject evt = null) {
  747. def fc = new JFileChooser(currentClasspathDir)
  748. fc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
  749. fc.acceptAllFileFilterUsed = true
  750. if (fc.showDialog(frame, "Add") == JFileChooser.APPROVE_OPTION) {
  751. currentClasspathDir = fc.currentDirectory
  752. Preferences.userNodeForPackage(Console).put('currentClasspathDir', currentClasspathDir.path)
  753. shell.getClassLoader().addURL(fc.selectedFile.toURL())
  754. }
  755. }
  756. void clearContext(EventObject evt = null) {
  757. def binding = new Binding()
  758. newScript(null, binding)
  759. // reload output transforms
  760. binding.variables._outputTransforms = OutputTransforms.loadOutputTransforms()
  761. }
  762. private void runScriptImpl(boolean selected) {
  763. if(scriptRunning) {
  764. statusLabel.text = 'Cannot run script now as a script is already running. Please wait or use "Interrupt Script" option.'
  765. return
  766. }
  767. scriptRunning = true
  768. interruptAction.enabled = true
  769. stackOverFlowError = false // reset this flag before running a script
  770. def endLine = System.getProperty('line.separator')
  771. def record = new HistoryRecord( allText: inputArea.getText().replaceAll(endLine, '\n'),
  772. selectionStart: textSelectionStart, selectionEnd: textSelectionEnd)
  773. addToHistory(record)
  774. pendingRecord = new HistoryRecord(allText:'', selectionStart:0, selectionEnd:0)
  775. if (prefs.getBoolean("autoClearOutput", false)) clearOutput()
  776. // Print the input text
  777. if (showScriptInOutput) {
  778. for (line in record.getTextToRun(selected).tokenize("\n")) {
  779. appendOutputNl('groovy> ', promptStyle)
  780. appendOutput(line, commandStyle)
  781. }
  782. appendOutputNl(" \n", promptStyle)
  783. }
  784. // Kick off a new thread to do the evaluation
  785. // Run in a thread outside of EDT, this method is usually called inside the EDT
  786. runThread = Thread.start {
  787. try {
  788. SwingUtilities.invokeLater { showExecutingMessage() }
  789. String name = scriptFile?.name ?: (DEFAULT_SCRIPT_NAME_START + scriptNameCounter++)
  790. if(beforeExecution) {
  791. beforeExecution()
  792. }
  793. def result = shell.run(record.getTextToRun(selected), name, [])
  794. if(afterExecution) {
  795. afterExecution()
  796. }
  797. SwingUtilities.invokeLater { finishNormal(result) }
  798. } catch (Throwable t) {
  799. if(t instanceof StackOverflowError) {
  800. // set the flag that will be used in printing exception details in output pane
  801. stackOverFlowError = true
  802. clearOutput()
  803. }
  804. SwingUtilities.invokeLater { finishException(t, true) }
  805. } finally {
  806. runThread = null
  807. scriptRunning = false
  808. interruptAction.enabled = false
  809. }
  810. }
  811. }
  812. void compileScript(EventObject evt = null) {
  813. if(scriptRunning) {
  814. statusLabel.text = 'Cannot compile script now as a script is already running. Please wait or use "Interrupt Script" option.'
  815. return
  816. }
  817. stackOverFlowError = false // reset this flag before running a script
  818. def endLine = System.getProperty('line.separator')
  819. def record = new HistoryRecord( allText: inputArea.getText().replaceAll(endLine, '\n'),
  820. selectionStart: textSelectionStart, selectionEnd: textSelectionEnd)
  821. if (prefs.getBoolean("autoClearOutput", false)) clearOutput()
  822. // Print the input text
  823. if (showScriptInOutput) {
  824. for (line in record.allText.tokenize("\n")) {
  825. appendOutputNl('groovy> ', promptStyle)
  826. appendOutput(line, commandStyle)
  827. }
  828. appendOutputNl(" \n", promptStyle)
  829. }
  830. // Kick off a new thread to do the compilation
  831. // Run in a thread outside of EDT, this method is usually called inside the EDT
  832. runThread = Thread.start {
  833. try {
  834. SwingUtilities.invokeLater { showCompilingMessage() }
  835. shell.parse(record.allText)
  836. SwingUtilities.invokeLater { compileFinishNormal() }
  837. } catch (Throwable t) {
  838. SwingUtilities.invokeLater { finishException(t, false) }
  839. } finally {
  840. runThread = null
  841. }
  842. }
  843. }
  844. def selectFilename(name = "Open") {
  845. def fc = new JFileChooser(currentFileChooserDir)
  846. fc.fileSelectionMode = JFileChooser.FILES_ONLY
  847. fc.acceptAllFileFilterUsed = true
  848. fc.fileFilter = groovyFileFilter
  849. if(name == "Save") {
  850. fc.selectedFile = new File("*.groovy")
  851. }
  852. if (fc.showDialog(frame, name) == JFileChooser.APPROVE_OPTION) {
  853. currentFileChooserDir = fc.currentDirectory
  854. Preferences.userNodeForPackage(Console).put('currentFileChooserDir', currentFileChooserDir.path)
  855. return fc.selectedFile
  856. } else {
  857. return null
  858. }
  859. }
  860. void setDirty(boolean newDirty) {
  861. //TODO when @BoundProperty is live, this should be handled via listeners
  862. dirty = newDirty
  863. saveAction.enabled = newDirty
  864. updateTitle()
  865. }
  866. private void setInputTextFromHistory(newIndex) {
  867. def endLine = System.getProperty('line.separator')
  868. if (historyIndex >= history.size()) {
  869. pendingRecord = new HistoryRecord( allText: inputArea.getText().replaceAll(endLine, '\n'),
  870. selectionStart: textSelectionStart, selectionEnd: textSelectionEnd)
  871. }
  872. historyIndex = newIndex
  873. def record
  874. if (historyIndex < history.size()) {
  875. record = history[historyIndex]
  876. statusLabel.text = "command history ${history.size() - historyIndex}"
  877. } else {
  878. record = pendingRecord
  879. statusLabel.text = 'at end of history'
  880. }
  881. inputArea.text = record.allText
  882. inputArea.selectionStart = record.selectionStart
  883. inputArea.selectionEnd = record.selectionEnd
  884. setDirty(true) // Should calculate dirty flag properly (hash last saved/read text in each file)
  885. updateHistoryActions()
  886. }
  887. private void updateHistoryActions() {
  888. nextHistoryAction.enabled = historyIndex < history.size()
  889. prevHistoryAction.enabled = historyIndex > 0
  890. }
  891. // Adds a variable to the binding
  892. // Useful for adding variables before opening the console
  893. void setVariable(String name, Object value) {
  894. shell.context.setVariable(name, value)
  895. }
  896. void showAbout(EventObject evt = null) {
  897. def version = GroovySystem.getVersion()
  898. def pane = swing.optionPane()
  899. // work around GROOVY-1048
  900. pane.setMessage('Welcome to the Groovy Console for evaluating Groovy scripts\nVersion ' + version)
  901. def dialog = pane.createDialog(frame, 'About GroovyConsole')
  902. dialog.show()
  903. }
  904. void find(EventObject evt = null) {
  905. FindReplaceUtility.showDialog()
  906. }
  907. void findNext(EventObject evt = null) {
  908. FindReplaceUtility.FIND_ACTION.actionPerformed(evt)
  909. }
  910. void findPrevious(EventObject evt = null) {
  911. def reverseEvt = new ActionEvent(
  912. evt.getSource(), evt.getID(),
  913. evt.getActionCommand(), evt.getWhen(),
  914. ActionEvent.SHIFT_MASK) //reverse
  915. FindReplaceUtility.FIND_ACTION.actionPerformed(reverseEvt)
  916. }
  917. void replace(EventObject evt = null) {
  918. FindReplaceUtility.showDialog(true)
  919. }
  920. void showMessage(String message) {
  921. statusLabel.text = message
  922. }
  923. void showExecutingMessage() {
  924. statusLabel.text = 'Script executing now. Please wait or use "Interrupt Script" option.'
  925. }
  926. void showCompilingMessage() {
  927. statusLabel.text = 'Script compiling now. Please wait.'
  928. }
  929. // Shows the detached 'outputArea' dialog
  930. void showOutputWindow(EventObject evt = null) {
  931. if (detachedOutput) {
  932. outputWindow.setLocationRelativeTo(frame)
  933. outputWindow.show()
  934. }
  935. }
  936. void hideOutputWindow(EventObject evt = null) {
  937. if (detachedOutput) {
  938. outputWindow.visible = false
  939. }
  940. }
  941. void hideAndClearOutputWindow(EventObject evt = null) {
  942. clearOutput()
  943. hideOutputWindow()
  944. }
  945. void smallerFont(EventObject evt = null){
  946. updateFontSize(inputArea.font.size - 2)
  947. }
  948. void updateTitle() {
  949. if (frame.properties.containsKey('title')) {
  950. if (scriptFile != null) {
  951. frame.title = scriptFile.name + (dirty?" * ":"") + " - GroovyConsole"
  952. } else {
  953. frame.title = "GroovyConsole"
  954. }
  955. }
  956. }
  957. private updateFontSize(newFontSize) {
  958. if (newFontSize > 40) {
  959. newFontSize = 40
  960. } else if (newFontSize < 4) {
  961. newFontSize = 4
  962. }
  963. prefs.putInt("fontSize", newFontSize)
  964. // don't worry, the fonts won't be changed to this family, the styles will only derive from this
  965. def newFont = new Font(inputEditor.defaultFamily, Font.PLAIN, newFontSize)
  966. inputArea.font = newFont
  967. outputArea.font = newFont
  968. }
  969. void invokeTextAction(evt, closure, area = inputArea) {
  970. def source = evt.getSource()
  971. if (source != null) {
  972. closure(area)
  973. }
  974. }
  975. void cut(EventObject evt = null) {
  976. invokeTextAction(evt, { source -> source.cut() })
  977. }
  978. void copy(EventObject evt = null) {
  979. invokeTextAction(evt, { source -> source.copy() }, copyFromComponent ?: inputArea)
  980. }
  981. void paste(EventObject evt = null) {
  982. invokeTextAction(evt, { source -> source.paste() })
  983. }
  984. void selectAll(EventObject evt = null) {
  985. invokeTextAction(evt, { source -> source.selectAll() })
  986. }
  987. void setRowNumAndColNum() {
  988. cursorPos = inputArea.getCaretPosition()
  989. rowNum = rootElement.getElementIndex(cursorPos) + 1
  990. def rowElement = rootElement.getElement(rowNum - 1)
  991. colNum = cursorPos - rowElement.getStartOffset() + 1
  992. rowNumAndColNum.setText("$rowNum:$colNum")
  993. }
  994. void print(EventObject evt = null) {
  995. inputEditor.printAction.actionPerformed(evt)
  996. }
  997. void undo(EventObject evt = null) {
  998. inputEditor.undoAction.actionPerformed(evt)
  999. }
  1000. void redo(EventObject evt = null) {
  1001. inputEditor.redoAction.actionPerformed(evt)
  1002. }
  1003. void hyperlinkUpdate(HyperlinkEvent e) {
  1004. if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) {
  1005. // URL of the form: file://myscript.groovy:32
  1006. String url = e.getURL()
  1007. int lineNumber = url[(url.lastIndexOf(':') + 1)..-1].toInteger()
  1008. def editor = inputEditor.textEditor
  1009. def text = editor.text
  1010. int newlineBefore = 0
  1011. int newlineAfter = 0
  1012. int currentLineNumber = 1
  1013. // let's find the previous and next newline surrounding the offending line
  1014. int i = 0
  1015. for (ch in text) {
  1016. if (ch == '\n') {
  1017. currentLineNumber++
  1018. }
  1019. if (currentLineNumber == lineNumber) {
  1020. newlineBefore = i
  1021. def nextNewline = text.indexOf('\n', i + 1)
  1022. newlineAfter = nextNewline > -1 ? nextNewline : text.length()
  1023. break
  1024. }
  1025. i++
  1026. }
  1027. // highlight / select the whole line
  1028. editor.setCaretPosition(newlineBefore)
  1029. editor.moveCaretPosition(newlineAfter)
  1030. }
  1031. }
  1032. void componentHidden(ComponentEvent e) { }
  1033. void componentMoved(ComponentEvent e) { }
  1034. void componentResized(ComponentEvent e) {
  1035. def component = e.getComponent()
  1036. prefs.putInt("${component.name}Width", component.width)
  1037. prefs.putInt("${component.name}Height", component.height)
  1038. }
  1039. public void componentShown(ComponentEvent e) { }
  1040. public void focusGained(FocusEvent e) {
  1041. // remember component with focus for text-copy functionality
  1042. if (e.component == outputArea || e.component == inputArea) {
  1043. copyFromComponent = e.component
  1044. }
  1045. }
  1046. public void focusLost(FocusEvent e) { }
  1047. }
  1048. class GroovyFileFilter extends FileFilter {
  1049. private static final GROOVY_SOURCE_EXTENSIONS = ['*.groovy', '*.gvy', '*.gy', '*.gsh', '*.story', '*.gpp', '*.grunit']
  1050. private static final GROOVY_SOURCE_EXT_DESC = GROOVY_SOURCE_EXTENSIONS.join(',')
  1051. public boolean accept(File f) {
  1052. if (f.isDirectory()) {
  1053. return true
  1054. }
  1055. GROOVY_SOURCE_EXTENSIONS.find {it == getExtension(f)} ? true : false
  1056. }
  1057. public String getDescription() {
  1058. "Groovy Source Files ($GROOVY_SOURCE_EXT_DESC)"
  1059. }
  1060. static String getExtension(f) {
  1061. def ext = null;
  1062. def s = f.getName()
  1063. def i = s.lastIndexOf('.')
  1064. if (i > 0 && i < s.length() - 1) {
  1065. ext = s.substring(i).toLowerCase()
  1066. }
  1067. "*$ext"
  1068. }
  1069. }