Copilot commented on code in PR #15367:
URL: https://github.com/apache/grails-core/pull/15367#discussion_r2748506166

##########
grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java:
##########
@@ -251,51 +241,50 @@ private boolean readPropOrTrue(String prop) {
         return property == null ? true : Boolean.valueOf(property);
     }
 
-    protected ConsoleReader createConsoleReader(InputStream systemIn) throws 
IOException {
-        // need to swap out the output to avoid logging during init
-        final PrintStream nullOutput = new PrintStream(new 
ByteArrayOutputStream());
-        final PrintStream originalOut = Log.getOutput();
-        try {
-            Log.setOutput(nullOutput);
-            ConsoleReader consoleReader = new ConsoleReader(systemIn, out);
-            consoleReader.setExpandEvents(false);
-            return consoleReader;
-        } finally {
-            Log.setOutput(originalOut);
-        }
+    protected LineReader createLineReader(Terminal terminal) throws 
IOException {
+        LineReader lineReader = LineReaderBuilder.builder()
+                .terminal(terminal)
+                .option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
+                .build();
+        return lineReader;
     }
 
     /**
-     * Creates the instance of Terminal used directly in GrailsConsole. Note 
that there is also
-     * another terminal instance created implicitly inside of ConsoleReader. 
That instance
-     * is controlled by the jline.terminal system property.
+     * Creates the instance of Terminal used directly in GrailsConsole.
      */
-    protected Terminal createTerminal() {
-        terminal = TerminalFactory.create();
-        if (isWindows()) {
-            terminal.setEchoEnabled(true);
-        }
+    protected Terminal createTerminal() throws IOException {
+        Terminal terminal = TerminalBuilder.builder()
+                .system(true)
+                .build();
         return terminal;
     }
 
     public void resetCompleters() {
-        final ConsoleReader reader = getReader();
-        if (reader != null) {
-            Collection<Completer> completers = reader.getCompleters();
-            for (Completer completer : completers) {
-                reader.removeCompleter(completer);
-            }
+        // In JLine 3, completers are set at LineReader creation time or via 
setCompleter
+        // We'll handle this differently - completers are managed via the 
LineReader
+    }
 
-            // for some unknown reason / bug in JLine you have to iterate over 
twice to clear the completers (WTF)
-            completers = reader.getCompleters();
-            for (Completer completer : completers) {
-                reader.removeCompleter(completer);
+    public void addCompleter(Completer completer) {
+        // In JLine 3, we need to recreate the LineReader with the new 
completer
+        // or use an AggregateCompleter. For now, this is a simplified 
implementation.
+        if (terminal != null) {
+            try {
+                reader = LineReaderBuilder.builder()
+                        .terminal(terminal)
+                        .completer(completer)
+                        .option(LineReader.Option.DISABLE_EVENT_EXPANSION, 
true)
+                        .build();
+                if (history != null) {
+                    reader.setVariable(LineReader.HISTORY_FILE, new 
File(System.getProperty("user.home"), HISTORYFILE).toPath());
+                }

Review Comment:
   The `addCompleter` method recreates the LineReader every time it's called, 
but doesn't properly transfer history state. When the reader is recreated, the 
history should be attached via `.history(history)` in the LineReaderBuilder, 
and the history file path should also be set. Currently, only the HISTORY_FILE 
variable is being set, which may not properly maintain history state across 
reader recreations.



##########
grails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/CreateAppCommand.groovy:
##########
@@ -119,37 +119,37 @@ class CreateAppCommand extends ArgumentCompletingCommand 
implements ProfileRepos
                     }.collect() { String pn ->
                         "${pn.substring(valStr.size())} ".toString()
                     }
-                    candidates.addAll(candidateProfiles)
-                    return cursor
+                    candidateProfiles.each { candidates.add(new 
org.jline.reader.Candidate(it)) }
+                    return
                 }
             } else if (lastOption.key == FEATURES_FLAG) {
                 def val = lastOption.value
                 def profile = 
profileRepository.getProfile(commandLine.hasOption(PROFILE_FLAG) ? 
commandLine.optionValue(PROFILE_FLAG).toString() : getDefaultProfile())
                 def featureNames = profile.features.collect() { Feature f -> 
f.name }
                 if (val == true) {
-                    candidates.addAll(featureNames)
-                    return cursor
+                    featureNames.each { candidates.add(new 
org.jline.reader.Candidate(it)) }
+                    return
                 } else if (!profileNames.contains(val)) {
                     def valStr = val.toString()
                     if (valStr.endsWith(',')) {
                         def specified = valStr.split(',')
-                        candidates.addAll(featureNames.findAll { String f ->
+                        featureNames.findAll { String f ->
                             !specified.contains(f)
-                        })
-                        return cursor
+                        }.each { candidates.add(new 
org.jline.reader.Candidate(it)) }
+                        return
                     }
 
                     def candidatesFeatures = featureNames.findAll { String pn 
->
                         pn.startsWith(valStr)
                     }.collect() { String pn ->
                         "${pn.substring(valStr.size())} ".toString()
                     }
-                    candidates.addAll(candidatesFeatures)
-                    return cursor
+                    candidatesFeatures.each { candidates.add(new 
org.jline.reader.Candidate(it)) }
+                    return

Review Comment:
   Same issue as with profile names: feature completion candidates should 
contain the full feature name, not just the suffix. The candidates should be 
created with the full feature name: `new org.jline.reader.Candidate(pn)` 
instead of `new org.jline.reader.Candidate("${pn.substring(valStr.size())} ")`.



##########
grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java:
##########
@@ -190,19 +187,12 @@ protected void initialize(InputStream systemIn, 
PrintStream systemOut, PrintStre
 
         redirectSystemOutAndErr(true);
 
-        System.setProperty(ShutdownHooks.JLINE_SHUTDOWNHOOK, "false");
-
         if (isInteractiveEnabled()) {
-            reader = createConsoleReader(systemIn);
-            reader.setBellEnabled(false);
-            reader.setCompletionHandler(new CandidateListCompletionHandler());
-            if (isActivateTerminal()) {
-                terminal = createTerminal();
-            }
-
+            terminal = createTerminal();
+            reader = createLineReader(terminal);
             history = prepareHistory();
             if (history != null) {
-                reader.setHistory(history);
+                reader.setVariable(LineReader.HISTORY_FILE, new 
File(System.getProperty("user.home"), HISTORYFILE).toPath());

Review Comment:
   History is not being properly attached to the LineReader. In JLine 3, 
history should be set via `.history(history)` in the LineReaderBuilder, not by 
setting `HISTORY_FILE` variable. The current implementation creates a 
`DefaultHistory` instance but never loads it from the history file or attaches 
it to the reader. The history file path should be set using 
`.variable(LineReader.HISTORY_FILE, path)` on the builder, and the history 
instance should be attached using `.history(history)` on the builder. Without 
properly loading and attaching the history, command history will not persist 
across sessions.
   ```suggestion
                   Path historyPath = new File(System.getProperty("user.home"), 
HISTORYFILE).toPath();
                   reader.setVariable(LineReader.HISTORY_FILE, historyPath);
                   if (history instanceof DefaultHistory) {
                       DefaultHistory defaultHistory = (DefaultHistory) history;
                       defaultHistory.attach(reader);
                       defaultHistory.load();
                   } else {
                       reader.setHistory(history);
                   }
   ```



##########
grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java:
##########
@@ -34,23 +33,21 @@
 import org.codehaus.groovy.runtime.StackTraceUtils;
 import org.codehaus.groovy.runtime.typehandling.NumberMath;
 
-import jline.Terminal;
-import jline.TerminalFactory;
-import jline.UnixTerminal;
-import jline.console.ConsoleReader;
-import jline.console.completer.Completer;
-import jline.console.history.FileHistory;
-import jline.console.history.History;
-import jline.internal.Log;
-import jline.internal.ShutdownHooks;
-import jline.internal.TerminalLineSettings;
+import org.jline.reader.Completer;
+import org.jline.reader.History;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.impl.history.DefaultHistory;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStyle;

Review Comment:
   Unused imports: `AttributedString` and `AttributedStyle` are imported but 
never used in the code. These should be removed.
   ```suggestion
   
   ```



##########
grails-bootstrap/src/main/groovy/org/grails/build/interactive/CandidateListCompletionHandler.java:
##########
@@ -18,76 +18,62 @@
  */
 package org.grails.build.interactive;
 
-import java.io.IOException;
 import java.util.List;
 
-import jline.console.ConsoleReader;
-import jline.console.CursorBuffer;
-import jline.console.completer.CompletionHandler;
+import org.jline.reader.Candidate;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
 
 /**
- * Fixes issues with the default CandidateListCompletionHandler such as 
clearing out the whole buffer when
- * a completion matches a list of candidates
+ * A Completer implementation that wraps candidate list completion behavior.
+ * In JLine 3, completion handling is integrated into the LineReader itself,
+ * so this class now acts as a utility completer that can be composed with 
others.
  *
  * @author Graeme Rocher
  * @since 2.0
  */
-public class CandidateListCompletionHandler implements CompletionHandler {
+public class CandidateListCompletionHandler implements Completer {
 
+    private final Completer delegate;
     private boolean eagerNewlines = true;
 
-    public void setAlwaysIncludeNewline(boolean eagerNewlines) {
-        this.eagerNewlines = eagerNewlines;
+    public CandidateListCompletionHandler() {
+        this.delegate = null;
     }
 
-    public boolean complete(ConsoleReader reader, 
@SuppressWarnings("rawtypes") List<CharSequence> candidates, int pos) throws 
IOException {
-        CursorBuffer buf = reader.getCursorBuffer();
-
-        // if there is only one completion, then fill in the buffer
-        if (candidates.size() == 1) {
-            String value = candidates.get(0).toString();
-
-            // fail if the only candidate is the same as the current buffer
-            if (value.equals(buf.toString())) {
-                return false;
-            }
-
-            
jline.console.completer.CandidateListCompletionHandler.setBuffer(reader, value, 
pos);
-
-            return true;
-        }
-
-        if (candidates.size() > 1) {
-            String value = getUnambiguousCompletions(candidates);
+    public CandidateListCompletionHandler(Completer delegate) {
+        this.delegate = delegate;
+    }
 
-            
jline.console.completer.CandidateListCompletionHandler.setBuffer(reader, value, 
pos);
-        }
+    public void setAlwaysIncludeNewline(boolean eagerNewlines) {
+        this.eagerNewlines = eagerNewlines;
+    }

Review Comment:
   The `eagerNewlines` field is set via `setAlwaysIncludeNewline` method but is 
never used in the implementation. In JLine 3, completion handling behavior 
(including newlines) is controlled by the LineReader's configuration, not by 
individual completers. This field and its setter method serve no purpose and 
could be removed, or the class could be redesigned if custom completion 
rendering is needed.



##########
grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java:
##########
@@ -674,7 +661,7 @@ private void logSimpleError(String msg) {
     }
 
     public boolean isAnsiEnabled() {
-        return Ansi.isEnabled() && (terminal != null && 
terminal.isAnsiSupported()) && ansiEnabled;
+        return Ansi.isEnabled() && (terminal != null && terminal.getType() != 
Terminal.TYPE_DUMB) && ansiEnabled;

Review Comment:
   The constant `Terminal.TYPE_DUMB` does not exist in JLine 3. In JLine 3, 
terminal types are identified differently. The correct way to check for a dumb 
terminal is to use `terminal.getType().equals("dumb")` or check if the terminal 
class is `DumbTerminal`. This will cause a compilation error.
   ```suggestion
           return Ansi.isEnabled() && (terminal != null && 
!"dumb".equals(terminal.getType())) && ansiEnabled;
   ```



##########
grails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/ArgumentCompletingCommand.groovy:
##########
@@ -54,15 +57,14 @@ abstract class ArgumentCompletingCommand implements 
Command, Completer {
                 if (lastOption) {
                     def lastArg = lastOption.key
                     if (arg.name.startsWith(lastArg)) {
-                        candidates.add("${argName.substring(lastArg.length())} 
".toString())
+                        candidates.add(new 
Candidate("${argName.substring(lastArg.length())} ".toString()))

Review Comment:
   Similar completion issue: when completing a partial flag name, the code 
creates a candidate with only the suffix (e.g., 
`"${argName.substring(lastArg.length())} "`). In JLine 3, candidates should 
contain the complete flag including the leading dash, not just the remaining 
characters. The candidate should be created with the full flag: `new 
Candidate("-$argName ")` or `new Candidate(flag + " ")`.
   ```suggestion
                           candidates.add(new Candidate("$flag ".toString()))
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to