You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/04/27 14:30:31 UTC
(groovy) branch master updated: Add missing Javadoc for groovy-typecheckers,groovy-console,groovy-cli-commons,groovy-cli-picocli
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new f405f5b7d5 Add missing Javadoc for groovy-typecheckers,groovy-console,groovy-cli-commons,groovy-cli-picocli
f405f5b7d5 is described below
commit f405f5b7d57c557e3b4b4daa79205aef3a827fdb
Author: Daniel Sun <su...@apache.org>
AuthorDate: Mon Apr 27 23:30:14 2026 +0900
Add missing Javadoc for groovy-typecheckers,groovy-console,groovy-cli-commons,groovy-cli-picocli
---
.../groovy/groovy/cli/commons/CliBuilder.groovy | 38 +++
.../groovy/cli/commons/OptionAccessor.groovy | 72 +++++
.../groovy/groovy/cli/picocli/CliBuilder.groovy | 24 +-
.../groovy/cli/picocli/OptionAccessor.groovy | 77 +++++
.../src/main/groovy/groovy/console/TextNode.groovy | 36 ++-
.../groovy/groovy/console/TextTreeNodeMaker.groovy | 19 +-
.../groovy/groovy/console/ui/AstBrowser.groovy | 199 +++++++++++++
.../console/ui/AstNodeToScriptAdapter.groovy | 53 +++-
.../console/ui/ButtonOrDefaultRenderer.groovy | 3 +
.../groovy/console/ui/ButtonOrTextEditor.groovy | 6 +
.../console/ui/CellValueToolTipJTable.groovy | 6 +
.../main/groovy/groovy/console/ui/Console.groovy | 331 ++++++++++++++++++---
.../groovy/console/ui/ConsoleIvyPlugin.groovy | 8 +
.../groovy/console/ui/ConsoleMavenPlugin.groovy | 9 +-
.../groovy/console/ui/ConsolePreferences.groovy | 28 +-
.../groovy/groovy/console/ui/ConsoleSupport.java | 45 ++-
.../groovy/console/ui/ConsoleTextEditor.java | 59 +++-
.../groovy/groovy/console/ui/HistoryRecord.groovy | 20 ++
.../src/main/groovy/groovy/console/ui/Icons.groovy | 44 +++
.../groovy/groovy/console/ui/ObjectBrowser.groovy | 40 ++-
.../groovy/console/ui/OutputTransforms.groovy | 16 +
.../console/ui/ScriptToTreeNodeAdapter.groovy | 69 +++++
.../groovy/groovy/console/ui/ThemeManager.groovy | 25 +-
.../groovy/console/ui/text/AutoIndentAction.groovy | 11 +
.../groovy/console/ui/text/FindReplaceUtility.java | 40 +++
.../groovy/console/ui/text/GroovyFilter.java | 67 +++++
.../console/ui/text/MatchingHighlighter.java | 12 +
.../console/ui/text/SmartDocumentFilter.java | 38 +++
.../ui/text/StructuredSyntaxDocumentFilter.java | 118 ++++++--
.../console/ui/text/StructuredSyntaxHandler.java | 127 +++++++-
.../console/ui/text/StructuredSyntaxResources.java | 33 ++
.../groovy/groovy/console/ui/text/TextEditor.java | 61 ++++
.../groovy/console/ui/text/TextUndoManager.java | 46 +++
.../groovy/console/ui/view/BasicContentPane.groovy | 14 +
.../groovy/console/ui/view/BasicMenuBar.groovy | 3 +
.../groovy/console/ui/view/BasicStatusBar.groovy | 3 +
.../groovy/console/ui/view/BasicToolBar.groovy | 1 +
.../groovy/console/ui/view/GTKDefaults.groovy | 1 +
.../groovy/console/ui/view/MacOSXDefaults.groovy | 3 +-
.../groovy/console/ui/view/MacOSXMenuBar.groovy | 6 +
.../groovy/typecheckers/FormatStringChecker.groovy | 10 +
.../groovy/typecheckers/ModifiesChecker.groovy | 3 +
.../groovy/groovy/typecheckers/NullChecker.groovy | 3 +
.../groovy/typecheckers/PurityChecker.groovy | 3 +
.../groovy/groovy/typecheckers/RegexChecker.groovy | 3 +
.../groovy/typecheckers/CheckingVisitor.groovy | 25 ++
46 files changed, 1773 insertions(+), 85 deletions(-)
diff --git a/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/CliBuilder.groovy b/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/CliBuilder.groovy
index 4f9cba80ef..4849b7d82c 100644
--- a/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/CliBuilder.groovy
+++ b/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/CliBuilder.groovy
@@ -302,8 +302,19 @@ class CliBuilder {
*/
Options options = new Options()
+ /**
+ * Typed option metadata keyed by long name or short name.
+ */
Map<String, TypedOption> savedTypeOptions = new HashMap<String, TypedOption>()
+ /**
+ * Creates and registers a typed option definition.
+ *
+ * @param args option attributes using the standard DSL keys
+ * @param type the target type for the option value
+ * @param description the option description
+ * @return the created typed option
+ */
public <T> TypedOption<T> option(Map args, Class<T> type, String description) {
def name = args.opt ?: '_'
args.type = type
@@ -406,6 +417,12 @@ class CliBuilder {
optionInstance
}
+ /**
+ * Adds options declared with {@link Option} annotations to this builder.
+ *
+ * @param optionClass the annotated class or interface
+ * @param namesAreSetters whether annotated methods should be treated as setters
+ */
void addOptionsFromAnnotations(Class optionClass, boolean namesAreSetters) {
optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method m ->
Annotation annotation = m.getAnnotation(Option)
@@ -506,6 +523,14 @@ class CliBuilder {
result
}
+ /**
+ * Applies parsed option values to an annotated target object or map.
+ *
+ * @param cli the parsed options accessor
+ * @param optionClass the annotated class or interface
+ * @param t the target object or backing map
+ * @param namesAreSetters whether annotated methods should be treated as setters
+ */
def setOptionsFromAnnotations(def cli, Class optionClass, Object t, boolean namesAreSetters) {
optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method m ->
Annotation annotation = m.getAnnotation(Option)
@@ -611,6 +636,12 @@ class CliBuilder {
return option
}
+ /**
+ * Normalizes DSL option details into Commons CLI-compatible values.
+ *
+ * @param m the raw option details
+ * @return the normalized option details
+ */
static Map adjustDetails(Map m) {
m.collectMany { k, v ->
if (k == 'args' && v == '+') {
@@ -626,6 +657,13 @@ class CliBuilder {
}.sum()
}
+ /**
+ * Expands {@code @file} arguments using command argument file semantics.
+ *
+ * @param args the original command-line arguments
+ * @return the arguments with any argument files expanded
+ * @throws IOException if an argument file cannot be read
+ */
static expandArgumentFiles(args) throws IOException {
def result = []
for (arg in args) {
diff --git a/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/OptionAccessor.groovy b/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/OptionAccessor.groovy
index f2dd92c04a..685e5924dd 100644
--- a/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/OptionAccessor.groovy
+++ b/subprojects/groovy-cli-commons/src/main/groovy/groovy/cli/commons/OptionAccessor.groovy
@@ -26,28 +26,68 @@ import org.codehaus.groovy.runtime.StringGroovyMethods
import java.lang.reflect.Array
+/**
+ * Provides typed and dynamic access to parsed command-line options.
+ */
class OptionAccessor {
+ /**
+ * The parsed command line.
+ */
CommandLine commandLine
+
+ /**
+ * Typed option metadata keyed by long name or short name.
+ */
Map<String, TypedOption> savedTypeOptions
+ /**
+ * Creates an accessor for the supplied parsed command line.
+ *
+ * @param commandLine the parsed command line
+ */
OptionAccessor(CommandLine commandLine) {
this.commandLine = commandLine
}
+ /**
+ * Checks whether the supplied typed option was specified.
+ *
+ * @param typedOption the option metadata
+ * @return {@code true} if the option is present
+ */
boolean hasOption(TypedOption typedOption) {
commandLine.hasOption(typedOption.longOpt ?: typedOption.opt)
}
+ /**
+ * Returns the converted default value for the named option.
+ *
+ * @param name the option name
+ * @return the converted default value, or {@code null} if none is defined
+ */
public <T> T defaultValue(String name) {
Class<T> type = savedTypeOptions[name]?.type
String value = savedTypeOptions[name]?.defaultValue() ? savedTypeOptions[name].defaultValue() : null
return (T) value ? getTypedValue(type, name, value) : null
}
+ /**
+ * Returns the typed value for the supplied option.
+ *
+ * @param typedOption the option metadata
+ * @return the converted option value, or {@code null} if the option is absent
+ */
public <T> T getOptionValue(TypedOption<T> typedOption) {
getOptionValue(typedOption, null)
}
+ /**
+ * Returns the typed value for the supplied option, falling back to the given default.
+ *
+ * @param typedOption the option metadata
+ * @param defaultValue the value to return when the option is absent
+ * @return the converted option value or {@code defaultValue}
+ */
public <T> T getOptionValue(TypedOption<T> typedOption, T defaultValue) {
String optionName = (String) typedOption.longOpt ?: typedOption.opt
if (commandLine.hasOption(optionName)) {
@@ -79,10 +119,24 @@ class OptionAccessor {
return result
}
+ /**
+ * Returns the typed value for the supplied option using subscript syntax.
+ *
+ * @param typedOption the option metadata
+ * @return the converted option value, or {@code null} if the option is absent
+ */
public <T> T getAt(TypedOption<T> typedOption) {
getAt(typedOption, null)
}
+ /**
+ * Returns the typed value for the supplied option using subscript syntax,
+ * falling back to the given default.
+ *
+ * @param typedOption the option metadata
+ * @param defaultValue the value to return when the option is absent
+ * @return the converted option value or {@code defaultValue}
+ */
public <T> T getAt(TypedOption<T> typedOption, T defaultValue) {
String optionName = (String) typedOption.longOpt ?: typedOption.opt
if (savedTypeOptions.containsKey(optionName)) {
@@ -118,10 +172,23 @@ class OptionAccessor {
StringGroovyMethods.asType(optionValue, (Class<T>) type)
}
+ /**
+ * Delegates unknown method calls to the underlying {@link CommandLine}.
+ *
+ * @param name the method name
+ * @param args the method arguments
+ * @return the delegated result
+ */
def invokeMethod(String name, Object args) {
return InvokerHelper.getMetaClass(commandLine).invokeMethod(commandLine, name, args)
}
+ /**
+ * Supports property-style access to option values and flags.
+ *
+ * @param name the property name
+ * @return the option value, default value, or flag state
+ */
def getProperty(String name) {
if (!savedTypeOptions.containsKey(name)) {
def alt = savedTypeOptions.find{ it.value.opt == name }
@@ -157,6 +224,11 @@ class OptionAccessor {
return result
}
+ /**
+ * Returns the remaining non-option arguments.
+ *
+ * @return the remaining arguments
+ */
List<String> arguments() {
commandLine.args.toList()
}
diff --git a/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/CliBuilder.groovy b/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/CliBuilder.groovy
index bf114f06ef..246d2e91b4 100644
--- a/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/CliBuilder.groovy
+++ b/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/CliBuilder.groovy
@@ -544,6 +544,14 @@ class CliBuilder {
this.errorWriter = writer
}
+ /**
+ * Defines an option with an explicit value type using the dynamic builder API.
+ *
+ * @param args the option attributes
+ * @param type the type used to convert option values
+ * @param description the usage text for the option
+ * @return the created typed option descriptor
+ */
public <T> TypedOption<T> option(Map args, Class<T> type, String description) {
def name = args.opt ?: '_'
args.type = type
@@ -609,6 +617,9 @@ class CliBuilder {
/**
* Make options accessible from command line args with parser.
* Returns null on bad command lines after displaying usage message.
+ *
+ * @param args the command line arguments to parse
+ * @return an accessor for the parsed options, or {@code null} if parsing failed
*/
OptionAccessor parse(args) {
CommandLine commandLine = createCommandLine()
@@ -649,8 +660,8 @@ class CliBuilder {
* Given an interface containing members with annotations, derive
* the options specification.
*
- * @param optionsClass
- * @param args
+ * @param optionsClass the annotated interface describing the supported options
+ * @param args the command line arguments to parse
* @return an instance containing the processed options
*/
public <T> T parseFromSpec(Class<T> optionsClass, String[] args) {
@@ -666,8 +677,8 @@ class CliBuilder {
* Given an instance containing members with annotations, derive
* the options specification.
*
- * @param optionInstance
- * @param args
+ * @param optionInstance the annotated instance to populate
+ * @param args the command line arguments to parse
* @return the options instance populated with the processed options
*/
public <T> T parseFromInstance(T optionInstance, args) {
@@ -897,6 +908,11 @@ class CliBuilder {
// implementation details -------------------------------------
/**
* Internal method: How to create an OptionSpec from the specification.
+ *
+ * @param shortname the short option name, or {@code _} for long-only options
+ * @param details the option attributes
+ * @param description the usage text for the option
+ * @return the created picocli option specification
*/
OptionSpec option(shortname, Map details, description) {
OptionSpec.Builder builder
diff --git a/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/OptionAccessor.groovy b/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/OptionAccessor.groovy
index 4f9f2a4810..c2303df5b2 100644
--- a/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/OptionAccessor.groovy
+++ b/subprojects/groovy-cli-picocli/src/main/groovy/groovy/cli/picocli/OptionAccessor.groovy
@@ -24,28 +24,68 @@ import org.codehaus.groovy.runtime.StringGroovyMethods
import picocli.CommandLine.Model.OptionSpec
import picocli.CommandLine.ParseResult
+/**
+ * Provides typed and dynamic access to values produced by {@link CliBuilder#parse(Object)}.
+ */
class OptionAccessor {
+ /**
+ * The underlying picocli parse result.
+ */
ParseResult parseResult
+
+ /**
+ * Type metadata for options declared by the originating {@link CliBuilder}.
+ */
Map<String, TypedOption> savedTypeOptions
+ /**
+ * Creates an accessor for the specified parse result.
+ *
+ * @param parseResult the parsed command line state
+ */
OptionAccessor(ParseResult parseResult) {
this.parseResult = parseResult
}
+ /**
+ * Checks whether the specified typed option was matched.
+ *
+ * @param typedOption the option to inspect
+ * @return {@code true} if the option was supplied on the command line
+ */
boolean hasOption(TypedOption typedOption) {
parseResult.hasMatchedOption(typedOption.longOpt ?: typedOption.opt as String)
}
+ /**
+ * Returns the converted default value for a named option.
+ *
+ * @param name the option name used to register the option metadata
+ * @return the converted default value, or {@code null} if none was declared
+ */
public <T> T defaultValue(String name) {
Class<T> type = savedTypeOptions[name]?.type
String value = savedTypeOptions[name]?.defaultValue() ? savedTypeOptions[name].defaultValue() : null
return (T) value ? getTypedValue(type, name, value) : null
}
+ /**
+ * Returns the value of the specified typed option.
+ *
+ * @param typedOption the option to inspect
+ * @return the parsed or configured value, or {@code null} if none is available
+ */
public <T> T getOptionValue(TypedOption<T> typedOption) {
getOptionValue(typedOption, null)
}
+ /**
+ * Returns the value of the specified typed option.
+ *
+ * @param typedOption the option to inspect
+ * @param defaultValue the fallback value to use when the option has no value
+ * @return the parsed value, configured default, or supplied fallback
+ */
public <T> T getOptionValue(TypedOption<T> typedOption, T defaultValue) {
String optionName = (String) typedOption.longOpt ?: typedOption.opt
if (parseResult.hasMatchedOption(optionName)) {
@@ -56,10 +96,23 @@ class OptionAccessor {
}
}
+ /**
+ * Returns the value of the specified typed option using subscript syntax.
+ *
+ * @param typedOption the option to inspect
+ * @return the parsed or configured value, or {@code null} if none is available
+ */
public <T> T getAt(TypedOption<T> typedOption) {
getAt(typedOption, null)
}
+ /**
+ * Returns the value of the specified typed option using subscript syntax.
+ *
+ * @param typedOption the option to inspect
+ * @param defaultValue the fallback value to use when the option has no value
+ * @return the parsed value, configured default, or supplied fallback
+ */
public <T> T getAt(TypedOption<T> typedOption, T defaultValue) {
getOptionValue(typedOption, defaultValue)
}
@@ -85,6 +138,12 @@ class OptionAccessor {
StringGroovyMethods.asType(optionValue, (Class<T>) type)
}
+ /**
+ * Returns properties collected for a map-style option.
+ *
+ * @param name the option name
+ * @return the parsed properties, or {@code null} if the option was not matched
+ */
Properties getOptionProperties(String name) {
if (!parseResult.hasMatchedOption(name)) {
return null
@@ -95,6 +154,13 @@ class OptionAccessor {
result
}
+ /**
+ * Provides limited Apache Commons CLI compatibility by delegating selected method calls to the underlying parse result.
+ *
+ * @param name the method name to invoke
+ * @param args the method arguments
+ * @return the delegated call result
+ */
def invokeMethod(String name, Object args) {
// TODO we could just declare normal methods to map commons-cli CommandLine methods to picocli ParseResult methods
if (name == 'hasOption') { name = 'hasMatchedOption'; args = [args[0] ].toArray() }
@@ -102,6 +168,12 @@ class OptionAccessor {
return InvokerHelper.getMetaClass(parseResult).invokeMethod(parseResult, name, args)
}
+ /**
+ * Resolves dynamic property lookups to parsed option values, defaults, or multi-value views.
+ *
+ * @param name the property name to resolve
+ * @return the resolved value, or {@code false} when no matching option exists
+ */
def getProperty(String name) {
if (name == 'parseResult') { return parseResult }
if (parseResult.hasMatchedOption(name)) {
@@ -150,6 +222,11 @@ class OptionAccessor {
false
}
+ /**
+ * Returns the remaining positional arguments.
+ *
+ * @return the unmatched positional arguments in encounter order
+ */
List<String> arguments() {
parseResult.hasMatchedPositional(0) ? parseResult.matchedPositional(0).stringValues() : []
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy
index 8621cf5284..753a6028a4 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/TextNode.groovy
@@ -16,38 +16,68 @@
* specific language governing permissions and limitations
* under the License.
*/
-/**
- * Represents a plain text node for use in the AST tree made by ASTBrowser
- */
package groovy.console
import groovy.transform.CompileStatic
+/**
+ * Represents a plain text node for use in the AST tree made by ASTBrowser.
+ */
@CompileStatic
class TextNode {
+ /** Value displayed for this node. */
Object userObject
+ /** Additional name/value metadata shown for this node. */
List<List<?>> properties
+ /** Parent node in the text tree. */
TextNode parent
+ /** Child nodes in insertion order. */
List children
+ /**
+ * Creates a node with the supplied display value.
+ *
+ * @param userObject value displayed for the node
+ */
TextNode(Object userObject) {
this.userObject = userObject
children = new ArrayList<TextNode>()
}
+ /**
+ * Creates a node with the supplied display value and metadata.
+ *
+ * @param userObject value displayed for the node
+ * @param properties metadata associated with the node
+ */
TextNode(Object userObject, List<List<?>> properties) {
this(userObject)
this.properties = properties
}
+ /**
+ * Adds a child node.
+ *
+ * @param child child node to append
+ */
void add(TextNode child) {
children << child
}
+ /**
+ * Sets the parent node.
+ *
+ * @param newParent parent node reference
+ */
void setParent(TextNode newParent) {
parent = newParent
}
+ /**
+ * Returns the display text for this node.
+ *
+ * @return the node text
+ */
String toString() {
userObject ? userObject.toString() : 'null'
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy
index 9b6cd46ada..503a03736b 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/TextTreeNodeMaker.groovy
@@ -16,20 +16,33 @@
* specific language governing permissions and limitations
* under the License.
*/
-/**
- * A factory class for text nodes for use in the AST tree made by ASTBrowser
- */
package groovy.console
import groovy.console.ui.AstBrowserNodeMaker
import groovy.transform.CompileStatic
+/**
+ * Factory for the text nodes displayed by ASTBrowser.
+ */
@CompileStatic
class TextTreeNodeMaker implements AstBrowserNodeMaker<TextNode> {
+ /**
+ * Creates a text node without additional metadata.
+ *
+ * @param userObject value displayed for the node
+ * @return the created text node
+ */
TextNode makeNode(Object userObject) {
new TextNode(userObject)
}
+ /**
+ * Creates a text node with additional metadata.
+ *
+ * @param userObject value displayed for the node
+ * @param properties metadata associated with the node
+ * @return the created text node
+ */
TextNode makeNodeWithProperties(Object userObject, List<List<?>> properties) {
new TextNode(userObject, properties)
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy
index a6c674b714..6ae9d8b308 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstBrowser.groovy
@@ -71,14 +71,27 @@ class AstBrowser {
private static final String BYTECODE_MSG_SELECT_NODE = '// Please select a class node in the tree view.'
private static final String NO_BYTECODE_AVAILABLE_AT_THIS_PHASE = '// No bytecode available at this phase'
+ /** UI components used by the browser panes and splitters. */
private inputArea, rootElement, decompiledSource, jTree, propertyTable, splitterPane, mainSplitter, bytecodeView, asmifierView
+ /** Display flags that control which script representations are shown. */
boolean showScriptFreeForm, showScriptClass, showClosureClasses, showTreeView
+ /** Class loader used to capture generated bytecode for the selected phase. */
GeneratedBytecodeAwareGroovyClassLoader classLoader
+ /** Persisted UI preferences for the browser window. */
def prefs = new AstBrowserUiPreferences()
+ /** Refresh action shared by toolbar and menu items. */
Action refreshAction
private CompilerConfiguration config
private Runnable themeChangeListener
+ /**
+ * Creates a browser for the supplied editor state.
+ *
+ * @param inputArea input component whose selection should track the tree
+ * @param rootElement root document element for the input component
+ * @param classLoader parent class loader used for compilation
+ * @param config optional compiler configuration
+ */
AstBrowser(inputArea, rootElement, classLoader, config = null) {
this.inputArea = inputArea
this.rootElement = rootElement
@@ -86,9 +99,16 @@ class AstBrowser {
this.config = config
}
+ /** Swing builder used to create and update the browser UI. */
SwingBuilder swing
+ /** Top-level browser window. */
def frame
+ /**
+ * Launches the AST browser for the supplied script file.
+ *
+ * @param args command-line arguments containing the script file path
+ */
static void main(args) {
if (!args) {
@@ -104,15 +124,29 @@ class AstBrowser {
}
}
+ /**
+ * Resets the auxiliary source views to their default placeholder text.
+ */
void initAuxViews() {
bytecodeView.textEditor.text = BYTECODE_MSG_SELECT_NODE
asmifierView.textEditor.text = BYTECODE_MSG_SELECT_NODE
}
+ /**
+ * Opens the browser for the supplied script producer.
+ *
+ * @param script closure that supplies the current script text
+ */
void run(Closure script) {
run(script, null)
}
+ /**
+ * Opens the browser for the supplied script producer.
+ *
+ * @param script closure that supplies the current script text
+ * @param name optional source name displayed in the window title
+ */
void run(Closure script, String name) {
swing = new SwingBuilder()
@@ -385,6 +419,13 @@ class AstBrowser {
return sw.toString()
}
+ /**
+ * Creates the table mouse listener used for copy and browse actions.
+ *
+ * @param valueCol column index containing the browsable value
+ * @param pathClosure closure used to build the browser path for a selected row
+ * @return the configured mouse listener
+ */
def mouseListener(int valueCol, Closure pathClosure) {
def outer = this
new MouseAdapter() {
@@ -444,10 +485,20 @@ class AstBrowser {
}
}
+ /**
+ * Increases the font size used by the browser views.
+ *
+ * @param evt triggering event, if any
+ */
void largerFont(EventObject evt = null) {
updateFontSize(decompiledSource.textEditor.font.size + 2)
}
+ /**
+ * Decreases the font size used by the browser views.
+ *
+ * @param evt triggering event, if any
+ */
void smallerFont(EventObject evt = null) {
updateFontSize(decompiledSource.textEditor.font.size - 2)
}
@@ -472,6 +523,13 @@ class AstBrowser {
propertyTable.rowHeight = newFontSize + 2
}
+ /**
+ * Opens the selected value in the object browser.
+ *
+ * @param table source table
+ * @param valueCol column containing the browsable value
+ * @param pathClosure closure used to build the browser path
+ */
void launch(table, valueCol, pathClosure) {
def selectedRow = table.selectedRow
if (selectedRow != -1) {
@@ -482,10 +540,16 @@ class AstBrowser {
}
}
+ /**
+ * Action handler that delegates to {@link #launch(Object, Object, Object)}.
+ */
void launchAction(table, valueCol, pathClosure, EventObject evt) {
launch(table, valueCol, pathClosure)
}
+ /**
+ * Copies the current tooltip value to the system clipboard.
+ */
void copyAction(table, MouseEvent me, EventObject evt) {
def toolTipText = table.getToolTipText(me)
if (toolTipText) {
@@ -493,6 +557,9 @@ class AstBrowser {
}
}
+ /**
+ * Displays the browser about dialog.
+ */
void aboutAction(EventObject evt) {
def pane = swing.optionPane()
def version = GroovySystem.version
@@ -502,18 +569,32 @@ class AstBrowser {
dialog.visible = true
}
+ /**
+ * Toggles display of the free-form script body.
+ */
void showScriptFreeForm(EventObject evt) {
showScriptFreeForm = evt.source.selected
}
+ /**
+ * Toggles display of the generated script class.
+ */
void showScriptClass(EventObject evt) {
showScriptClass = evt.source.selected
}
+ /**
+ * Toggles display of generated closure and lambda classes.
+ */
void showClosureClasses(EventObject evt) {
showClosureClasses = evt.source.selected
}
+ /**
+ * Toggles visibility of the tree pane.
+ *
+ * @param evt triggering event, if any
+ */
void showTreeView(EventObject evt = null) {
showTreeView = !showTreeView
splitterPane.visible = showTreeView
@@ -532,6 +613,12 @@ class AstBrowser {
'ASMifier'
}
+ /**
+ * Rebuilds the decompiled source view for the selected compile phase.
+ *
+ * @param phaseId compile phase identifier
+ * @param source current source text
+ */
void decompile(phaseId, source) {
decompiledSource.textEditor.cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)
decompiledSource.textEditor.text = 'Loading...'
@@ -556,6 +643,13 @@ class AstBrowser {
}
}
+ /**
+ * Rebuilds the AST tree for the selected compile phase.
+ *
+ * @param jTree tree component to update
+ * @param script current source text
+ * @param compilePhase compile phase identifier
+ */
void compile(jTree, String script, int compilePhase) {
jTree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR))
def model = jTree.model
@@ -591,17 +685,30 @@ class AstBrowser {
*/
class AstBrowserUiPreferences {
+ /** Saved window location. */
final frameLocation
+ /** Saved window size. */
final frameSize
+ /** Saved divider location for the tree/property splitter. */
final verticalDividerLocation
+ /** Saved divider location for the main splitter. */
final horizontalDividerLocation
+ /** Saved free-form script visibility flag. */
final boolean showScriptFreeForm
+ /** Saved tree visibility flag. */
final boolean showTreeView
+ /** Saved script class visibility flag. */
final boolean showScriptClass
+ /** Saved closure class visibility flag. */
final boolean showClosureClasses
+ /** Saved decompiled source font size. */
int decompiledSourceFontSize
+ /** Saved compile phase selection. */
final CompilePhaseAdapter selectedPhase
+ /**
+ * Loads persisted browser UI preferences.
+ */
AstBrowserUiPreferences() {
Preferences prefs = Preferences.userNodeForPackage(AstBrowserUiPreferences)
frameLocation = [
@@ -624,6 +731,9 @@ class AstBrowserUiPreferences {
}
}
+ /**
+ * Persists the current browser UI state.
+ */
def save(frame, vSplitter, hSplitter, scriptFreeFormPref, scriptClassPref, closureClassesPref, CompilePhaseAdapter phase, showTreeView) {
Preferences prefs = Preferences.userNodeForPackage(AstBrowserUiPreferences)
prefs.putInt('decompiledFontSize', decompiledSourceFontSize as int)
@@ -656,14 +766,27 @@ enum CompilePhaseAdapter {
OUTPUT(Phases.OUTPUT, 'Output'),
FINALIZATION(Phases.FINALIZATION, 'Finalization')
+ /** Compile phase identifier from {@link Phases}. */
final int phaseId
+ /** Human-readable label shown in the phase picker. */
final String string
+ /**
+ * Creates a phase adapter for the supplied compile phase.
+ *
+ * @param phaseId compile phase identifier
+ * @param string display label
+ */
CompilePhaseAdapter(int phaseId, String string) {
this.phaseId = phaseId
this.string = string
}
+ /**
+ * Returns the label shown in the phase picker.
+ *
+ * @return the display label
+ */
String toString() {
return string
}
@@ -676,6 +799,7 @@ enum CompilePhaseAdapter {
class TreeNodeWithProperties extends DefaultMutableTreeNode {
private static final long serialVersionUID = -1428101618435607457L
+ /** Additional properties associated with this tree node. */
List<List<?>> properties
/**
@@ -688,15 +812,31 @@ class TreeNodeWithProperties extends DefaultMutableTreeNode {
this.properties = properties
}
+ /**
+ * Returns the value of the named property.
+ *
+ * @param name property name
+ * @return the property value, or {@code null} if absent
+ */
String getPropertyValue(String name) {
def match = properties.find { List list -> name == list[0] }
return match != null ? match[1] : null
}
+ /**
+ * Indicates whether this node represents a class node.
+ *
+ * @return {@code true} for class and inner class nodes
+ */
boolean isClassNode() {
getPropertyValue('class') in ['class org.codehaus.groovy.ast.ClassNode', 'class org.codehaus.groovy.ast.InnerClassNode']
}
+ /**
+ * Indicates whether this node represents a method or constructor node.
+ *
+ * @return {@code true} for method and constructor nodes
+ */
boolean isMethodNode() {
getPropertyValue('class') in ['class org.codehaus.groovy.ast.MethodNode', 'class org.codehaus.groovy.ast.ConstructorNode']
}
@@ -707,8 +847,21 @@ class TreeNodeWithProperties extends DefaultMutableTreeNode {
*/
@CompileStatic
interface AstBrowserNodeMaker<T> {
+ /**
+ * Creates a node for the supplied user object.
+ *
+ * @param userObject value shown for the node
+ * @return the created node
+ */
T makeNode(Object userObject)
+ /**
+ * Creates a node for the supplied user object and metadata.
+ *
+ * @param userObject value shown for the node
+ * @param properties metadata associated with the node
+ * @return the created node
+ */
T makeNodeWithProperties(Object userObject, List<List<?>> properties)
}
@@ -717,24 +870,50 @@ interface AstBrowserNodeMaker<T> {
*/
@CompileStatic
class SwingTreeNodeMaker implements AstBrowserNodeMaker<DefaultMutableTreeNode> {
+ /**
+ * Creates a Swing tree node without metadata.
+ *
+ * @param userObject value shown for the node
+ * @return the created tree node
+ */
DefaultMutableTreeNode makeNode(Object userObject) {
new DefaultMutableTreeNode(userObject)
}
+ /**
+ * Creates a Swing tree node with metadata.
+ *
+ * @param userObject value shown for the node
+ * @param properties metadata associated with the node
+ * @return the created tree node
+ */
DefaultMutableTreeNode makeNodeWithProperties(Object userObject, List<List<?>> properties) {
new TreeNodeWithProperties(userObject, properties)
}
}
+/**
+ * Captures generated bytecode while delegating normal class creation.
+ */
class BytecodeCollector extends ClassCollector {
+ /** Captured bytecode keyed by class name. */
Map<String, byte[]> bytecode
+ /**
+ * Creates a collector that stores generated bytecode in the supplied map.
+ *
+ * @param delegate collector providing the compilation context
+ * @param bytecode destination map for generated bytecode
+ */
BytecodeCollector(ClassCollector delegate, Map<String, byte[]> bytecode) {
super(delegate.cl, delegate.unit, delegate.su)
this.bytecode = bytecode
}
+ /**
+ * Stores generated bytecode before defining the class.
+ */
@Override
protected Class createClass(byte[] code, ClassNode classNode) {
bytecode[classNode.name] = code
@@ -743,25 +922,45 @@ class BytecodeCollector extends ClassCollector {
}
+/**
+ * Groovy class loader that retains generated bytecode for AST Browser inspection.
+ */
@CompileStatic
class GeneratedBytecodeAwareGroovyClassLoader extends GroovyClassLoader {
private final Map<String, byte[]> bytecode = new HashMap<String, byte[]>()
+ /**
+ * Creates a class loader that records generated bytecode.
+ *
+ * @param parent parent class loader
+ */
GeneratedBytecodeAwareGroovyClassLoader(final GroovyClassLoader parent) {
super(parent)
}
+ /**
+ * Creates a collector that stores generated bytecode for later display.
+ */
@Override
protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
def collector = super.createCollector(unit, su)
new BytecodeCollector(collector, bytecode)
}
+ /**
+ * Clears any previously captured bytecode.
+ */
void clearBytecodeTable() {
bytecode.clear()
}
+ /**
+ * Returns the captured bytecode for the named class.
+ *
+ * @param className fully qualified class name
+ * @return the captured bytecode, or {@code null} if unavailable
+ */
byte[] getBytecode(final String className) {
bytecode[className]
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
index fc6f2ab327..67677329c9 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
@@ -186,13 +186,26 @@ and [compilephase] is a valid Integer based org.codehaus.groovy.control.CompileP
class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperation, GroovyClassVisitor, GroovyCodeVisitor {
private final Writer _out
+ /** Stack of nested class names used while rendering constructors. */
Stack<String> classNameStack = []
+ /** Current indentation prefix. */
String _indent = ''
+ /** Indicates whether the next write should emit indentation first. */
boolean readyToIndent = true
+ /** Whether free-form script statements should be rendered. */
boolean showScriptFreeForm
+ /** Whether the generated script class should be rendered. */
boolean showScriptClass
+ /** Tracks whether the free-form script body has already been emitted. */
boolean scriptHasBeenVisited
+ /**
+ * Creates a visitor that renders AST nodes as Groovy source.
+ *
+ * @param writer target writer
+ * @param showScriptFreeForm whether free-form script statements should be rendered
+ * @param showScriptClass whether the generated script class should be rendered
+ */
AstNodeToScriptVisitor(Writer writer, boolean showScriptFreeForm = true, boolean showScriptClass = true) {
this._out = writer
this.showScriptFreeForm = showScriptFreeForm
@@ -200,6 +213,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
this.scriptHasBeenVisited = false
}
+ /**
+ * Renders the supplied primary class node and optional script body.
+ */
@Override
void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
@@ -246,6 +262,11 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
}
}
+ /**
+ * Writes text, applying the current indentation rules.
+ *
+ * @param parameter value to append to the output
+ */
void print(parameter) {
def output = parameter.toString()
@@ -264,10 +285,20 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
_out.print output
}
+ /**
+ * This visitor writes directly to its target and does not support println-style output.
+ *
+ * @param parameter ignored output parameter
+ */
def println(parameter) {
throw new UnsupportedOperationException('Wrong API')
}
+ /**
+ * Executes the supplied block one indentation level deeper.
+ *
+ * @param block block to execute while indented
+ */
def indented(Closure block) {
String startingIndent = _indent
_indent = _indent + ' '
@@ -275,6 +306,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
_indent = startingIndent
}
+ /**
+ * Ensures the output ends with a single line break.
+ */
def printLineBreak() {
if (!_out.toString().endsWith('\n')) {
_out.print '\n'
@@ -282,6 +316,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
readyToIndent = true
}
+ /**
+ * Ensures the output ends with a blank line.
+ */
def printDoubleBreak() {
if (_out.toString().endsWith('\n\n')) {
// do nothing
@@ -294,6 +331,11 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
readyToIndent = true
}
+ /**
+ * Writes the package declaration for the current source unit.
+ *
+ * @param packageNode package node to render
+ */
void visitPackage(PackageNode packageNode) {
if (packageNode) {
@@ -312,6 +354,11 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
}
}
+ /**
+ * Writes an import declaration.
+ *
+ * @param node import node to render
+ */
void visitImport(ImportNode node) {
if (node) {
node.annotations?.each {
@@ -940,7 +987,8 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
/**
* Prints out the type, safely handling arrays.
- * @param classNode
+ *
+ * @param classNode type to render
*/
void visitType(ClassNode classNode) {
def name = classNode.name
@@ -1109,6 +1157,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
statement?.messageExpression?.visit this
}
+ /**
+ * Retained for compatibility; delegates to {@link #visitArgumentlistExpression(ArgumentListExpression)}.
+ */
@Deprecated
void visitArgumentlistExpression(ArgumentListExpression expression, boolean showTypesIgnored) {
visitArgumentlistExpression(expression)
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrDefaultRenderer.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrDefaultRenderer.groovy
index ab796a1513..c76b67b5d3 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrDefaultRenderer.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrDefaultRenderer.groovy
@@ -34,6 +34,9 @@ import java.awt.Component
class ButtonOrDefaultRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 4771388915540692105L
+ /**
+ * Returns the supplied component directly when possible, otherwise delegates to the default renderer.
+ */
Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof JComponent) {
value.setSize(Math.round(value.getSize().getWidth())?.toInteger(), table.getRowHeight(row))
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrTextEditor.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrTextEditor.groovy
index 383f1bb09e..08aa6c5543 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrTextEditor.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ButtonOrTextEditor.groovy
@@ -42,6 +42,9 @@ class ButtonOrTextEditor extends AbstractCellEditor implements TableCellEditor {
/** The Swing component being edited. */
protected JComponent editorComponent
+ /**
+ * Returns an editor component that matches the current cell value.
+ */
@Override
Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof JButton) {
@@ -58,6 +61,9 @@ class ButtonOrTextEditor extends AbstractCellEditor implements TableCellEditor {
editorComponent
}
+ /**
+ * Returns the current editor component.
+ */
@Override
Object getCellEditorValue() {
editorComponent
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/CellValueToolTipJTable.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/CellValueToolTipJTable.groovy
index 1d45047c1a..ec530978ef 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/CellValueToolTipJTable.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/CellValueToolTipJTable.groovy
@@ -21,9 +21,15 @@ package groovy.console.ui
import javax.swing.JTable
import java.awt.event.MouseEvent
+/**
+ * Table that exposes the current cell value as a tooltip.
+ */
class CellValueToolTipJTable extends JTable {
private static final long serialVersionUID = 7112943034646629299L
+ /**
+ * Returns the hovered cell value as tooltip text.
+ */
String getToolTipText(MouseEvent me) {
int viewRowIndex = rowAtPoint(me.point)
int viewColumnIndex = columnAtPoint(me.point)
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy
index ca78b92d18..bb9781c6cb 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy
@@ -108,148 +108,228 @@ import java.util.prefs.Preferences
*/
class Console implements CaretListener, HyperlinkListener, ComponentListener, FocusListener {
+ /** Prefix used for generated script names inside the console. */
static final String DEFAULT_SCRIPT_NAME_START = 'ConsoleScript'
private static final boolean DEBUG_GRAPE = Boolean.getBoolean('groovy.grape.debug')
+ /** User preferences backing console settings. */
static prefs = Preferences.userNodeForPackage(Console)
- // Whether or not std output should be captured to the console
+ /** Whether stdout is redirected into the console output pane. */
static boolean captureStdOut = prefs.getBoolean('captureStdOut', true)
+ /** Whether stderr is redirected into the console output pane. */
static boolean captureStdErr = prefs.getBoolean('captureStdErr', true)
+ /** Active console controllers sharing the global stream interceptors. */
static consoleControllers = []
+ /** Whether the smart syntax highlighter is enabled by default. */
static boolean smartHighlighter = prefs.getBoolean('smartHighlighter',
Boolean.valueOf(SystemUtil.getSystemPropertySafe('groovy.console.enable.smart.highlighter', 'true')))
+ /** Whether full stack traces are shown for execution failures. */
boolean fullStackTraces = prefs.getBoolean('fullStackTraces',
Boolean.valueOf(System.getProperty('groovy.full.stacktrace', 'false')))
+ /** Action that toggles full stack trace output. */
Action fullStackTracesAction
+ /** Whether executed script text is echoed into the output pane. */
boolean showScriptInOutput = prefs.getBoolean('showScriptInOutput', true)
+ /** Action that toggles script echoing in the output pane. */
Action showScriptInOutputAction
+ /** Whether non-null results are transformed for visual display. */
boolean visualizeScriptResults = prefs.getBoolean('visualizeScriptResults', false)
+ /** Action that toggles result visualization. */
Action visualizeScriptResultsAction
+ /** Whether the toolbar is visible. */
boolean showToolbar = prefs.getBoolean('showToolbar', true)
+ /** Toolbar component shown above the editor. */
Component toolbar
+ /** Action that toggles toolbar visibility. */
Action showToolbarAction
+ /** Whether output is shown in a detached window. */
boolean detachedOutput = prefs.getBoolean('detachedOutput', false)
+ /** Action that toggles detached output mode. */
Action detachedOutputAction
+ /** Whether the split pane stacks input and output vertically. */
boolean orientationVertical = prefs.getBoolean('orientationVertical', true)
+ /** Action that toggles the split-pane orientation. */
Action orientationVerticalAction
+ /** Action that shows the detached output window. */
Action showOutputWindowAction
+ /** Menu action variant that hides the detached output window. */
Action hideOutputWindowAction1
+ /** Toolbar action variant that hides the detached output window. */
Action hideOutputWindowAction2
+ /** Popup action variant that hides the detached output window. */
Action hideOutputWindowAction3
+ /** Shortcut action variant that hides the detached output window. */
Action hideOutputWindowAction4
+ /** Divider size to restore when output is no longer detached. */
int origDividerSize
+ /** Detached output window component. */
Component outputWindow
+ /** Last focused text component used by copy/select-all actions. */
Component copyFromComponent
+ /** Placeholder component shown when output is detached. */
Component blank
+ /** Scroll container that wraps the output area. */
Component scrollArea
+ /** Whether output is cleared before each run. */
boolean autoClearOutput = prefs.getBoolean('autoClearOutput', false)
+ /** Action that toggles automatic output clearing. */
Action autoClearOutputAction
- // Safer thread interruption
+ /** Whether the {@code @ThreadInterrupt} transform is applied to scripts. */
boolean threadInterrupt = prefs.getBoolean('threadInterrupt', false)
+ /** Action that toggles thread interruption support. */
Action threadInterruptAction
+ /** Action that toggles saving before each run. */
Action saveOnRunAction
+ /** Whether the current script is saved before it is run. */
boolean saveOnRun = prefs.getBoolean('saveOnRun', false)
+ /** Action that toggles loop mode. */
Action loopModeAction
+ /** Whether unchanged scripts are rerun after a delay. */
boolean loopMode = prefs.getBoolean('loopMode', false)
+ /** Hash of the last script text scheduled for execution. */
int inputAreaContentHash
+ /** Name of the currently selected theme mode. */
String currentTheme = ThemeManager.currentMode.name()
- //to allow loading classes dynamically when using @Grab (GROOVY-4877, GROOVY-5871)
+ /** Whether script execution uses the shell class loader as context loader. */
boolean useScriptClassLoaderForScriptExecution = false
- // Maximum size of history
+ /** Maximum number of history entries retained by the console. */
int maxHistory = 10
- // Maximum number of characters to show on console at any time
+ /** Maximum number of characters retained in the output pane. */
int maxOutputChars = System.getProperty('groovy.console.output.limit', '20000') as int
- // File to output stdout & stderr, in addition to console
+ /** Optional writer that mirrors output to a log file. */
PrintWriter outputPrintWriter = null
- // UI
+ /** Swing builder used to create and wire the console UI. */
SwingBuilder swing
+ /** Main console frame or dialog. */
RootPaneContainer frame
+ /** Styled text editor used for script input. */
ConsoleTextEditor inputEditor
+ /** Split pane separating input and output. */
JSplitPane splitPane
+ /** Raw text component used for script editing. */
JTextPane inputArea
+ /** Output text pane used for results and errors. */
JTextPane outputArea
+ /** Status label shown in the footer. */
JLabel statusLabel
+ /** Footer label showing the caret row and column. */
JLabel rowNumAndColNum
- // row info
+ /** Root document element for row calculations. */
Element rootElement
+ /** Current caret offset in the input document. */
int cursorPos
+ /** Current one-based caret row. */
int rowNum
+ /** Current one-based caret column. */
int colNum
- // Styles for output area
+ /** Style used for prompt text in the output pane. */
Style promptStyle
+ /** Style used for echoed commands in the output pane. */
Style commandStyle
+ /** Style used for regular output text. */
Style outputStyle
+ /** Style used for stack traces and error text. */
Style stacktraceStyle
+ /** Style used for clickable error links. */
Style hyperlinkStyle
+ /** Style used for evaluated result text. */
Style resultStyle
- // Internal history
+ /** Execution history records kept for navigation and result lookup. */
List history = []
+ /** Index of the currently selected history record. */
int historyIndex = 1 // valid values are 0..history.length()
+ /** Pending editor state kept while navigating command history. */
HistoryRecord pendingRecord = new HistoryRecord(allText: '', selectionStart: 0, selectionEnd: 0)
+ /** Action that moves to the previous history entry. */
Action prevHistoryAction
+ /** Action that moves to the next history entry. */
Action nextHistoryAction
- // Current editor state
+ /** Whether the current editor contents differ from the saved script. */
boolean dirty
+ /** Action that saves the current script. */
Action saveAction
+ /** Selection start cached from the caret listener. */
int textSelectionStart // keep track of selections in inputArea
+ /** Selection end cached from the caret listener. */
int textSelectionEnd
+ /** Current script file or generated script identifier. */
def scriptFile
+ /** Initial directory for file open/save dialogs. */
File currentFileChooserDir = new File(Preferences.userNodeForPackage(Console).get('currentFileChooserDir', '.'))
+ /** Initial directory for classpath jar selection dialogs. */
File currentClasspathJarDir = new File(Preferences.userNodeForPackage(Console).get('currentClasspathJarDir', '.'))
+ /** Initial directory for classpath directory selection dialogs. */
File currentClasspathDir = new File(Preferences.userNodeForPackage(Console).get('currentClasspathDir', '.'))
- // Running scripts
+ /** Base compiler configuration used to seed new scripts. */
CompilerConfiguration baseConfig
+ /** Compiler configuration for the current script shell. */
CompilerConfiguration config
+ /** Shell used to compile and run scripts. */
GroovyShell shell
+ /** Counter used for generated script names. */
int scriptNameCounter = 0
+ /** Interceptor that captures stdout. */
SystemOutputInterceptor systemOutInterceptor
+ /** Interceptor that captures stderr. */
SystemOutputInterceptor systemErrorInterceptor
+ /** Background thread currently compiling or running a script. */
Thread runThread = null
+ /** Optional hook invoked before script execution starts. */
Closure beforeExecution
+ /** Optional hook invoked after script execution completes. */
Closure afterExecution
+ /** Console window icon resource. */
public static URL ICON_PATH = Console.class.classLoader.getResource('groovy/console/ui/ConsoleIcon.png')
- // used by ObjectBrowser and AST Viewer
+ /** Returns the icon used for AST and object browser tree nodes. */
static javax.swing.Icon getNodeIcon() { Icons.green('fiber_manual_record') }
- // used by AST Viewer
+ /** File filter used for Groovy script open/save dialogs. */
static groovyFileFilter = new GroovyFileFilter()
+ /** Whether a script is currently being compiled or executed. */
boolean scriptRunning = false
+ /** Whether the last failure was a stack overflow. */
boolean stackOverFlowError = false
+ /** Action that interrupts the active script thread. */
Action interruptAction
- // Script arguments
+ /** Space-separated arguments passed to script execution. */
String scriptArgs = ''
+ /** Action that edits the configured script arguments. */
Action setScriptArgsAction
+ /** Action that selects the next word in the editor. */
Action selectWordAction
+ /** Action that selects the previous word in the editor. */
Action selectPreviousWordAction
+ /** Lazily created preferences dialog controller. */
ConsolePreferences consolePreferences
+ /** Launches the console application from the command line. */
static void main(args) {
MessageSource messages = new MessageSource(Console)
def cli = new CliBuilderInternal(usage: 'groovyConsole [options] [filename]', stopAtNonOption: false,
@@ -314,6 +394,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Loads the configured output size limit, honoring the system property override. */
int loadMaxOutputChars() {
// For backwards compatibility 'maxOutputChars' remains defined in the Console class
// and the System Property takes precedence as the default value.
@@ -321,6 +402,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
return System.getProperty('groovy.console.output.limit', "${max}") as int
}
+ /** Shows the console preferences dialog. */
void preferences(EventObject evt = null) {
if (!consolePreferences) {
consolePreferences = new ConsolePreferences(this)
@@ -328,6 +410,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
consolePreferences.show()
}
+ /** Updates output mirroring preferences and recreates the log writer if needed. */
void setOutputPreferences(boolean useOutputFile, File outputFile) {
prefs.remove('outputLogFileName')
if (!useOutputFile) {
@@ -341,12 +424,14 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Opens the configured output log file in append mode. */
void createOutputPrintWriter(File outputFile) {
outputPrintWriter = new PrintWriter(new FileOutputStream(
outputFile,
true))
}
+ /** Closes the optional output log writer. */
void closeOutputPrintWriter() {
if (outputPrintWriter != null) {
outputPrintWriter.close()
@@ -354,10 +439,12 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Creates a console backed by a fresh binding. */
Console(Binding binding = new Binding()) {
this(null, binding)
}
+ /** Creates a console with the supplied parent loader, binding, and base compiler configuration. */
Console(ClassLoader parent, Binding binding = new Binding(), CompilerConfiguration baseConfig = new CompilerConfiguration(System.getProperties())) {
this.baseConfig = baseConfig
this.maxOutputChars = loadMaxOutputChars()
@@ -403,6 +490,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
binding.variables._outputTransforms = OutputTransforms.loadOutputTransforms()
}
+ /** Recreates the Groovy shell for a new script context. */
void newScript(ClassLoader parent, Binding binding) {
config = new CompilerConfiguration(baseConfig)
config.addCompilationCustomizers(*baseConfig.compilationCustomizers)
@@ -412,6 +500,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
shell = new GroovyShell(parent, binding, config)
}
+ /** Default Swing delegates used to build the console frame and menu bar. */
static frameConsoleDelegates = [
rootContainerDelegate: {
frame(
@@ -433,10 +522,12 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
]
+ /** Builds and shows the console UI using default delegates. */
void run() {
run(frameConsoleDelegates)
}
+ /** Builds and shows the console UI using the supplied Swing delegates. */
void run(Map defaults) {
swing = new SwingBuilder()
@@ -518,6 +609,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
})
}
+ /** Starts interceptors that route standard output and error into the console. */
void installInterceptor() {
systemOutInterceptor = new SystemOutputInterceptor(this.¬ifySystemOut, true)
systemOutInterceptor.start()
@@ -526,6 +618,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
// TODO: would this be a good place to assign the console id?
}
+ /** Adds a history record and trims the list to the configured maximum size. */
void addToHistory(record) {
history.add(record)
// history.size here just retrieves method closure
@@ -547,21 +640,24 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
- // Append a string to the output area
+ /** Appends plain text to the output pane. */
void appendOutput(String text, AttributeSet style) {
def doc = outputArea.styledDocument
insertString(doc, doc.length, text, style)
ensureNoDocLengthOverflow(doc)
}
+ /** Appends a window description to the output pane. */
void appendOutput(Window window, AttributeSet style) {
appendOutput(window.toString(), style)
}
+ /** Appends an object's string form to the output pane. */
void appendOutput(Object object, AttributeSet style) {
appendOutput(object.toString(), style)
}
+ /** Appends an embedded component to the output pane. */
void appendOutput(Component component, AttributeSet style) {
SimpleAttributeSet sas = new SimpleAttributeSet()
sas.addAttribute(StyleConstants.NameAttribute, 'component')
@@ -569,6 +665,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
appendOutput(component.toString(), sas)
}
+ /** Appends an embedded icon to the output pane. */
void appendOutput(Icon icon, AttributeSet style) {
SimpleAttributeSet sas = new SimpleAttributeSet()
sas.addAttribute(StyleConstants.NameAttribute, 'icon')
@@ -576,6 +673,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
appendOutput(icon.toString(), sas)
}
+ /** Appends a stack trace, hyperlinking script line references when possible. */
void appendStacktrace(text) {
// prevent NPE when outputArea is missing, i.e. there is currently no window present
// TODO the text should not be swallowed (options: postpone output, open new window, log file, terminal, ...)
@@ -621,6 +719,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
ensureNoDocLengthOverflow(doc)
}
+ /** Inserts text into a document and optionally mirrors it to the output log file. */
void insertString(Document doc, int offset, String text, AttributeSet attributeSet, boolean outputToFile = true) {
doc.insertString(offset, text, attributeSet)
@@ -631,7 +730,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
- // Append a string to the output area on a new line
+ /** Appends text to the output pane, forcing a preceding line break. */
void appendOutputNl(text, style) {
def doc = outputArea.styledDocument
def len = doc.length
@@ -643,6 +742,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
appendOutput(text, style)
}
+ /** Appends text and preserves compact line spacing in the output pane. */
void appendOutputLines(text, style) {
appendOutput(text, style)
def doc = outputArea.styledDocument
@@ -653,7 +753,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
doc.remove(len, 2) // windows hack to fix (improve?) line spacing
}
- // Return false if use elected to cancel
+ /** Prompts to save unsaved changes before an operation proceeds. */
boolean askToSaveFile() {
if (!dirty) {
return true
@@ -670,27 +770,30 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Emits the platform default warning beep. */
void beep() {
Toolkit.defaultToolkit.beep()
}
- // Binds the '_' and '__' variables in the shell
+ /** Refreshes the shell bindings that expose the last result and result history. */
void bindResults() {
shell.setVariable('_', getLastResult()) // lastResult doesn't seem to work
shell.setVariable('__', history.collect { it.result })
}
- // Handles menu event
+ /** Toggles whether stdout is captured by all console windows. */
static void captureStdOut(EventObject evt) {
captureStdOut = evt.source.selected
prefs.putBoolean('captureStdOut', captureStdOut)
}
+ /** Toggles whether stderr is captured by all console windows. */
static void captureStdErr(EventObject evt) {
captureStdErr = evt.source.selected
prefs.putBoolean('captureStdErr', captureStdErr)
}
+ /** Toggles whether full stack traces are reported for failures. */
void fullStackTraces(EventObject evt) {
fullStackTraces = evt.source.selected
System.setProperty('groovy.full.stacktrace',
@@ -698,22 +801,26 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
prefs.putBoolean('fullStackTraces', fullStackTraces)
}
+ /** Toggles whether executed source text is echoed to the output pane. */
void showScriptInOutput(EventObject evt) {
showScriptInOutput = evt.source.selected
prefs.putBoolean('showScriptInOutput', showScriptInOutput)
}
+ /** Toggles whether results are transformed before display. */
void visualizeScriptResults(EventObject evt) {
visualizeScriptResults = evt.source.selected
prefs.putBoolean('visualizeScriptResults', visualizeScriptResults)
}
+ /** Toggles toolbar visibility and persists the selection. */
void showToolbar(EventObject evt) {
showToolbar = evt.source.selected
prefs.putBoolean('showToolbar', showToolbar)
toolbar.visible = showToolbar
}
+ /** Toggles the input/output split orientation. */
void orientationVertical(EventObject evt) {
def oldValue = orientationVertical
orientationVertical = evt.source.selected
@@ -729,6 +836,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Toggles whether output is shown in a detached window. */
void detachedOutput(EventObject evt) {
def oldDetachedOutput = detachedOutput
detachedOutput = evt.source.selected
@@ -751,18 +859,35 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Toggles whether output is cleared before each run or compile. */
void autoClearOutput(EventObject evt) {
autoClearOutput = evt.source.selected
prefs.putBoolean('autoClearOutput', autoClearOutput)
}
- /** @since 6.0.0 */
+ /**
+ * Switches the console to the bundled light theme.
+ *
+ * @since 6.0.0
+ */
void lightTheme(EventObject evt = null) { switchTheme(ThemeManager.ThemeMode.LIGHT) }
- /** @since 6.0.0 */
+ /**
+ * Switches the console to the bundled dark theme.
+ *
+ * @since 6.0.0
+ */
void darkTheme(EventObject evt = null) { switchTheme(ThemeManager.ThemeMode.DARK) }
- /** @since 6.0.0 */
+ /**
+ * Switches the console to follow the operating system theme.
+ *
+ * @since 6.0.0
+ */
void systemTheme(EventObject evt = null) { switchTheme(ThemeManager.ThemeMode.SYSTEM) }
- /** @since 6.0.0 */
+ /**
+ * Advances to the next available theme mode.
+ *
+ * @since 6.0.0
+ */
void cycleTheme(EventObject evt = null) { switchTheme(ThemeManager.cycleMode()) }
private void switchTheme(ThemeManager.ThemeMode mode) {
@@ -856,6 +981,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
outputArea.repaint()
}
+ /** Toggles the {@code @ThreadInterrupt} transform for future scripts. */
void threadInterruption(EventObject evt) {
threadInterrupt = evt.source.selected
prefs.putBoolean('threadInterrupt', threadInterrupt)
@@ -874,6 +1000,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Updates cached selection bounds and the row/column display. */
void caretUpdate(CaretEvent e) {
textSelectionStart = Math.min(dot(e), mark(e))
textSelectionEnd = Math.max(dot(e), mark(e))
@@ -882,21 +1009,24 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
// GROOVY-8339: to avoid illegal access to a non-visible implementation class - can be removed if a more general solution is found
@CompileStatic
+ /** Returns the caret dot position without touching implementation details. */
int dot(CaretEvent e) {
e.dot
}
// GROOVY-8339: to avoid illegal access to a non-visible implementation class - can be removed if a more general solution is found
@CompileStatic
+ /** Returns the caret mark position without touching implementation details. */
int mark(CaretEvent e) {
e.mark
}
+ /** Clears all text from the output pane. */
void clearOutput(EventObject evt = null) {
outputArea.text = ''
}
- // If at exit time, a script is running, the user is given an option to interrupt it first
+ /** Prompts to interrupt the running script before exiting. */
def askToInterruptScript() {
if (!scriptRunning) return true
def rc = JOptionPane.showConfirmDialog(frame, "Script executing. Press 'OK' to attempt to interrupt it before exiting.",
@@ -909,10 +1039,12 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Interrupts the currently running script thread, if any. */
void doInterrupt(EventObject evt = null) {
runThread?.interrupt()
}
+ /** Handles desktop quit requests by delegating to {@link #exit}. */
void exitDesktop(EventObject evt = null, quitResponse = null) {
if (exit(evt)) {
quitResponse.performQuit()
@@ -921,6 +1053,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Closes this console after offering to interrupt and save. */
boolean exit(EventObject evt = null) {
if (askToInterruptScript()) {
def exit = askToSaveFile()
@@ -941,6 +1074,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Resets the editor to a new unsaved script. */
void fileNewFile(EventObject evt = null) {
if (askToSaveFile()) {
scriptFile = null
@@ -949,7 +1083,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
- // Start a new window with a copy of current variables
+ /** Opens another console window seeded with the current binding variables. */
void fileNewWindow(EventObject evt = null) {
Console consoleController = new Console(
new Binding(
@@ -974,6 +1108,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
swing.doLater swing.inputArea.&requestFocus
}
+ /** Prompts for a script file and loads it into the editor. */
void fileOpen(EventObject evt = null) {
if (askToSaveFile()) {
def scriptName = selectFilename()
@@ -983,6 +1118,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Loads the supplied script file into the editor. */
void loadScriptFile(File file) {
swing.edt {
inputArea.editable = false
@@ -1014,7 +1150,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
- // Save file - return false if user cancelled save
+ /** Saves the current script, prompting for a file when needed. */
boolean fileSave(EventObject evt = null) {
if (scriptFile == null) {
return fileSaveAs(evt)
@@ -1025,7 +1161,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
return true
}
- // Save file - return false if user cancelled save
+ /** Saves the current script under a newly selected file name. */
boolean fileSaveAs(EventObject evt = null) {
scriptFile = selectFilename('Save')
if (scriptFile != null) {
@@ -1037,6 +1173,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Reports a compilation or execution failure in the output pane. */
def finishException(Throwable t, boolean executing) {
if (executing) {
statusLabel.text = 'Execution terminated with exception.'
@@ -1133,6 +1270,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
appendStacktrace("\n${sw.builder}\n")
}
+ /** Reports successful execution and publishes the resulting value. */
def finishNormal(Object result, Long elapsedTime=null) {
String elapsedTimeStr = null != elapsedTime ? " Elapsed time: ${elapsedTime}ms." : ''
// Take down the wait/cancel dialog
@@ -1156,6 +1294,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Reports successful compilation without execution. */
def compileFinishNormal() {
statusLabel.text = 'Compilation complete.'
}
@@ -1168,7 +1307,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
outputWindow.pack()
}
- // Gets the last, non-null result
+ /** Returns the most recent non-null result from execution history. */
def getLastResult() {
// runtime bugs in here history.reverse produces odd lookup
// return history.reverse.find {it != null}
@@ -1183,6 +1322,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
return null
}
+ /** Moves forward in command history. */
void historyNext(EventObject evt = null) {
if (historyIndex < history.size()) {
setInputTextFromHistory(historyIndex + 1)
@@ -1192,6 +1332,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Moves backward in command history. */
void historyPrev(EventObject evt = null) {
if (historyIndex > 0) {
setInputTextFromHistory(historyIndex - 1)
@@ -1201,6 +1342,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
+ /** Opens the object browser for the last non-null result. */
void inspectLast(EventObject evt = null) {
if (null == lastResult) {
JOptionPane.showMessageDialog(frame, 'The last result is null.',
@@ -1210,14 +1352,17 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
ObjectBrowser.inspect(lastResult)
}
+ /** Opens the object browser for the current shell variables. */
void inspectVariables(EventObject evt = null) {
ObjectBrowser.inspect(shell.getContext().variables)
}
+ /** Opens the AST browser for the current editor contents. */
void inspectAst(EventObject evt = null) {
new AstBrowser(inputArea, rootElement, shell.getClassLoader(), config).run({ inputArea.getText() })
}
+ /** Opens the CST viewer for the current editor contents. */
void inspectCst(EventObject evt = null) {
String text = this.inputEditor.textEditor.text
def charStream = CharStreams.fromReader(new StringReader(text))
@@ -1269,30 +1414,59 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
null
}
+ /** Opens the lexer token inspector for the current editor contents. */
void inspectTokens(EventObject evt = null) {
def content = inputArea.getText()
def lf = new LexerFrame(new StringReader(content))
lf.visible = true
}
- /** @since 6.0.0 */
+ /**
+ * Returns whether toolbar icons follow the editor font size.
+ *
+ * @since 6.0.0
+ */
boolean isScaleIconsWithFont() { prefs.getBoolean('scaleIconsWithFont', false) }
- /** @since 6.0.0 */
+ /**
+ * Returns the current toolbar icon size.
+ *
+ * @since 6.0.0
+ */
int getCurrentIconSize() { Icons.currentSize }
- /** @since 6.0.0 */
+ /**
+ * Switches toolbar icons to the small preset.
+ *
+ * @since 6.0.0
+ */
void smallIcons(EventObject evt = null) { applyIconSize(Icons.SIZE_SMALL) }
- /** @since 6.0.0 */
+ /**
+ * Switches toolbar icons to the normal preset.
+ *
+ * @since 6.0.0
+ */
void normalIcons(EventObject evt = null) { applyIconSize(Icons.SIZE_NORMAL) }
- /** @since 6.0.0 */
+ /**
+ * Switches toolbar icons to the large preset.
+ *
+ * @since 6.0.0
+ */
void largeIcons(EventObject evt = null) { applyIconSize(Icons.SIZE_LARGE) }
- /** @since 6.0.0 */
+ /**
+ * Toggles whether toolbar icons track the editor font size.
+ *
+ * @since 6.0.0
+ */
void scaleIconsWithFont(EventObject evt = null) {
setScaleIconsWithFont(evt?.source?.isSelected() as boolean)
}
- /** @since 6.0.0 */
+ /**
+ * Persists whether toolbar icons track the editor font size.
+ *
+ * @since 6.0.0
+ */
void setScaleIconsWithFont(boolean enabled) {
prefs.putBoolean('scaleIconsWithFont', enabled)
if (enabled) {
@@ -1302,7 +1476,11 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
}
}
- /** @since 6.0.0 */
+ /**
+ * Applies a toolbar icon size and refreshes the toolbar.
+ *
+ * @since 6.0.0
+ */
void applyIconSize(int size) {
// only persist the preset when not tracking the font
if (!prefs.getBoolean('scaleIconsWithFont', false)) {
@@ -1313,12 +1491,20 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
toolbar?.repaint()
}
- /** @since 6.0.0 */
+ /**
+ * Reapplies the current theme and retints console components.
+ *
+ * @since 6.0.0
+ */
void reapplyTheme() {
switchTheme(ThemeManager.currentMode)
}
- /** @since 6.0.0 */
+ /**
+ * Reloads theme definitions and reapplies the active theme.
+ *
+ * @since 6.0.0
+ */
void reloadThemes() {
ThemeManager.reloadThemes()
reapplyTheme()
@@ -1341,10 +1527,12 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
prefs.getInt('iconSize', Icons.SIZE_NORMAL)
}
-void largerFont(EventObject evt = null) {
+ /** Increases the editor font size. */
+ void largerFont(EventObject evt = null) {
updateFontSize(inputArea.font.size + 2)
}
+ /** Routes captured stdout to the owning console or every console window. */
static boolean notifySystemOut(int consoleId, String str) {
if (!captureStdOut) {
// Output as normal
@@ -1369,6 +1557,7 @@ void largerFont(EventObject evt = null) {
return false
}
+ /** Routes captured stderr to the owning console or every console window. */
static boolean notifySystemErr(int consoleId, String str) {
if (!captureStdErr) {
// Output as normal
@@ -1393,6 +1582,7 @@ void largerFont(EventObject evt = null) {
return false
}
+ /** Returns the identifier used by output interceptors for this console. */
int getConsoleId() {
return System.identityHashCode(this)
}
@@ -1460,11 +1650,12 @@ void largerFont(EventObject evt = null) {
abstract Object compile(String src)
}
+ /** Compiles and runs the current editor contents as Java source. */
void runJava(EventObject evt = null) {
runScript(evt, new JavaSourceType())
}
- // actually run the script
+ /** Compiles and runs the current editor contents. */
void runScript(EventObject evt = null, SourceType st = new GroovySourceType()) {
saveInputAreaContentHash()
if (saveOnRun && scriptFile != null) {
@@ -1474,25 +1665,30 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Toggles whether scripts are saved before they run. */
void saveOnRun(EventObject evt = null) {
saveOnRun = evt.source.selected
prefs.putBoolean('saveOnRun', saveOnRun)
}
+ /** Toggles whether the console reruns unchanged scripts after a delay. */
void loopMode(EventObject evt = null) {
loopMode = evt.source.selected
prefs.putBoolean('loopMode', loopMode)
}
+ /** Compiles and runs the current selection as Java source. */
void runSelectedJava(EventObject evt = null) {
runSelectedScript(evt, new JavaSourceType())
}
+ /** Compiles and runs only the current editor selection. */
void runSelectedScript(EventObject evt = null, SourceType st = new GroovySourceType()) {
saveInputAreaContentHash()
runScriptImpl(true, st)
}
+ /** Adds jar files to the shell classpath. */
void addClasspathJar(EventObject evt = null) {
def fc = new JFileChooser(currentClasspathJarDir)
fc.fileSelectionMode = JFileChooser.FILES_ONLY
@@ -1507,6 +1703,7 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Adds a directory to the shell classpath. */
void addClasspathDir(EventObject evt = null) {
def fc = new JFileChooser(currentClasspathDir)
fc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
@@ -1518,6 +1715,7 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Displays the current shell classpath in a dialog. */
void listClasspath(EventObject evt = null) {
List<URL> urls = []
@@ -1548,6 +1746,7 @@ void largerFont(EventObject evt = null) {
dialog.visible = true
}
+ /** Clears the shell binding and starts a fresh script context. */
void clearContext(EventObject evt = null) {
def binding = new Binding()
newScript(null, binding)
@@ -1555,7 +1754,11 @@ void largerFont(EventObject evt = null) {
binding.variables._outputTransforms = OutputTransforms.loadOutputTransforms()
}
- /** @since 6.0.0 */
+ /**
+ * Prompts for space-separated script arguments used by future runs.
+ *
+ * @since 6.0.0
+ */
void setScriptArgs(EventObject evt = null) {
def result = JOptionPane.showInputDialog(frame, 'Enter script arguments (space-separated):',
'Set Script Arguments', JOptionPane.PLAIN_MESSAGE, null, null, scriptArgs)
@@ -1687,10 +1890,12 @@ void largerFont(EventObject evt = null) {
return Optional.empty()
}
+ /** Compiles the current editor contents as Java source. */
void compileAsJava(EventObject evt = null) {
compileScript(evt, new JavaSourceType())
}
+ /** Compiles the current editor contents without running them. */
void compileScript(EventObject evt = null, SourceType st = new GroovySourceType()) {
if (scriptRunning) {
statusLabel.text = 'Cannot compile script now as a script is already running. Please wait or use "Interrupt Script" option.'
@@ -1728,6 +1933,7 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Opens a file chooser and returns the selected script file. */
def selectFilename(name = 'Open') {
def fc = new JFileChooser(currentFileChooserDir)
fc.fileSelectionMode = JFileChooser.FILES_ONLY
@@ -1745,6 +1951,7 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Marks the editor dirty state and refreshes save affordances. */
void setDirty(boolean newDirty) {
//TODO when @BoundProperty is live, this should be handled via listeners
dirty = newDirty
@@ -1779,12 +1986,12 @@ void largerFont(EventObject evt = null) {
prevHistoryAction.enabled = historyIndex > 0
}
- // Adds a variable to the binding
- // Useful for adding variables before opening the console
+ /** Adds a variable to the shell binding. */
void setVariable(String name, Object value) {
shell.getContext().setVariable(name, value)
}
+ /** Shows the about dialog for the console. */
void showAbout(EventObject evt = null) {
def version = GroovySystem.getVersion()
def pane = swing.optionPane()
@@ -1802,14 +2009,17 @@ void largerFont(EventObject evt = null) {
dialog.show()
}
+ /** Shows the find dialog. */
void find(EventObject evt = null) {
FindReplaceUtility.showDialog()
}
+ /** Repeats the previous find operation in the forward direction. */
void findNext(EventObject evt = null) {
FindReplaceUtility.FIND_ACTION.actionPerformed(evt)
}
+ /** Repeats the previous find operation in the reverse direction. */
void findPrevious(EventObject evt = null) {
def reverseEvt = new ActionEvent(
evt.getSource(), evt.getID(),
@@ -1818,10 +2028,12 @@ void largerFont(EventObject evt = null) {
FindReplaceUtility.FIND_ACTION.actionPerformed(reverseEvt)
}
+ /** Shows the replace dialog. */
void replace(EventObject evt = null) {
FindReplaceUtility.showDialog(true)
}
+ /** Comments or uncomments the current line selection. */
void comment(EventObject evt = null) {
def rootElement = inputArea.document.defaultRootElement
def cursorPos = inputArea.getCaretPosition()
@@ -1863,6 +2075,7 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Expands the selection from word to line to surrounding block. */
void selectBlock(EventObject evt = null) {
final int startPos = inputArea.getSelectionStart()
final int endPos = inputArea.getSelectionEnd()
@@ -1931,19 +2144,22 @@ void largerFont(EventObject evt = null) {
inputArea.setSelectionEnd(endBlockPos)
}
+ /** Updates the status bar with the supplied message. */
void showMessage(String message) {
statusLabel.text = message
}
+ /** Shows the standard script-executing status message. */
void showExecutingMessage() {
statusLabel.text = 'Script executing now. Please wait or use "Interrupt Script" option.'
}
+ /** Shows the standard script-compiling status message. */
void showCompilingMessage() {
statusLabel.text = 'Script compiling now. Please wait.'
}
- // Shows the detached 'outputArea' dialog
+ /** Shows the detached output window when detached mode is enabled. */
void showOutputWindow(EventObject evt = null) {
if (detachedOutput) {
outputWindow.setLocationRelativeTo(frame)
@@ -1951,27 +2167,32 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Hides the detached output window when detached mode is enabled. */
void hideOutputWindow(EventObject evt = null) {
if (detachedOutput) {
outputWindow.visible = false
}
}
+ /** Clears and then hides the detached output window. */
void hideAndClearOutputWindow(EventObject evt = null) {
clearOutput()
hideOutputWindow()
}
+ /** Decreases the editor font size. */
void smallerFont(EventObject evt = null) {
updateFontSize(inputArea.font.size - 2)
}
+ /** Switches between smart and regex-based syntax highlighters. */
void smartHighlighter(EventObject evt = null) {
inputEditor.enableHighLighter(evt.source.selected ? SmartDocumentFilter : GroovyFilter)
inputEditor.textEditor.setText(inputEditor.textEditor.getText()) // enable the highlighter immediately
prefs.putBoolean('smartHighlighter', evt.source.selected)
}
+ /** Updates the main window title to reflect the current script and dirty state. */
void updateTitle() {
if (frame.title) {
String title = 'GroovyConsole'
@@ -2020,6 +2241,7 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Invokes a text action against the active source component when present. */
void invokeTextAction(evt, closure, area = inputArea) {
def source = evt.getSource()
if (source != null) {
@@ -2027,22 +2249,27 @@ void largerFont(EventObject evt = null) {
}
}
+ /** Cuts the current editor selection. */
void cut(EventObject evt = null) {
invokeTextAction(evt, { source -> source.cut() })
}
+ /** Copies the current selection from the focused text component. */
void copy(EventObject evt = null) {
invokeTextAction(evt, { source -> source.copy() }, copyFromComponent ?: inputArea)
}
+ /** Pastes clipboard text into the input editor. */
void paste(EventObject evt = null) {
invokeTextAction(evt, { source -> source.paste() })
}
+ /** Selects all text in the focused text component. */
void selectAll(EventObject evt = null) {
invokeTextAction(evt, { source -> source.selectAll() }, copyFromComponent ?: inputArea)
}
+ /** Updates the cached caret row and column display. */
void setRowNumAndColNum() {
cursorPos = inputArea.getCaretPosition()
rowNum = rootElement.getElementIndex(cursorPos) + 1
@@ -2053,18 +2280,22 @@ void largerFont(EventObject evt = null) {
rowNumAndColNum.setText("$rowNum:$colNum")
}
+ /** Prints the input editor contents. */
void print(EventObject evt = null) {
inputEditor.printAction.actionPerformed(evt)
}
+ /** Undoes the most recent edit in the input editor. */
void undo(EventObject evt = null) {
inputEditor.undoAction.actionPerformed(evt)
}
+ /** Redoes the most recently undone edit in the input editor. */
void redo(EventObject evt = null) {
inputEditor.redoAction.actionPerformed(evt)
}
+ /** Highlights the source line referenced by an activated output hyperlink. */
void hyperlinkUpdate(HyperlinkEvent e) {
if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) {
// URL of the form: file://myscript.groovy:32
@@ -2099,10 +2330,13 @@ void largerFont(EventObject evt = null) {
}
}
+ /** No-op component listener implementation. */
void componentHidden(ComponentEvent e) {}
+ /** No-op component listener implementation. */
void componentMoved(ComponentEvent e) {}
+ /** Persists component dimensions when the editor or frame is resized. */
void componentResized(ComponentEvent e) {
def component = e.getComponent()
if (component == outputArea || component == inputArea) {
@@ -2115,8 +2349,10 @@ void largerFont(EventObject evt = null) {
}
}
+ /** No-op component listener implementation. */
void componentShown(ComponentEvent e) {}
+ /** Tracks the last focused text component for copy and select-all actions. */
void focusGained(FocusEvent e) {
// remember component with focus for text-copy functionality
if (e.component == outputArea || e.component == inputArea) {
@@ -2124,6 +2360,7 @@ void largerFont(EventObject evt = null) {
}
}
+ /** No-op focus listener implementation. */
void focusLost(FocusEvent e) {}
private static boolean isWindows() {
@@ -2135,11 +2372,13 @@ void largerFont(EventObject evt = null) {
}
}
+/** File filter for Groovy source files recognized by the console. */
@CompileStatic
class GroovyFileFilter extends FileFilter {
private static final List GROOVY_SOURCE_EXTENSIONS = ['*.groovy', '*.gvy', '*.gy', '*.gsh', '*.story', '*.gpp', '*.grunit']
private static final GROOVY_SOURCE_EXT_DESC = GROOVY_SOURCE_EXTENSIONS.join(',')
+ /** Accepts directories and files with supported Groovy source extensions. */
boolean accept(File f) {
if (f.isDirectory()) {
return true
@@ -2147,10 +2386,12 @@ class GroovyFileFilter extends FileFilter {
GROOVY_SOURCE_EXTENSIONS.find { it == getExtension(f) } ? true : false
}
+ /** Returns the chooser description for supported Groovy source files. */
String getDescription() {
"Groovy Source Files ($GROOVY_SOURCE_EXT_DESC)"
}
+ /** Returns the wildcard extension for the supplied file name. */
static String getExtension(File f) {
def ext = null
def s = f.getName()
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleIvyPlugin.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleIvyPlugin.groovy
index a2580e6734..21e0b3a080 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleIvyPlugin.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleIvyPlugin.groovy
@@ -27,10 +27,18 @@ import groovy.grape.GrapeEngine
* Adds Groovy Grape feedback (via an Ivy listener).
*/
class ConsoleIvyPlugin {
+ /** Console receiving dependency progress messages. */
Console savedConsole
+ /** Dependency coordinates already reported as resolving. */
Set<String> resolvedDependencies = []
+ /** Artifacts already reported as downloading. */
Set<String> downloadedArtifacts = []
+ /**
+ * Registers an Ivy listener that reports Grape progress to the console.
+ *
+ * @param console console to receive progress messages
+ */
def addListener(Console console) {
savedConsole = console
GrapeEngine engine = Grape.instance
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleMavenPlugin.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleMavenPlugin.groovy
index fb21e82cfa..96f39f8cc1 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleMavenPlugin.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleMavenPlugin.groovy
@@ -27,10 +27,18 @@ import groovy.grape.GrapeEngine
* @since 6.0.0
*/
class ConsoleMavenPlugin {
+ /** Console receiving dependency progress messages. */
Console savedConsole
+ /** Dependency coordinates already reported as resolving. */
Set<String> resolvedDependencies = []
+ /** Artifacts already reported as downloading. */
Set<String> downloadedArtifacts = []
+ /**
+ * Registers a Maven Resolver listener that reports Grape progress to the console.
+ *
+ * @param console console to receive progress messages
+ */
def addListener(Console console) {
savedConsole = console
GrapeEngine engine = Grape.instance
@@ -52,4 +60,3 @@ class ConsoleMavenPlugin {
}
}
}
-
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsolePreferences.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsolePreferences.groovy
index 912191804b..47b99ec1fa 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsolePreferences.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsolePreferences.groovy
@@ -28,27 +28,37 @@ import javax.swing.JOptionPane
import javax.swing.filechooser.FileNameExtensionFilter
import java.awt.Dimension
+/**
+ * Manages the Groovy Console preferences dialog and persisted settings.
+ */
class ConsolePreferences {
- // Default maximum number of characters to show on console at any time
+ /** Default maximum number of characters retained in the output area. */
static int DEFAULT_MAX_OUTPUT_CHARS = 20000
+ /** Default loop mode delay, in milliseconds. */
static int DEFAULT_LOOP_MODE_DELAY_MILLIS = 1000
+ /** Maximum number of characters retained in the output area. */
@Bindable
int maxOutputChars
+ /** Delay between loop mode executions, in milliseconds. */
@Bindable
int loopModeDelay
+ /** Selected icon size for console actions. */
@Bindable
int iconSize
+ /** Whether action icons should track the current font size. */
@Bindable
boolean scaleIconsWithFont
+ /** Optional custom light theme path. */
@Bindable
String customLightThemePath
+ /** Optional custom dark theme path. */
@Bindable
String customDarkThemePath
@@ -56,8 +66,14 @@ class ConsolePreferences {
private final MessageSource T
private JDialog dialog
+ /** Output file currently selected in the preferences dialog. */
File outputFile
+ /**
+ * Creates a preferences controller for the supplied console.
+ *
+ * @param console console instance that owns the dialog
+ */
ConsolePreferences(console) {
this.console = console
T = new MessageSource(Console)
@@ -71,6 +87,9 @@ class ConsolePreferences {
console.maxOutputChars = maxOutputChars
}
+ /**
+ * Displays the preferences dialog.
+ */
void show() {
console.swing.edt {
if (!dialog) {
@@ -345,6 +364,11 @@ class ConsolePreferences {
}
// Useful for testing gui
+ /**
+ * Launches the preferences dialog in isolation for manual UI checks.
+ *
+ * @param args ignored command-line arguments
+ */
static void main(args) {
javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName())
def c = new Expando().with {
@@ -363,4 +387,4 @@ class ConsolePreferences {
println "console.maxOutputChars==${c.maxOutputChars}"
println 'done'
}
-}
\ No newline at end of file
+}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleSupport.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleSupport.java
index d304f5b676..e2dfc8dcac 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleSupport.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleSupport.java
@@ -28,16 +28,25 @@ import javax.swing.text.StyledDocument;
import java.awt.Color;
/**
- * Base class for console
+ * Base support class for consoles that evaluate Groovy scripts and render styled output.
*/
public abstract class ConsoleSupport {
+ /** Style used for prompts. */
Style promptStyle;
+ /** Style used for entered commands. */
Style commandStyle;
+ /** Style used for evaluated output. */
Style outputStyle;
private GroovyShell shell;
+ /** Counter used to create unique script names. */
int counter;
+ /**
+ * Initializes the styles used by the output document.
+ *
+ * @param outputArea text pane whose styled document should be configured
+ */
protected void addStylesToDocument(JTextPane outputArea) {
StyledDocument doc = outputArea.getStyledDocument();
@@ -56,18 +65,38 @@ public abstract class ConsoleSupport {
StyleConstants.setBold(outputStyle, true);
}
+ /**
+ * Returns the style used for entered commands.
+ *
+ * @return the command style
+ */
public Style getCommandStyle() {
return commandStyle;
}
+ /**
+ * Returns the style used for evaluated output.
+ *
+ * @return the output style
+ */
public Style getOutputStyle() {
return outputStyle;
}
+ /**
+ * Returns the style used for prompts.
+ *
+ * @return the prompt style
+ */
public Style getPromptStyle() {
return promptStyle;
}
+ /**
+ * Returns the shell used for script evaluation.
+ *
+ * @return the lazily created shell
+ */
public GroovyShell getShell() {
if (shell == null) {
shell = new GroovyShell();
@@ -75,6 +104,12 @@ public abstract class ConsoleSupport {
return shell;
}
+ /**
+ * Evaluates the supplied text and delegates failures to {@link #handleException(String, Exception)}.
+ *
+ * @param text script text to evaluate
+ * @return the evaluation result, or {@code null} when evaluation fails
+ */
protected Object evaluate(String text) {
String name = "Script" + counter++;
try {
@@ -85,5 +120,11 @@ public abstract class ConsoleSupport {
}
}
+ /**
+ * Handles an exception raised while evaluating the supplied script text.
+ *
+ * @param text script text being evaluated
+ * @param e exception raised during evaluation
+ */
protected abstract void handleException(String text, Exception e);
-}
\ No newline at end of file
+}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleTextEditor.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleTextEditor.java
index dca38fadeb..27328dc2f7 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleTextEditor.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ConsoleTextEditor.java
@@ -69,10 +69,20 @@ public class ConsoleTextEditor extends JScrollPane {
private static final String PREFERENCE_FONT_SIZE = "fontSize";
private static final int DEFAULT_FONT_SIZE = 12;
+ /**
+ * Returns the preferred monospaced font family for the editor.
+ *
+ * @return the default font family name
+ */
public String getDefaultFamily() {
return defaultFamily;
}
+ /**
+ * Sets the preferred monospaced font family for the editor.
+ *
+ * @param defaultFamily the font family name to use
+ */
public void setDefaultFamily(String defaultFamily) {
this.defaultFamily = defaultFamily;
}
@@ -152,6 +162,9 @@ public class ConsoleTextEditor extends JScrollPane {
private int fontSize;
/**
+ * Returns the undo manager backing this editor.
+ *
+ * @return the editor undo manager
* @since 6.0.0
*/
public TextUndoManager getUndoManager() {
@@ -184,7 +197,8 @@ public class ConsoleTextEditor extends JScrollPane {
}
/**
- * Creates a new instance of ConsoleTextEditor
+ * Creates a styled text editor with line numbers, undo/redo support,
+ * printing support, and syntax highlighting.
*/
public ConsoleTextEditor() {
fontSize = PREFERENCES.getInt(PREFERENCE_FONT_SIZE, DEFAULT_FONT_SIZE);
@@ -278,6 +292,11 @@ public class ConsoleTextEditor extends JScrollPane {
}
}
+ /**
+ * Shows or hides the line-number gutter.
+ *
+ * @param showLineNumbers {@code true} to show line numbers
+ */
public void setShowLineNumbers(boolean showLineNumbers) {
if (showLineNumbers) {
JPanel view = new JPanel(new BorderLayout());
@@ -289,19 +308,37 @@ public class ConsoleTextEditor extends JScrollPane {
}
}
+ /**
+ * Updates whether the text editor accepts user edits.
+ *
+ * @param editable {@code true} if the editor should be editable
+ */
public void setEditable(boolean editable) {
textEditor.setEditable(editable);
}
+ /**
+ * Reports whether plain-text clipboard content is currently available.
+ *
+ * @return {@code true} if the clipboard contains a string
+ */
public boolean clipBoardAvailable() {
Transferable t = StructuredSyntaxResources.SYSTEM_CLIPBOARD.getContents(this);
return t.isDataFlavorSupported(DataFlavor.stringFlavor);
}
+ /**
+ * Returns the wrapped text editor component.
+ *
+ * @return the embedded text editor
+ */
public TextEditor getTextEditor() {
return textEditor;
}
+ /**
+ * Installs actions exposed by this editor on its action map.
+ */
protected void initActions() {
ActionMap map = getActionMap();
map.put(StructuredSyntaxResources.PRINT, new PrintAction());
@@ -394,18 +431,38 @@ public class ConsoleTextEditor extends JScrollPane {
}
}
+ /**
+ * Returns the action that undoes the most recent edit.
+ *
+ * @return the undo action
+ */
public Action getUndoAction() {
return undoAction;
}
+ /**
+ * Returns the action that redoes the most recently undone edit.
+ *
+ * @return the redo action
+ */
public Action getRedoAction() {
return redoAction;
}
+ /**
+ * Returns the action that prints the editor content.
+ *
+ * @return the print action
+ */
public Action getPrintAction() {
return printAction;
}
+ /**
+ * Replaces the current syntax highlighter document filter.
+ *
+ * @param clazz the filter type to instantiate for the current document
+ */
public void enableHighLighter(Class<? extends DocumentFilter> clazz) {
DefaultStyledDocument doc = (DefaultStyledDocument) textEditor.getDocument();
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/HistoryRecord.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/HistoryRecord.groovy
index 6eddf0d060..0ced82b8f5 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/HistoryRecord.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/HistoryRecord.groovy
@@ -20,15 +20,30 @@ package groovy.console.ui
import groovy.transform.CompileStatic
+/**
+ * Captures the editor state and execution outcome for a console history entry.
+ */
@CompileStatic
class HistoryRecord {
+ /** Full editor contents captured for this history entry. */
String allText
+ /** Start offset of the selected text. */
int selectionStart
+ /** End offset of the selected text. */
int selectionEnd
+ /** Generated script name used during execution. */
String scriptName
+ /** Execution result, when evaluation completed successfully. */
Object result
+ /** Execution failure, when evaluation did not complete successfully. */
Throwable exception
+ /**
+ * Returns the text that should be executed for this history entry.
+ *
+ * @param useSelection whether to execute only the current selection
+ * @return the selected text with leading imports, or the full script text
+ */
String getTextToRun(boolean useSelection) {
if (useSelection && selectionStart != selectionEnd) {
// Retrieve all the imports included in the script before the current selection
@@ -41,6 +56,11 @@ class HistoryRecord {
return allText
}
+ /**
+ * Returns the execution result or captured exception.
+ *
+ * @return the successful result, or the exception when execution failed
+ */
Object getValue() {
return exception ? exception : result
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Icons.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Icons.groovy
index 4078efb811..59b838b1dd 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Icons.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Icons.groovy
@@ -46,9 +46,13 @@ import java.util.function.Function
* @since 6.0.0
*/
class Icons {
+ /** Small toolbar icon size in pixels. */
static final int SIZE_SMALL = 16
+ /** Default toolbar icon size in pixels. */
static final int SIZE_NORMAL = 18
+ /** Large toolbar icon size in pixels. */
static final int SIZE_LARGE = 28
+ /** Fixed menu icon size in pixels. */
static final int MENU_SIZE = 18
private static final String PATH = 'groovy/console/ui/icons/'
@@ -59,47 +63,74 @@ class Icons {
// ---- fixed-size icons (in-window menus, popups, AstBrowser/ObjectBrowser toolbars) ----
+ /** Returns a neutral app-themed icon at the fixed menu size. */
static DynamicSVGIcon load(String name) {
menuIcon(name, { Color c -> UIManager.getColor('Label.foreground') ?: c })
}
+ /** Returns a green app-themed icon at the fixed menu size. */
static DynamicSVGIcon green(String name) { menuIcon(name, greenMapper({ ThemeManager.isDark() })) }
+ /** Returns a red app-themed icon at the fixed menu size. */
static DynamicSVGIcon red(String name) { menuIcon(name, redMapper({ ThemeManager.isDark() })) }
+ /** Returns a blue app-themed icon at the fixed menu size. */
static DynamicSVGIcon blue(String name) { menuIcon(name, blueMapper({ ThemeManager.isDark() })) }
+ /** Returns an amber app-themed icon at the fixed menu size. */
static DynamicSVGIcon amber(String name) { menuIcon(name, amberMapper({ ThemeManager.isDark() })) }
+ /** Returns a violet app-themed icon at the fixed menu size. */
static DynamicSVGIcon violet(String name) { menuIcon(name, violetMapper({ ThemeManager.isDark() })) }
+ /** Returns a teal app-themed icon at the fixed menu size. */
static DynamicSVGIcon teal(String name) { menuIcon(name, tealMapper({ ThemeManager.isDark() })) }
+ /** Returns a Java-brand blue app-themed icon at the fixed menu size. */
static DynamicSVGIcon javaBlue(String name) { menuIcon(name, javaBlueMapper({ ThemeManager.isDark() })) }
+ /** Returns a muted blue app-themed icon at the fixed menu size. */
static DynamicSVGIcon subtleBlue(String name) { menuIcon(name, subtleBlueMapper({ ThemeManager.isDark() })) }
// ---- main menu-bar icons (tint follows the menu-bar background, which is OS-drawn on macOS) ----
+ /** Returns a neutral menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menu(String name) {
menuIcon(name, { Color c -> ThemeManager.menuIconForeground ?: c })
}
+ /** Returns a green menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuGreen(String name) { menuIcon(name, greenMapper({ ThemeManager.isMenuDark() })) }
+ /** Returns a red menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuRed(String name) { menuIcon(name, redMapper({ ThemeManager.isMenuDark() })) }
+ /** Returns a blue menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuBlue(String name) { menuIcon(name, blueMapper({ ThemeManager.isMenuDark() })) }
+ /** Returns an amber menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuAmber(String name) { menuIcon(name, amberMapper({ ThemeManager.isMenuDark() })) }
+ /** Returns a violet menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuViolet(String name) { menuIcon(name, violetMapper({ ThemeManager.isMenuDark() })) }
+ /** Returns a teal menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuTeal(String name) { menuIcon(name, tealMapper({ ThemeManager.isMenuDark() })) }
+ /** Returns a Java-brand blue menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuJavaBlue(String name) { menuIcon(name, javaBlueMapper({ ThemeManager.isMenuDark() })) }
+ /** Returns a muted blue menu-bar icon tinted for the current menu background. */
static DynamicSVGIcon menuSubtleBlue(String name) { menuIcon(name, subtleBlueMapper({ ThemeManager.isMenuDark() })) }
// ---- toolbar / resizable ----
+ /** Returns a neutral resizable toolbar icon. */
static DynamicSVGIcon toolbar(String name) {
toolbarIcon(name, { Color c -> UIManager.getColor('Label.foreground') ?: c })
}
+ /** Returns a green resizable toolbar icon. */
static DynamicSVGIcon toolbarGreen(String name) { toolbarIcon(name, greenMapper({ ThemeManager.isDark() })) }
+ /** Returns a red resizable toolbar icon. */
static DynamicSVGIcon toolbarRed(String name) { toolbarIcon(name, redMapper({ ThemeManager.isDark() })) }
+ /** Returns a blue resizable toolbar icon. */
static DynamicSVGIcon toolbarBlue(String name) { toolbarIcon(name, blueMapper({ ThemeManager.isDark() })) }
+ /** Returns an amber resizable toolbar icon. */
static DynamicSVGIcon toolbarAmber(String name) { toolbarIcon(name, amberMapper({ ThemeManager.isDark() })) }
+ /** Returns a violet resizable toolbar icon. */
static DynamicSVGIcon toolbarViolet(String name) { toolbarIcon(name, violetMapper({ ThemeManager.isDark() })) }
+ /** Returns a teal resizable toolbar icon. */
static DynamicSVGIcon toolbarTeal(String name) { toolbarIcon(name, tealMapper({ ThemeManager.isDark() })) }
+ /** Returns a Java-brand blue resizable toolbar icon. */
static DynamicSVGIcon toolbarJavaBlue(String name) { toolbarIcon(name, javaBlueMapper({ ThemeManager.isDark() })) }
+ /** Returns a muted blue resizable toolbar icon. */
static DynamicSVGIcon toolbarSubtleBlue(String name) { toolbarIcon(name, subtleBlueMapper({ ThemeManager.isDark() })) }
// Material "700" for light theme / "400" for dark — muted enough not to
@@ -141,6 +172,7 @@ class Icons {
toolbarIcons.each { it.setSize(size) }
}
+ /** Returns the current toolbar icon size in pixels. */
static int getCurrentSize() { currentSize }
/** Re-run the colour filter so every icon adopts the new LaF foreground. */
@@ -159,17 +191,26 @@ class Icons {
private Icon disabledDelegate
private int size
+ /**
+ * Creates a dynamic icon wrapper for the given SVG resource.
+ *
+ * @param path the classpath-relative SVG resource path
+ * @param size the initial square icon size in pixels
+ * @param colorMapper the color filter applied to the loaded icon
+ */
DynamicSVGIcon(String path, int size, Closure<Color> colorMapper) {
this.path = path
this.colorMapper = colorMapper
setSize(size)
}
+ /** Rebuilds this icon at a new square size. */
void setSize(int newSize) {
this.size = newSize
rebuildDelegates()
}
+ /** Rebuilds this icon so it adopts current theme colours. */
void refreshColors() {
// rebuild the FlatSVGIcon entirely — setColorFilter alone leaves the
// internal raster cache in a state where some contexts (notably the
@@ -186,8 +227,11 @@ class Icons {
this.disabledDelegate = delegate.getDisabledIcon()
}
+ /** Returns the current icon width in pixels. */
@Override int getIconWidth() { size }
+ /** Returns the current icon height in pixels. */
@Override int getIconHeight() { size }
+ /** Paints the enabled or disabled delegate icon for the target component. */
@Override void paintIcon(Component c, Graphics g, int x, int y) {
if (c != null && !c.isEnabled()) {
disabledDelegate.paintIcon(c, g, x, y)
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy
index dcc0ae0f15..59c710781d 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy
@@ -53,17 +53,42 @@ import static org.apache.groovy.parser.antlr4.GroovyLexer.VOCABULARY
*/
class ObjectBrowser {
+ /** Inspector backing the currently displayed object. */
def inspector
+ /** Breadcrumb text describing the current inspection path. */
String path
+ /** Number of path cards created so far. */
int pathCount = 0
+ /** Tracks the currently selected inspection card. */
def tracker
- def swing, frame, fieldTable, methodTable, arrayTable, collectionTable, mapTable, cards, pathMenu, mb
+ /** Swing builder used to create browser UI components. */
+ def swing
+ /** Top-level browser window. */
+ def frame
+ /** Table showing property and field data. */
+ def fieldTable
+ /** Table showing methods and meta-methods. */
+ def methodTable
+ /** Table showing array contents. */
+ def arrayTable
+ /** Table showing collection contents. */
+ def collectionTable
+ /** Table showing map entries. */
+ def mapTable
+ /** Card container holding each inspected object view. */
+ def cards
+ /** Menu listing visited inspection paths. */
+ def pathMenu
+ /** Menu bar attached to the browser window. */
+ def mb
private static final Comparator<Object> comparator = new Inspector.MemberComparatorWithValue()
+ /** Launches the browser against a sample string. */
static void main(args) {
inspect('some String')
}
+ /** Opens an object browser for the supplied object. */
static void inspect(objectUnderInspection, String path = '') {
def browser = new ObjectBrowser(path: path ?: '')
browser.inspector = new Inspector(objectUnderInspection)
@@ -73,6 +98,7 @@ class ObjectBrowser {
browser.run()
}
+ /** Adds another inspected object as a new card in the current window. */
void inspectAlso(objectUnderInspection, String path = '') {
int idx = pathCount++
def pathId = 'path' + idx
@@ -84,6 +110,7 @@ class ObjectBrowser {
mb.revalidate()
}
+ /** Builds and shows the object browser window. */
void run() {
swing = new SwingBuilder()
tracker = new CardTracker()
@@ -302,6 +329,7 @@ class ObjectBrowser {
}
}
+ /** Creates a mouse listener that can browse or copy the selected table row. */
def mouseListener(int valueCol, Closure pathClosure) {
def outer = this
new MouseAdapter() {
@@ -352,6 +380,7 @@ class ObjectBrowser {
}
}
+ /** Opens the selected table value in this window or a new window. */
void launch(table, valueCol, boolean newWindow, pathClosure) {
def selectedRow = table.selectedRow
if (selectedRow != -1) {
@@ -365,6 +394,7 @@ class ObjectBrowser {
}
}
+ /** Adds column sorting support to the supplied table. */
void addSorter(table) {
if (table != null) {
def sorter = new TableSorter(table.model)
@@ -373,16 +403,19 @@ class ObjectBrowser {
}
}
+ /** Switches the visible inspection card to the supplied index. */
void showAction(int idx, EventObject evt) {
tracker.current = idx
show()
}
+ /** Shows the card for the current tracker position. */
void show() {
cards.layout.show(cards, 'path' + tracker.current)
cards.revalidate()
}
+ /** Displays usage help for the object browser. */
void usageAction(EventObject evt) {
def pane = swing.optionPane()
// work around GROOVY-1048
@@ -396,10 +429,12 @@ class ObjectBrowser {
dialog.show()
}
+ /** Delegates popup-menu browsing to {@link #launch}. */
void launchAction(table, valueCol, boolean newWindow, pathClosure, EventObject evt) {
launch(table, valueCol, newWindow, pathClosure)
}
+ /** Copies the tooltip text for the clicked table cell to the clipboard. */
void copyAction(table, MouseEvent me, EventObject evt) {
def toolTipText = table.getToolTipText(me)
if (toolTipText) {
@@ -407,6 +442,7 @@ class ObjectBrowser {
}
}
+ /** Displays the about dialog for the object browser. */
void aboutAction(EventObject evt) {
def pane = swing.optionPane()
// work around GROOVY-1048
@@ -417,6 +453,8 @@ class ObjectBrowser {
}
}
+/** Tracks the currently selected card index for the browser. */
@Bindable class CardTracker {
+ /** Zero-based index of the visible card. */
int current = 0
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/OutputTransforms.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/OutputTransforms.groovy
index 022b4d754c..025ec54404 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/OutputTransforms.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/OutputTransforms.groovy
@@ -35,11 +35,20 @@ import java.awt.Transparency
import java.awt.Window
import java.awt.image.BufferedImage
+/**
+ * Provides the transformation pipeline used to render console results.
+ */
@CompileStatic
class OutputTransforms {
+ /** Lazily loaded output transforms composed from user and built-in transforms. */
@Lazy static List<Closure> localTransforms = loadOutputTransforms()
+ /**
+ * Loads user-defined and built-in output transforms.
+ *
+ * @return transforms applied to console results
+ */
static List<Closure> loadOutputTransforms() {
def transforms = []
@@ -97,6 +106,13 @@ class OutputTransforms {
return (List<Closure>) transforms
}
+ /**
+ * Applies the configured transforms until one returns a non-null value.
+ *
+ * @param base original result value
+ * @param transforms transforms to apply
+ * @return the transformed value, or the original value when no transform matches
+ */
static transformResult(base, List<Closure> transforms = localTransforms) {
for (Closure c : transforms) {
def result = c(base as Object)
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
index d6eab946eb..e0e406ab77 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
@@ -116,9 +116,13 @@ import java.util.concurrent.atomic.AtomicBoolean
*/
class ScriptToTreeNodeAdapter {
+ /** Maps AST node class names to display templates. */
static Properties classNameToStringForm
+ /** Display flags that control which script structures are included. */
boolean showScriptFreeForm, showScriptClass, showClosureClasses
+ /** Class loader used for script compilation. */
final GroovyClassLoader classLoader
+ /** Factory used to create tree node instances. */
final AstBrowserNodeMaker nodeMaker
private final CompilerConfiguration config
@@ -148,6 +152,16 @@ class ScriptToTreeNodeAdapter {
}
}
+ /**
+ * Creates an adapter for the supplied compilation settings.
+ *
+ * @param classLoader class loader used for compilation
+ * @param showScriptFreeForm whether to include free-form script statements
+ * @param showScriptClass whether to include the generated script class
+ * @param showClosureClasses whether to include generated closure classes
+ * @param nodeMaker node factory used for tree creation
+ * @param config optional compiler configuration
+ */
ScriptToTreeNodeAdapter(classLoader, showScriptFreeForm, showScriptClass, showClosureClasses, nodeMaker, config = null) {
this.classLoader = classLoader ?: new GroovyClassLoader(getClass().classLoader)
this.showScriptFreeForm = showScriptFreeForm
@@ -164,6 +178,7 @@ class ScriptToTreeNodeAdapter {
* a Groovy script in String form
* @param compilePhase
* the int based CompilePhase to compile it to.
+ * @return the generated tree root
*/
def compile(String script, int compilePhase) {
def scriptName = 'script' + System.currentTimeMillis() + '.groovy'
@@ -194,10 +209,22 @@ class ScriptToTreeNodeAdapter {
return operation.root
}
+ /**
+ * Creates a tree node for the supplied AST node.
+ *
+ * @param node AST node to adapt
+ * @return the created tree node
+ */
def make(node) {
makeWithTable(node, getPropertyTable(node))
}
+ /**
+ * Creates a tree node for a method node with its descriptor metadata.
+ *
+ * @param node method node to adapt
+ * @return the created tree node
+ */
def make(MethodNode node) {
def table = getPropertyTable(node)
extendMethodNodePropertyTable(table, node)
@@ -210,6 +237,9 @@ class ScriptToTreeNodeAdapter {
/**
* Extends the method node property table by adding custom properties.
+ *
+ * @param table property table to extend
+ * @param node method node supplying the descriptor
*/
void extendMethodNodePropertyTable(List<List<?>> table, MethodNode node) {
def descriptor = BytecodeHelper.getMethodDescriptor(node)
@@ -244,6 +274,12 @@ class ScriptToTreeNodeAdapter {
}.sort { list -> list[0] } // sort by name
}
+ /**
+ * Returns a string representation without crossing visibility boundaries.
+ *
+ * @param o value to convert
+ * @return string form of the supplied value
+ */
// GROOVY-8339: to avoid illegal access to a non-visible implementation class - can be removed if a more general solution is found
@CompileStatic
String toString(Object o) {
@@ -273,20 +309,42 @@ class ScriptToTreeNodeAdapter {
*/
class TreeNodeBuildingNodeOperation implements CompilationUnit.IPrimaryClassNodeOperation {
+ /** Root node for the generated tree. */
final root
+ /** Tracks whether free-form source nodes were already collected. */
final sourceCollected = new AtomicBoolean(false)
+ /** Adapter used to create child nodes. */
final ScriptToTreeNodeAdapter adapter
+ /** Whether free-form script statements should be included. */
final showScriptFreeForm
+ /** Whether the generated script class should be included. */
final showScriptClass
+ /** Whether generated closure classes should be included. */
final showClosureClasses
+ /** Factory used to create tree nodes. */
final nodeMaker
+ /**
+ * Creates an operation with closure class nodes hidden.
+ *
+ * @param adapter adapter used to create tree nodes
+ * @param showScriptFreeForm whether free-form script statements should be shown
+ * @param showScriptClass whether the generated script class should be shown
+ */
TreeNodeBuildingNodeOperation(ScriptToTreeNodeAdapter adapter, showScriptFreeForm, showScriptClass) {
this(adapter, showScriptFreeForm, showScriptClass, false)
}
+ /**
+ * Creates an operation for building the AST tree.
+ *
+ * @param adapter adapter used to create tree nodes
+ * @param showScriptFreeForm whether free-form script statements should be shown
+ * @param showScriptClass whether the generated script class should be shown
+ * @param showClosureClasses whether generated closure classes should be shown
+ */
TreeNodeBuildingNodeOperation(ScriptToTreeNodeAdapter adapter, showScriptFreeForm, showScriptClass, showClosureClasses) {
if (!adapter) throw new IllegalArgumentException('Null: adapter')
this.adapter = adapter
@@ -297,6 +355,9 @@ class TreeNodeBuildingNodeOperation implements CompilationUnit.IPrimaryClassNode
root = nodeMaker.makeNode('root')
}
+ /**
+ * Adds the current class node and optional script statements to the tree.
+ */
@Override
void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
// module node
@@ -326,6 +387,11 @@ class TreeNodeBuildingNodeOperation implements CompilationUnit.IPrimaryClassNode
}
}
+ /**
+ * Adds generated closure and lambda classes for the supplied outer class.
+ *
+ * @param classNode outer class being inspected
+ */
protected void makeClosureClassTreeNodes(ClassNode classNode) {
def compileUnit = classNode.compileUnit
if (!compileUnit.generatedInnerClasses) return
@@ -463,6 +529,7 @@ class TreeNodeBuildingNodeOperation implements CompilationUnit.IPrimaryClassNode
@PackageScope
class TreeNodeBuildingVisitor extends CodeVisitorSupport {
+ /** Node currently being populated by the visitor. */
def currentNode
private final adapter
@@ -653,6 +720,8 @@ class TreeNodeBuildingVisitor extends CodeVisitorSupport {
/**
* Makes walking parameters look like others in the visitor.
+ *
+ * @param node parameter node to add
*/
void visitParameter(Parameter node) {
addNode(node, Parameter, { })
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ThemeManager.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ThemeManager.groovy
index 44fec58c67..a62c483b0d 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ThemeManager.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ThemeManager.groovy
@@ -37,10 +37,17 @@ import java.util.prefs.Preferences
class ThemeManager {
/**
+ * Available theme selection modes for the console.
+ *
* @since 6.0.0
*/
enum ThemeMode {
- LIGHT, DARK, SYSTEM
+ /** Always use the light theme. */
+ LIGHT,
+ /** Always use the dark theme. */
+ DARK,
+ /** Follow the operating system theme. */
+ SYSTEM
}
private static final Preferences prefs = Preferences.userNodeForPackage(Console)
@@ -53,18 +60,22 @@ class ThemeManager {
// frames (AstBrowser, ObjectBrowser) can retint their own text panes/icons
private static final List<Runnable> themeChangeListeners = new CopyOnWriteArrayList<>()
+ /** Registers a listener notified after the console theme changes. */
static void addThemeChangeListener(Runnable listener) {
themeChangeListeners << listener
}
+ /** Removes a previously registered theme-change listener. */
static void removeThemeChangeListener(Runnable listener) {
themeChangeListeners.remove(listener)
}
+ /** Notifies all registered listeners that the theme has changed. */
static void notifyThemeChanged() {
themeChangeListeners.each { it.run() }
}
+ /** Returns the configured theme mode, defaulting to {@link ThemeMode#SYSTEM}. */
static ThemeMode getCurrentMode() {
try {
ThemeMode.valueOf(prefs.get('theme', 'SYSTEM').toUpperCase())
@@ -73,11 +84,13 @@ class ThemeManager {
}
}
+ /** Returns whether the active console theme currently resolves to dark. */
static boolean isDark() {
def mode = currentMode
mode == ThemeMode.DARK || (mode == ThemeMode.SYSTEM && isSystemDarkMode())
}
+ /** Applies and persists the supplied theme mode. */
static void applyTheme(ThemeMode mode) {
prefs.put('theme', mode.name())
refreshSystemDarkMode()
@@ -95,6 +108,7 @@ class ThemeManager {
prior == null || prior != cachedSystemDark
}
+ /** Returns whether the operating system appearance currently resolves to dark. */
static boolean isSystemDarkMode() {
if (cachedSystemDark == null) {
cachedSystemDark = probeSystemDarkMode()
@@ -126,6 +140,7 @@ class ThemeManager {
}
}
+ /** Cycles through light, dark, and system theme modes. */
static ThemeMode cycleMode() {
switch (currentMode) {
case ThemeMode.LIGHT: return ThemeMode.DARK
@@ -134,6 +149,7 @@ class ThemeManager {
}
}
+ /** Returns a user-facing label for the current theme mode. */
static String getThemeLabel() {
switch (currentMode) {
case ThemeMode.LIGHT: return 'Light'
@@ -142,14 +158,17 @@ class ThemeManager {
}
}
+ /** Returns the active theme's editor background colour. */
static Color getInputBackground() {
activeTheme.inputBackground
}
+ /** Returns the active theme's output background colour. */
static Color getOutputBackground() {
activeTheme.outputBackground
}
+ /** Builds the Swing style map for the active theme and font family. */
static Map getStyles(String fontFamily) {
buildSwingStyles(activeTheme, fontFamily)
}
@@ -170,13 +189,17 @@ class ThemeManager {
private static final Map<String, Object> themeCache = [:]
+ /** Returns the configured custom light-theme file path, if any. */
static String getCustomLightPath() { prefs.get('themeCustomLightPath', null) }
+ /** Returns the configured custom dark-theme file path, if any. */
static String getCustomDarkPath() { prefs.get('themeCustomDarkPath', null) }
+ /** Persists the custom light-theme file path or clears it when blank. */
static void setCustomLightPath(String path) {
path ? prefs.put('themeCustomLightPath', path) : prefs.remove('themeCustomLightPath')
}
+ /** Persists the custom dark-theme file path or clears it when blank. */
static void setCustomDarkPath(String path) {
path ? prefs.put('themeCustomDarkPath', path) : prefs.remove('themeCustomDarkPath')
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/AutoIndentAction.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/AutoIndentAction.groovy
index 055b011694..185cd3cd91 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/AutoIndentAction.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/AutoIndentAction.groovy
@@ -26,12 +26,23 @@ import javax.swing.text.AttributeSet
import javax.swing.text.SimpleAttributeSet
import java.awt.event.ActionEvent
+/**
+ * Inserts a newline that preserves the indentation of the current line.
+ */
@CompileStatic
class AutoIndentAction extends AbstractAction {
private static final long serialVersionUID = -2680684287296849370L
+ /**
+ * Attributes used for inserted text.
+ */
AttributeSet simpleAttributeSet = new SimpleAttributeSet()
+ /**
+ * Inserts a newline and copies the leading whitespace of the current line.
+ *
+ * @param evt the action event providing the target text pane
+ */
void actionPerformed(ActionEvent evt) {
JTextPane inputArea = (JTextPane) evt.source
def rootElement = inputArea.document.defaultRootElement
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/FindReplaceUtility.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/FindReplaceUtility.java
index b3469098f4..ee7b7b2a03 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/FindReplaceUtility.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/FindReplaceUtility.java
@@ -49,16 +49,34 @@ import java.awt.event.TextEvent;
import java.awt.event.TextListener;
import java.util.EventListener;
+/**
+ * Shared find/replace dialog support for text components used by the console.
+ */
public final class FindReplaceUtility {
+ /**
+ * Action command for find operations.
+ */
public static final String FIND_ACTION_COMMAND = "Find";
+ /**
+ * Action command for single replace operations.
+ */
public static final String REPLACE_ACTION_COMMAND = "Replace";
+ /**
+ * Action command for replace-all operations.
+ */
public static final String REPLACE_ALL_ACTION_COMMAND = "Replace All";
+ /**
+ * Action command for closing the dialog.
+ */
public static final String CLOSE_ACTION_COMMAND = "Close";
+ /**
+ * Shared action that repeats a find against the active text component.
+ */
public static final Action FIND_ACTION = new FindAction();
private static final JDialog FIND_REPLACE_DIALOG = new JDialog();
@@ -198,6 +216,11 @@ public final class FindReplaceUtility {
private FindReplaceUtility() {
}
+ /**
+ * Registers a listener for dialog text events.
+ *
+ * @param tl the listener to add
+ */
public static void addTextListener(TextListener tl) {
EVENT_LISTENER_LIST.add(TextListener.class, tl);
}
@@ -236,12 +259,19 @@ public final class FindReplaceUtility {
}
/**
+ * Starts tracking the supplied text component for find/replace actions.
+ *
* @param textComponent the text component to listen to
*/
public static void registerTextComponent(JTextComponent textComponent) {
textComponent.addFocusListener(TEXT_FOCUS_LISTENER);
}
+ /**
+ * Removes a listener for dialog text events.
+ *
+ * @param tl the listener to remove
+ */
public static void removeTextListener(TextListener tl) {
EVENT_LISTENER_LIST.remove(TextListener.class, tl);
}
@@ -353,11 +383,16 @@ public final class FindReplaceUtility {
}
+ /**
+ * Shows the find dialog for the active text component.
+ */
public static void showDialog() {
showDialog(false);
}
/**
+ * Shows either the find or replace dialog for the active text component.
+ *
* @param isReplace show a replace dialog rather than a find dialog if true
*/
public static void showDialog(boolean isReplace) {
@@ -403,6 +438,8 @@ public final class FindReplaceUtility {
}
/**
+ * Stops tracking the supplied text component for find/replace actions.
+ *
* @param textComponent the text component to stop listening to
*/
public static void unregisterTextComponent(JTextComponent textComponent) {
@@ -557,6 +594,9 @@ public final class FindReplaceUtility {
}
}
+ /**
+ * Disposes of the shared find/replace dialog.
+ */
public static void dispose() {
FIND_REPLACE_DIALOG.dispose();
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/GroovyFilter.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/GroovyFilter.java
index d2ae3a6436..c2a7ed6086 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/GroovyFilter.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/GroovyFilter.java
@@ -32,39 +32,99 @@ import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
+/**
+ * Regex-based syntax-highlighting filter for Groovy source text.
+ */
public class GroovyFilter extends StructuredSyntaxDocumentFilter {
// java tab policy action
private static final Action AUTO_TAB_ACTION = new AutoTabAction();
// Style names
+ /**
+ * Style name used for comments.
+ */
public static final String COMMENT = "comment";
+ /**
+ * Pattern matching block comments.
+ */
public static final String SLASH_STAR_COMMENT = "/\\*(?s:.)*?(?:\\*/|\\z)";
+ /**
+ * Pattern matching line comments.
+ */
public static final String SLASH_SLASH_COMMENT = "//.*";
+ /**
+ * Pattern matching double-quoted and triple-double-quoted strings.
+ */
public static final String QUOTES =
"(?ms:\"{3}.*?(?:\"{3}|\\z))|(?:\"{1}.*?(?:\"|\\Z))";
+ /**
+ * Pattern matching single-quoted and triple-single-quoted strings.
+ */
public static final String SINGLE_QUOTES =
"(?ms:'{3}(?!'{1,3}).*?(?:'{3}|\\z))|(?:'{1}.*?(?:'|\\z))";
+ /**
+ * Pattern matching slashy and dollar-slashy strings.
+ */
public static final String SLASHY_QUOTES = "(?:/[^/*].*?(?<!\\\\)/|(?ms:\\$/.*?(?:/\\$|\\z)))";
+ /**
+ * Style name used for numeric literals.
+ */
public static final String DIGIT = "DIGIT";
+ /**
+ * Pattern matching decimal integer literals.
+ */
public static final String DECIMAL_INTEGER_LITERAL = "(?:0|[1-9](?:[_0-9]*[0-9])?)[lL]?";
+ /**
+ * Pattern matching hexadecimal integer literals.
+ */
public static final String HEX_INTEGER_LITERAL = "0[xX][0-9a-fA-F](?:[0-9a-fA-F_]*[0-9a-fA-F])?";
+ /**
+ * Pattern matching octal integer literals.
+ */
public static final String OCTAL_INTEGER_LITERAL = "0[0-7](?:[_0-7]*[0-7])?";
+ /**
+ * Pattern matching binary integer literals.
+ */
public static final String BINARY_INTEGER_LITERAL = "0[bB][01](?:[_01]*[01])?";
+ /**
+ * Pattern matching decimal floating-point literals.
+ */
public static final String DECIMAL_FLOATING_POINT_LITERAL = "(?:0|[1-9](?:[_0-9]*[0-9])?)?\\.?[0-9](?:[_0-9]*[0-9])?(?:[eE][+-]?[0-9]+(?:[_0-9]*[0-9])?)?[fFdD]?";
+ /**
+ * Pattern matching hexadecimal floating-point literals.
+ */
public static final String HEXADECIMAL_FLOATING_POINT_LITERAL = "0[xX](?:[0-9a-fA-F](?:[0-9a-fA-F_]*[0-9a-fA-F])?)?\\.?(?:[0-9a-fA-F_]*[0-9a-fA-F])?(?:[pP][+-]?[0-9]+(?:[_0-9]*[0-9])?)?[fFdD]?";
+ /**
+ * Pattern matching annotations.
+ */
public static final String ANNOTATION = "@[A-Za-z](?:[\\w.]*\\w)?";
+ /**
+ * Pattern matching identifiers.
+ */
public static final String IDENT = "[\\w\\$&&[\\D]][\\w\\$]*";
+ /**
+ * Pattern matching method-style identifiers followed by an opening parenthesis.
+ */
public static final String OPERATION = "[\\w\\$&&[\\D]][\\w\\$]* *\\(";
+ /**
+ * Pattern matching left parentheses inside operation matches.
+ */
public static final String LEFT_PARENS = "\\(";
+ /**
+ * Style name used for reserved words.
+ */
public static final String RESERVED_WORD = "reserved";
+ /**
+ * Patterns matching Groovy reserved words.
+ */
public static final String[] RESERVED_WORDS = {"\\babstract\\b",
"\\bassert\\b",
"\\bdefault\\b",
@@ -125,6 +185,8 @@ public class GroovyFilter extends StructuredSyntaxDocumentFilter {
/**
* Creates a new instance of GroovyFilter
+ *
+ * @param doc the document to highlight
*/
public GroovyFilter(DefaultStyledDocument doc) {
super(doc);
@@ -189,6 +251,11 @@ public class GroovyFilter extends StructuredSyntaxDocumentFilter {
getRootNode().putChild(IDENT, node);
}
+ /**
+ * Installs the auto-indent action on the supplied text component.
+ *
+ * @param tComp the text component to configure
+ */
public static void installAutoTabAction(JTextComponent tComp) {
tComp.getActionMap().put("GroovyFilter-autoTab", AUTO_TAB_ACTION);
KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false);
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java
index 5978fe2352..b3be8dd8b9 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java
@@ -71,6 +71,12 @@ public class MatchingHighlighter implements CaretListener {
);
private volatile List<Tuple3<Integer, Position, Integer>> highlightedTokenInfoList = Collections.emptyList();
+ /**
+ * Creates a highlighter bound to the supplied document filter and editor.
+ *
+ * @param smartDocumentFilter the token source used to find matching delimiters
+ * @param textEditor the text pane whose delimiters are highlighted
+ */
public MatchingHighlighter(SmartDocumentFilter smartDocumentFilter, JTextPane textEditor) {
this.smartDocumentFilter = smartDocumentFilter;
this.textEditor = textEditor;
@@ -79,11 +85,17 @@ public class MatchingHighlighter implements CaretListener {
initStyles();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void caretUpdate(CaretEvent e) {
highlight();
}
+ /**
+ * Schedules highlighting for the delimiter nearest the caret.
+ */
public void highlight() {
// `SwingUtilities.invokeLater` is used to avoid "java.lang.IllegalStateException: Attempt to mutate in notification"
SwingUtilities.invokeLater(this::doHighlight);
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java
index c6f54867dd..25dcc7d393 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java
@@ -133,6 +133,9 @@ import static org.apache.groovy.parser.antlr4.GroovyLexer.YIELD;
* @since 3.0.0
*/
public class SmartDocumentFilter extends DocumentFilter {
+ /**
+ * Token types rendered with the reserved-word style.
+ */
public static final List<Integer> HIGHLIGHTED_TOKEN_TYPE_LIST =
Arrays.asList(AS, ASYNC, AWAIT, DEF, DEFER, IN, TRAIT, THREADSAFE,
VAR, BuiltInPrimitiveType, ABSTRACT, ASSERT, BREAK, CASE, CATCH, CLASS, CONST, CONTINUE, DEFAULT, DO,
@@ -145,6 +148,11 @@ public class SmartDocumentFilter extends DocumentFilter {
private final StyleContext styleContext;
private final Style defaultStyle;
+ /**
+ * Creates a filter that styles the supplied document.
+ *
+ * @param styledDocument the document to tokenize and highlight
+ */
public SmartDocumentFilter(DefaultStyledDocument styledDocument) {
this.styledDocument = styledDocument;
@@ -155,6 +163,9 @@ public class SmartDocumentFilter extends DocumentFilter {
initStyles();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void insertString(DocumentFilter.FilterBypass fb, int offset,
String text, AttributeSet attrs) throws BadLocationException {
@@ -165,6 +176,9 @@ public class SmartDocumentFilter extends DocumentFilter {
parseDocument();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
throws BadLocationException {
@@ -173,6 +187,9 @@ public class SmartDocumentFilter extends DocumentFilter {
parseDocument();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void replace(DocumentFilter.FilterBypass fb, int offset,
int length, String text, AttributeSet attrs)
@@ -489,17 +506,38 @@ public class SmartDocumentFilter extends DocumentFilter {
private volatile List<Token> latestTokenList = Collections.emptyList();
private volatile Tuple2<Integer, Integer> renderRange;
+ /**
+ * Indicates whether the cached token list reflects the current document text.
+ *
+ * @return {@code true} if the latest parse completed successfully
+ */
public boolean isLatest() {
return latest;
}
+ /**
+ * Returns the tokens produced by the most recent successful parse.
+ *
+ * @return the latest token list
+ */
public List<Token> getLatestTokenList() {
return latestTokenList;
}
+ /**
+ * Limits rendering to the supplied character range for the next parse.
+ *
+ * @param renderRange the inclusive start and stop offsets to re-render, or {@code null} for all text
+ */
public void setRenderRange(Tuple2<Integer, Integer> renderRange) {
this.renderRange = renderRange;
}
+
+ /**
+ * Returns the character range scheduled for selective re-rendering.
+ *
+ * @return the current render range, or {@code null} if the whole document will be rendered
+ */
public Tuple2<Integer, Integer> getRenderRange() {
return renderRange;
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxDocumentFilter.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxDocumentFilter.java
index 9437c87a45..0a8537bdfc 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxDocumentFilter.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxDocumentFilter.java
@@ -40,8 +40,14 @@ import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * Document filter that applies regex-driven syntax highlighting to styled text.
+ */
public class StructuredSyntaxDocumentFilter extends DocumentFilter {
+ /**
+ * Spaces inserted in place of tab characters.
+ */
public static final String TAB_REPLACEMENT = " ";
private static final MLComparator ML_COMPARATOR = new MLComparator();
@@ -51,7 +57,9 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
*/
protected LexerNode lexer = new LexerNode(true);
- // The styled document the filter parses
+ /**
+ * The styled document currently being parsed.
+ */
protected DefaultStyledDocument styledDocument;
// the document buffer and segment
@@ -272,6 +280,9 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
return string;
}
+ /**
+ * Regex-driven lexer node that applies styles and nested lexers.
+ */
public final class LexerNode {
private Style defaultStyle;
@@ -286,8 +297,10 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
private CharBuffer lastBuffer;
- /*
- * Creates a new instance of LexerNode
+ /**
+ * Creates a lexer node.
+ *
+ * @param isParent {@code true} if this node is the root node
*/
LexerNode(boolean isParent) {
StyleContext sc = StyleContext.getDefaultStyleContext();
@@ -305,6 +318,11 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
return regexp.substring(1);
}
+ /**
+ * Returns the fallback style for unmatched text.
+ *
+ * @return the default style
+ */
public Style getDefaultStyle() {
return defaultStyle;
}
@@ -340,10 +358,12 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
}
/**
- * @param buffer
- * @param offset
- * @param length
- * @throws BadLocationException
+ * Parses the supplied buffer and applies styles to matching ranges.
+ *
+ * @param buffer the character buffer to parse
+ * @param offset the starting offset
+ * @param length the number of characters to parse
+ * @throws BadLocationException if document positions cannot be resolved
*/
public void parse(CharBuffer buffer, int offset, int length) throws BadLocationException {
// get the index of where we can start to look for an exit:
@@ -416,8 +436,10 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
}
/**
- * @param regexp
- * @param node
+ * Registers a child lexer for matches of the supplied expression.
+ *
+ * @param regexp the expression to match
+ * @param node the child lexer to apply within matching text
*/
public void putChild(String regexp, LexerNode node) {
node.defaultStyle = styleMap.get(regexp);
@@ -428,16 +450,20 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
}
/**
- * @param regexps
- * @param node
+ * Registers a child lexer for matches of any supplied expression.
+ *
+ * @param regexps the expressions to match
+ * @param node the child lexer to apply within matching text
*/
public void putChild(String[] regexps, LexerNode node) {
putChild(buildRegexp(regexps), node);
}
/**
- * @param regexp
- * @param style
+ * Associates a style with matches of the supplied expression.
+ *
+ * @param regexp the expression to match
+ * @param style the style to apply
*/
public void putStyle(String regexp, Style style) {
checkRegexp(regexp);
@@ -446,22 +472,28 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
}
/**
- * @param regexps
- * @param style
+ * Associates a style with matches of any supplied expression.
+ *
+ * @param regexps the expressions to match
+ * @param style the style to apply
*/
public void putStyle(String[] regexps, Style style) {
putStyle(buildRegexp(regexps), style);
}
/**
- * @param regexp
+ * Removes the child lexer registered for the supplied expression.
+ *
+ * @param regexp the expression whose child lexer should be removed
*/
public void removeChild(String regexp) {
children.remove(regexp);
}
/**
- * @param regexp
+ * Removes the style associated with the supplied expression.
+ *
+ * @param regexp the expression whose style should be removed
*/
public void removeStyle(String regexp) {
styleMap.remove(regexp);
@@ -469,27 +501,52 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
}
/**
- * @param regexps
+ * Removes the style associated with any of the supplied expressions.
+ *
+ * @param regexps the expressions whose combined style should be removed
*/
public void removeStyle(String[] regexps) {
removeStyle(buildRegexp(regexps));
}
+ /**
+ * Sets the fallback style for unmatched text.
+ *
+ * @param style the default style to use
+ */
public void setDefaultStyle(Style style) {
defaultStyle = style;
}
}
+ /**
+ * Tracks a multi-line styled region within the document.
+ */
protected class MultiLineRun {
private Position start;
private Position end;
private int delimiterSize;
+ /**
+ * Creates a multi-line run using a two-character delimiter.
+ *
+ * @param start the inclusive start offset
+ * @param end the inclusive end offset
+ * @throws BadLocationException if either offset is invalid
+ */
public MultiLineRun(int start, int end) throws BadLocationException {
this(start, end, 2);
}
+ /**
+ * Creates a multi-line run with the supplied delimiter size.
+ *
+ * @param start the inclusive start offset
+ * @param end the inclusive end offset
+ * @param delimiterSize the delimiter width in characters
+ * @throws BadLocationException if either offset is invalid
+ */
public MultiLineRun(int start, int end, int delimiterSize) throws BadLocationException {
if (start > end) {
String msg = "Start offset is after end: ";
@@ -504,6 +561,11 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
this.delimiterSize = delimiterSize;
}
+ /**
+ * Returns the delimiter width used by this run.
+ *
+ * @return the delimiter size in characters
+ */
public int getDelimiterSize() {
return delimiterSize;
}
@@ -516,18 +578,38 @@ public class StructuredSyntaxDocumentFilter extends DocumentFilter {
return getDelimiterSize();
}
+ /**
+ * Returns the inclusive end offset.
+ *
+ * @return the end offset
+ */
public int end() {
return end.getOffset();
}
+ /**
+ * Returns the run length.
+ *
+ * @return the number of characters in the run
+ */
public int length() {
return end.getOffset() - start.getOffset();
}
+ /**
+ * Returns the inclusive start offset.
+ *
+ * @return the start offset
+ */
public int start() {
return start.getOffset();
}
+ /**
+ * Returns a textual description of the run bounds.
+ *
+ * @return the start and end positions
+ */
@Override
public String toString() {
return start.toString() + " " + end.toString();
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxHandler.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxHandler.java
index 2f811acfb9..6b05b1af72 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxHandler.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxHandler.java
@@ -23,44 +23,149 @@ import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
+/**
+ * SAX handler that reads structured syntax definitions into a document filter.
+ */
public class StructuredSyntaxHandler extends DefaultHandler {
//StyleConstants.
+ /**
+ * XML element name for regular expression entries.
+ */
public static final String REGEXP = "regexp";
+ /**
+ * XML element name for style entries.
+ */
public static final String STYLE = "style";
+ /**
+ * Alignment value name for centered text.
+ */
public static final String ALIGN_CENTER = "ALIGN_CENTER";
+ /**
+ * Alignment value name for justified text.
+ */
public static final String ALIGN_JUSTIFIED = "ALIGN_JUSTIFIED";
+ /**
+ * Alignment value name for left-aligned text.
+ */
public static final String ALIGN_LEFT = "ALIGN_LEFT";
+ /**
+ * Alignment value name for right-aligned text.
+ */
public static final String ALIGN_RIGHT = "ALIGN_RIGHT";
+ /**
+ * Style attribute name for alignment.
+ */
public static final String ALIGNMENT = "alignment";
+ /**
+ * Style attribute name for background color.
+ */
public static final String BACKGROUND = "background";
+ /**
+ * Style attribute name for bidirectional text level.
+ */
public static final String BIDI_LEVEL = "bidiLevel";
+ /**
+ * Style attribute name for bold text.
+ */
public static final String BOLD = "bold";
+ /**
+ * Style attribute name for embedded components.
+ */
public static final String COMPONENT_ATTRIBUTE = "componentAttribute";
+ /**
+ * Style attribute name for component element names.
+ */
public static final String COMPONENT_ELEMENT_NAME = "componentElementName";
+ /**
+ * Style attribute name for composed text.
+ */
public static final String COMPOSED_TEXT_ATTRIBUTE = "composedTextAttribute";
+ /**
+ * Style attribute name for first-line indentation.
+ */
public static final String FIRST_LINE_INDENT = "firstLineIndent";
+ /**
+ * Style attribute name for font family.
+ */
public static final String FONT_FAMILY = "fontFamily";
+ /**
+ * Style attribute name for font size.
+ */
public static final String FONT_SIZE = "fontSize";
+ /**
+ * Style attribute name for foreground color.
+ */
public static final String FOREGROUND = "foreground";
+ /**
+ * Style attribute name for embedded icons.
+ */
public static final String ICON_ATTRIBUTE = "iconAttribute";
+ /**
+ * Style attribute name for icon element names.
+ */
public static final String ICON_ELEMENT_NAME = "iconElementName";
+ /**
+ * Style attribute name for italic text.
+ */
public static final String ITALIC = "italic";
+ /**
+ * Style attribute name for left indentation.
+ */
public static final String LEFT_INDENT = "leftIndent";
+ /**
+ * Style attribute name for line spacing.
+ */
public static final String LINE_SPACING = "lineSpacing";
+ /**
+ * Style attribute name for model attributes.
+ */
public static final String MODEL_ATTRIBUTE = "modelAttribute";
+ /**
+ * Style attribute name for named attributes.
+ */
public static final String NAME_ATTRIBUTE = "nameAttribute";
+ /**
+ * Style attribute name for orientation.
+ */
public static final String ORIENTATION = "orientation";
+ /**
+ * Style attribute name for resolve attributes.
+ */
public static final String RESOLVE_ATTRIBUTE = "resolveAttribute";
+ /**
+ * Style attribute name for right indentation.
+ */
public static final String RIGHT_INDENT = "rightIndent";
+ /**
+ * Style attribute name for space above paragraphs.
+ */
public static final String SPACE_ABOVE = "spaceAbove";
+ /**
+ * Style attribute name for space below paragraphs.
+ */
public static final String SPACE_BELOW = "spaceBelow";
+ /**
+ * Style attribute name for strike-through text.
+ */
public static final String STRIKE_THROUGH = "strikeThrough";
+ /**
+ * Style attribute name for subscript text.
+ */
public static final String SUBSCRIPT = "subscript";
+ /**
+ * Style attribute name for superscript text.
+ */
public static final String SUPERSCRIPT = "superscript";
+ /**
+ * Style attribute name for tab sets.
+ */
public static final String TAB_SET = "tabSet";
+ /**
+ * Style attribute name for underlined text.
+ */
public static final String UNDERLINE = "underline";
private StructuredSyntaxDocumentFilter.LexerNode currentNode;
@@ -68,40 +173,58 @@ public class StructuredSyntaxHandler extends DefaultHandler {
private final StructuredSyntaxDocumentFilter filter;
/**
- * Creates a new instance of MasterFrameHandler
+ * Creates a handler for the supplied syntax filter.
*
- * @param filter
+ * @param filter the filter receiving parsed syntax definitions
*/
public StructuredSyntaxHandler(StructuredSyntaxDocumentFilter filter) {
this.filter = filter;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void characters(char[] ch, int start, int length) {
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void endElement(String uri,
String localName,
String qName) throws SAXException {
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void error(SAXParseException e) throws SAXException {
throw new SAXException("Line: " + e.getLineNumber() + " message: " + e.getMessage());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void startDocument() throws SAXException {
super.startDocument();
currentNode = filter.getRootNode();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void startElement(String uri,
String localName,
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxResources.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxResources.java
index f2061d8508..a5ca160473 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxResources.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/StructuredSyntaxResources.java
@@ -32,6 +32,9 @@ public final class StructuredSyntaxResources {
// ClipBoard
// ==================================================
+ /**
+ * Shared clipboard used by the console text components.
+ */
public static final Clipboard SYSTEM_CLIPBOARD = getSystemOrAppLevelClipboard();
private static Clipboard getSystemOrAppLevelClipboard() {
@@ -62,21 +65,51 @@ public final class StructuredSyntaxResources {
// STANDARD FONTS
// =====================================================
+ /**
+ * Large UI font used by legacy console components.
+ */
public static final Font LARGE_FONT = Font.decode("Arial-24");
+ /**
+ * Medium UI font used by legacy console components.
+ */
public static final Font MEDIUM_FONT = Font.decode("Arial-18");
+ /**
+ * Small UI font used by legacy console components.
+ */
public static final Font SMALL_FONT = Font.decode("Arial-12");
+ /**
+ * Default monospaced font for editors.
+ */
public static final Font EDITOR_FONT = Font.decode("Monospaced-12");
// =====================================================
// UNDO/REDO NAMES
// =====================================================
+ /**
+ * Presentation name for undo actions.
+ */
public static final String UNDO = "Undo";
+ /**
+ * Presentation name for redo actions.
+ */
public static final String REDO = "Redo";
+ /**
+ * Presentation name for print actions.
+ */
public static final String PRINT = "Print";
+ /**
+ * Presentation name for find actions.
+ */
public static final String FIND = "Find";
+ /**
+ * Presentation name for find-next actions.
+ */
public static final String FIND_NEXT = "Find Next";
+ /**
+ * Presentation name for replace actions.
+ */
public static final String REPLACE = "Replace";
// singleton
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java
index 332c1b5415..d9aa201bcb 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java
@@ -68,11 +68,29 @@ import java.util.regex.Pattern;
public class TextEditor extends JTextPane implements Pageable, Printable {
@Serial private static final long serialVersionUID = 8478062945229999402L;
+ /**
+ * Action name for opening the find dialog.
+ */
public static final String FIND = "Find...";
+ /**
+ * Action name for repeating the next find.
+ */
public static final String FIND_NEXT = "Find Next";
+ /**
+ * Action name for repeating the previous find.
+ */
public static final String FIND_PREVIOUS = "Find Previous";
+ /**
+ * Action name for opening the replace dialog.
+ */
public static final String REPLACE = "Replace...";
+ /**
+ * Action name for automatic indentation.
+ */
public static final String AUTO_INDENT = "AutoIndent";
+ /**
+ * Action name for backspace deletion.
+ */
public static final String DELETE = "delete";
private static final String TABBED_SPACES = " ";
@@ -131,6 +149,8 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
/**
* Creates a new instance of TextEditor
+ *
+ * @param tabsAsSpaces whether typed tabs should be converted to spaces
*/
public TextEditor(boolean tabsAsSpaces) {
this(tabsAsSpaces, false);
@@ -138,6 +158,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
/**
* Creates a new instance of TextEditor
+ *
+ * @param tabsAsSpaces whether typed tabs should be converted to spaces
+ * @param multiLineTab whether tab actions should affect all selected lines
*/
public TextEditor(boolean tabsAsSpaces, boolean multiLineTab) {
this(tabsAsSpaces, multiLineTab, false);
@@ -145,6 +168,10 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
/**
* Creates a new instance of TextEditor
+ *
+ * @param tabsAsSpaces whether typed tabs should be converted to spaces
+ * @param multiLineTab whether tab actions should affect all selected lines
+ * @param unwrapped whether long lines should avoid viewport wrapping
*/
public TextEditor(boolean tabsAsSpaces, boolean multiLineTab, boolean unwrapped) {
this.tabsAsSpaces = tabsAsSpaces;
@@ -213,6 +240,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
overtypeCaret.setBlinkRate(defaultCaret.getBlinkRate());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void addNotify() {
super.addNotify();
@@ -220,6 +250,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
FindReplaceUtility.registerTextComponent(this);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public int getNumberOfPages() {
Paper paper = PAGE_FORMAT.getPaper();
@@ -227,16 +260,25 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
return numPages;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public PageFormat getPageFormat(int pageIndex) throws IndexOutOfBoundsException {
return PAGE_FORMAT;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public Printable getPrintable(int param) throws IndexOutOfBoundsException {
return this;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public int print(Graphics graphics, PageFormat pageFormat, int page)
throws PrinterException {
@@ -301,6 +343,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
return Printable.NO_SUCH_PAGE;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public boolean getScrollableTracksViewportWidth() {
boolean bool = super.getScrollableTracksViewportWidth();
@@ -343,6 +388,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
return unwrapped;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
protected void processKeyEvent(KeyEvent e) {
super.processKeyEvent(e);
@@ -357,6 +405,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
}
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void removeNotify() {
super.removeNotify();
@@ -364,6 +415,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
FindReplaceUtility.unregisterTextComponent(this);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void replaceSelection(String text) {
// Implement overtype mode by selecting the character at the current
@@ -377,6 +431,9 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
super.replaceSelection(text);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void setBounds(int x, int y, int width, int height) {
if (unwrapped) {
@@ -390,6 +447,8 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
}
/**
+ * Enables or disables indenting all selected lines when tabbing.
+ *
* @param multiLineTab the new multiLine tab value
*/
public void isMultiLineTabbed(boolean multiLineTab) {
@@ -397,6 +456,8 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
}
/**
+ * Enables or disables replacing typed tab characters with spaces.
+ *
* @param tabsAsSpaces whether tabs are converted to spaces
*/
public void isTabsAsSpaces(boolean tabsAsSpaces) {
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextUndoManager.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextUndoManager.java
index 5a83a9e20e..45108e694d 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextUndoManager.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextUndoManager.java
@@ -60,10 +60,18 @@ public class TextUndoManager extends UndoManager {
this.recording = recording;
}
+ /**
+ * Registers a listener for undo/redo state changes.
+ *
+ * @param pcl the listener to add
+ */
public void addPropertyChangeListener(PropertyChangeListener pcl) {
propChangeSupport.addPropertyChangeListener(pcl);
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void die() {
boolean undoable = canUndo();
@@ -71,6 +79,9 @@ public class TextUndoManager extends UndoManager {
firePropertyChangeEvent(UndoManager.UndoName, undoable, canUndo());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void discardAllEdits() {
boolean undoable = canUndo();
@@ -84,16 +95,31 @@ public class TextUndoManager extends UndoManager {
firePropertyChangeEvent(UndoManager.UndoName, redoable, canRedo());
}
+ /**
+ * Fires an undo-state property change event.
+ *
+ * @param name the property name
+ * @param oldValue the previous value
+ * @param newValue the new value
+ */
protected void firePropertyChangeEvent(String name,
boolean oldValue,
boolean newValue) {
propChangeSupport.firePropertyChange(name, oldValue, newValue);
}
+ /**
+ * Indicates whether the document differs from the last reset point.
+ *
+ * @return {@code true} if undo history contains unreset edits
+ */
public boolean hasChanged() {
return modificationMarker != editToBeUndone();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void redo() throws javax.swing.undo.CannotRedoException {
compoundEdit.end();
@@ -110,6 +136,9 @@ public class TextUndoManager extends UndoManager {
firePropertyChangeEvent(UndoManager.UndoName, undoable, canUndo());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
protected void redoTo(UndoableEdit edit) {
compoundEdit.end();
@@ -127,10 +156,18 @@ public class TextUndoManager extends UndoManager {
}
+ /**
+ * Removes a listener for undo/redo state changes.
+ *
+ * @param pcl the listener to remove
+ */
public void removePropertyChangeListener(PropertyChangeListener pcl) {
propChangeSupport.removePropertyChangeListener(pcl);
}
+ /**
+ * Marks the current undo position as the unmodified state.
+ */
public void reset() {
boolean changed = modificationMarker != editToBeUndone();
if (changed) {
@@ -138,6 +175,9 @@ public class TextUndoManager extends UndoManager {
}
}
+ /**
+ * {@inheritDoc}
+ */
@Override
protected void trimEdits(int from, int to) {
boolean undoable = canUndo();
@@ -150,6 +190,9 @@ public class TextUndoManager extends UndoManager {
firePropertyChangeEvent(UndoManager.RedoName, redoable, canRedo());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void undo() throws javax.swing.undo.CannotUndoException {
compoundEdit.end();
@@ -168,6 +211,9 @@ public class TextUndoManager extends UndoManager {
firePropertyChangeEvent(UndoManager.RedoName, redoable, canRedo());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void undoableEditHappened(UndoableEditEvent uee) {
if (!recording) {
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicContentPane.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicContentPane.groovy
index c1fe372586..32a4c44d3c 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicContentPane.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicContentPane.groovy
@@ -44,10 +44,13 @@ import static javax.swing.JSplitPane.VERTICAL_SPLIT
Preferences prefs = Preferences.userNodeForPackage(Console)
def detachedOutputFlag = prefs.getBoolean('detachedOutput', false)
+/** Detached output frame shown when console output is separated from the main window. */
outputWindow = frame(visible: false, defaultCloseOperation: WindowConstants.HIDE_ON_CLOSE) {
+ /** Placeholder component swapped in while the output area is detached. */
blank = glue()
blank.preferredSize = [0, 0] as Dimension
}
+/** Split pane that hosts the editor and the attached output area. */
splitPane = splitPane(resizeWeight: 0.5, orientation:
prefs.getBoolean('orientationVertical', true) ? VERTICAL_SPLIT : HORIZONTAL_SPLIT) {
def editor = new ConsoleTextEditor()
@@ -55,12 +58,15 @@ splitPane = splitPane(resizeWeight: 0.5, orientation:
if (smartHighlighterEnabled) {
editor.enableHighLighter(SmartDocumentFilter)
}
+ /** Editor wrapper used for script input. */
inputEditor = widget(editor, border: emptyBorder(0))
buildOutputArea(prefs)
}
private def buildOutputArea(prefs) {
+ /** Scroll pane that contains the console output component. */
scrollArea = scrollPane(border: emptyBorder(0)) {
+ /** Read-only text pane used to render console output. */
outputArea = textPane(
editable: false,
name: 'outputArea',
@@ -73,6 +79,7 @@ private def buildOutputArea(prefs) {
}
+/** Text component backing the script input area. */
inputArea = inputEditor.textEditor
inputArea.background = ThemeManager.inputBackground
// attach ctrl-enter to input area
@@ -102,21 +109,27 @@ def applyStyle = { Style style, values -> values.each { k, v -> style.addAttribu
Style regular = doc.addStyle('regular', defStyle)
applyStyle(regular, styles.regular)
+/** Style used for console prompts. */
promptStyle = doc.addStyle('prompt', regular)
applyStyle(promptStyle, styles.prompt)
+/** Style used for echoed commands. */
commandStyle = doc.addStyle('command', regular)
applyStyle(commandStyle, styles.command)
+/** Style used for standard output. */
outputStyle = doc.addStyle('output', regular)
applyStyle(outputStyle, styles.output)
+/** Style used for evaluation results. */
resultStyle = doc.addStyle('result', regular)
applyStyle(resultStyle, styles.result)
+/** Style used for stack traces. */
stacktraceStyle = doc.addStyle('stacktrace', regular)
applyStyle(stacktraceStyle, styles.stacktrace)
+/** Style used for hyperlinks in the output pane. */
hyperlinkStyle = doc.addStyle('hyperlink', regular)
applyStyle(hyperlinkStyle, styles.hyperlink)
@@ -158,6 +171,7 @@ inputEditor.preferredSize = [
prefs.getInt('inputAreaHeight', (fm.getHeight() + fm.getLeading()) * 12)
] as Dimension
+/** Original split-pane divider size restored when output is reattached. */
origDividerSize = -1
if (detachedOutputFlag) {
splitPane.add(blank, JSplitPane.BOTTOM)
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicMenuBar.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicMenuBar.groovy
index 8fa5045dfe..83ad4ec7ae 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicMenuBar.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicMenuBar.groovy
@@ -72,8 +72,11 @@ menuBar {
separator()
menu(text: 'Theme') {
buttonGroup(id: 'themeGroup')
+ /** Menu item that selects the light theme. */
lightThemeMenuItem = radioButtonMenuItem(lightThemeAction, buttonGroup: themeGroup, selected: controller.currentTheme == 'LIGHT')
+ /** Menu item that selects the dark theme. */
darkThemeMenuItem = radioButtonMenuItem(darkThemeAction, buttonGroup: themeGroup, selected: controller.currentTheme == 'DARK')
+ /** Menu item that selects the system theme. */
systemThemeMenuItem = radioButtonMenuItem(systemThemeAction, buttonGroup: themeGroup, selected: controller.currentTheme == 'SYSTEM')
}
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicStatusBar.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicStatusBar.groovy
index 36c649be82..b205395d72 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicStatusBar.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicStatusBar.groovy
@@ -22,14 +22,17 @@ import javax.swing.SwingConstants
import java.awt.BorderLayout
import java.awt.GridBagConstraints
+/** Status bar panel shown at the bottom of the console window. */
statusPanel = panel(constraints: BorderLayout.SOUTH) {
gridBagLayout()
separator(gridwidth: GridBagConstraints.REMAINDER, fill: GridBagConstraints.HORIZONTAL)
+ /** Label displaying the current console status message. */
status = label("Welcome to Groovy ${GroovySystem.version}.",
weightx: 1.0,
anchor: GridBagConstraints.WEST,
fill: GridBagConstraints.HORIZONTAL,
insets: [1, 3, 1, 3])
separator(orientation: SwingConstants.VERTICAL, fill: GridBagConstraints.VERTICAL)
+ /** Label displaying the current caret line and column. */
rowNumAndColNum = label('1:1', insets: [1, 3, 1, 3])
}
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicToolBar.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicToolBar.groovy
index 07cd511532..559a117996 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicToolBar.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/BasicToolBar.groovy
@@ -26,6 +26,7 @@ import java.awt.BorderLayout
// Toolbar buttons override each action's smallIcon with a resizable toolbar-only
// icon — so View → Icon Size / Scale with Font affect toolbar icons only,
// not the matching menu items (which keep the fixed-size menu icons).
+/** Toolbar shown above the editor and output area. */
toolbar = toolBar(rollover: true, visible: controller.showToolbar, constraints: BorderLayout.NORTH) {
button(newFileAction, text: null, icon: Icons.toolbar('note_add'))
button(openAction, text: null, icon: Icons.toolbar('folder_open'))
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/GTKDefaults.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/GTKDefaults.groovy
index 16c88fbc79..3166d5e1d3 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/GTKDefaults.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/GTKDefaults.groovy
@@ -45,6 +45,7 @@ try {
ps.isAttributeValueSupported(orient, docFlav, attrset)
} catch (NullPointerException npe) {
//print will bomb out... replace with disabled print action
+ /** Disabled print action used when native printing support is unavailable. */
printAction = action(
name: 'Print...',
closure: controller.&print,
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXDefaults.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXDefaults.groovy
index 2bb5fe7ac4..5a7f10fafe 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXDefaults.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXDefaults.groovy
@@ -35,4 +35,5 @@ styles.regular[StyleConstants.FontFamily] = fontFamily
styles[StyleContext.DEFAULT_STYLE] = styles[StyleContext.DEFAULT_STYLE] ?: [:]
styles[StyleContext.DEFAULT_STYLE][StyleConstants.FontFamily] = fontFamily
-menuBarClass = MacOSXMenuBar
\ No newline at end of file
+/** Menu bar implementation used for macOS screen-menu integration. */
+menuBarClass = MacOSXMenuBar
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXMenuBar.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXMenuBar.groovy
index 3ef144cb8d..19db4d15bd 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXMenuBar.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/view/MacOSXMenuBar.groovy
@@ -236,6 +236,12 @@ menuBar {
}
}
+/**
+ * Tests whether the current class loader can resolve the supplied class name.
+ *
+ * @param className the fully qualified class name to resolve
+ * @return {@code true} if the class is available
+ */
static classExists(String className) {
try {
MacOSXMenuBar.class.classLoader.loadClass(className)
diff --git a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy
index 6ab8f48c3c..e77bfca59c 100644
--- a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy
+++ b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy
@@ -72,7 +72,14 @@ class FormatStringChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckin
private static final ClassNode LOCALE_TYPE = makeCached(Locale)
private static final ClassNode FORMATTER_TYPE = makeCached(Formatter)
+ /**
+ * Pattern used to extract {@link Formatter} conversion specifiers from constant format strings.
+ */
private String formatSpecifier = /%(\d+\$)?([-#+ 0,(\<]*)?(\d+)?(\.\d+)?([tT])?([a-zA-Z%])/
+
+ /**
+ * AST patterns for formatter-style methods that this checker validates automatically.
+ */
private List<ASTNode> formatMethods = [
macro(CompilePhase.SEMANTIC_ANALYSIS) { String.format(a) }.withConstraints { varargPlaceholder a },
macro { _.format(a) }.withConstraints { varargPlaceholder a },
@@ -83,6 +90,9 @@ class FormatStringChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckin
macro { _.formatted(a) }.withConstraints{ varargPlaceholder a }
]
+ /**
+ * Registers checks for built-in formatter calls and methods annotated with {@link FormatMethod}.
+ */
@Override
Object run() {
afterMethodCall { call ->
diff --git a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/ModifiesChecker.groovy b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/ModifiesChecker.groovy
index 7412fc215c..87fcf24438 100644
--- a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/ModifiesChecker.groovy
+++ b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/ModifiesChecker.groovy
@@ -94,6 +94,9 @@ class ModifiesChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL
private static final Set<String> SIDE_EFFECT_FREE_ANNOS = Set.of('SideEffectFree')
private static final Set<String> CONTRACT_ANNOS = Set.of('Contract')
+ /**
+ * Registers frame-condition checks for methods visited by the type checker.
+ */
@Override
Object run() {
afterVisitMethod { MethodNode mn ->
diff --git a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/NullChecker.groovy b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/NullChecker.groovy
index 71ed0e24f6..e7ebb5a9c9 100644
--- a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/NullChecker.groovy
+++ b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/NullChecker.groovy
@@ -107,6 +107,9 @@ class NullChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
private static final Set<String> NONNULL_BY_DEFAULT_ANNOS = Set.of('NonNullByDefault', 'NonnullByDefault', 'NullMarked')
private static final Set<String> NULL_UNMARKED_ANNOS = Set.of('NullUnmarked')
+ /**
+ * Registers null-safety checks for each visited method body.
+ */
@Override
Object run() {
boolean strict = options?.strict ?: false
diff --git a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/PurityChecker.groovy b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/PurityChecker.groovy
index 198ca35d26..de9ceba61e 100644
--- a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/PurityChecker.groovy
+++ b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/PurityChecker.groovy
@@ -165,6 +165,9 @@ class PurityChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
'java.sql.',
]
+ /**
+ * Registers purity checks for annotated methods encountered during type checking.
+ */
@Override
Object run() {
Set<String> baseAllows = parseAllows(options?.allows as String)
diff --git a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy
index f53ef4f3ed..04d671a401 100644
--- a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy
+++ b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy
@@ -107,6 +107,9 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
private static final String REGEX_GROUP_COUNT = RegexChecker.getSimpleName() + '_INFERRED_GROUP_COUNT'
private static final String REGEX_MATCHER_RESULT_TYPE = RegexChecker.getSimpleName() + '_MATCHER_RESULT_INFERRED_TYPE'
+ /**
+ * Registers regex validation and matcher type-inference hooks for visited methods.
+ */
@Override
Object run() {
beforeVisitMethod { MethodNode method ->
diff --git a/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy b/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy
index f7b285fc1d..7ab6430786 100644
--- a/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy
+++ b/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy
@@ -28,16 +28,35 @@ import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codehaus.groovy.control.SourceUnit
+/**
+ * Base visitor support for type-checker extensions that need lightweight constant propagation
+ * and access to the underlying variable represented by a {@link VariableExpression}.
+ */
@AutoFinal @CompileStatic
class CheckingVisitor extends ClassCodeVisitorSupport {
+ /**
+ * Returns no backing source unit because subclasses report errors through the enclosing
+ * type-checking extension rather than this visitor.
+ */
@Override
protected SourceUnit getSourceUnit() {
null
}
+ /**
+ * Tracks local variables whose values are known constant expressions during the current visit.
+ */
protected final Map<Expression,Expression> localConstVars = [:]
+ /**
+ * Resolves a constant expression of the requested type from an expression, following tracked
+ * locals and field initializers when possible.
+ *
+ * @param exp expression to inspect
+ * @param type required runtime type for the constant value
+ * @return the matching constant expression, or {@code null} when none can be resolved
+ */
protected Expression findConstExp(Expression exp, Class type) {
if (exp instanceof ConstantExpression && type.isAssignableFrom(exp.value.getClass())) {
return exp
@@ -54,6 +73,12 @@ class CheckingVisitor extends ClassCodeVisitorSupport {
null
}
+ /**
+ * Follows {@link VariableExpression#accessedVariable} links until the original variable or field is found.
+ *
+ * @param ve variable expression to resolve
+ * @return the underlying target variable
+ */
protected Variable findTargetVariable(VariableExpression ve) {
def accessedVariable = ve.accessedVariable
if (accessedVariable != null && accessedVariable != ve) {