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


##########
grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java:
##########
@@ -251,51 +241,64 @@ 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, History history) 
throws IOException {
+        LineReaderBuilder builder = LineReaderBuilder.builder()
+                .terminal(terminal)
+                .option(LineReader.Option.DISABLE_EVENT_EXPANSION, true);
+        if (history != null) {
+            builder.variable(LineReader.HISTORY_FILE, new 
File(System.getProperty("user.home"), HISTORYFILE).toPath());
+            builder.history(history);
         }
+        LineReader lineReader = builder.build();
+        if (history != null) {
+            history.attach(lineReader);
+            history.load();
+        }

Review Comment:
   The history setup appears to be redundant. The builder is configured with 
both `HISTORY_FILE` variable (line 249) and the history object (line 250), then 
after building the LineReader, the code manually attaches and loads the history 
(lines 254-255). 
   
   In JLine 3, when you provide a history object to the builder via 
`.history(history)`, you typically don't need to manually call 
`history.attach(lineReader)` and `history.load()` afterwards - the builder 
handles this. The manual attach/load could potentially cause the history to be 
loaded twice or cause other issues.
   
   Consider removing the manual attach/load calls (lines 254-255) and let the 
LineReaderBuilder handle the history setup, or verify that this double-setup is 
intentional and necessary for the specific use case.
   ```suggestion
   
   ```



##########
grails-shell-cli/src/main/groovy/org/grails/cli/GrailsCli.groovy:
##########
@@ -434,47 +432,66 @@ class GrailsCli {
                     // CTRL-D was pressed, exit interactive mode
                     exitInteractiveMode()
                 } else if (commandLine.trim()) {
-                    if (nonBlockingInput.isNonBlockingEnabled()) {
-                        handleCommandWithCancellationSupport(console, 
commandLine, commandExecutor, nonBlockingInput)
-                    } else {
-                        handleCommand(cliParser.parseString(commandLine))
-                    }
+                    handleCommandWithCancellationSupport(console, commandLine, 
commandExecutor)
                 }
             } catch (BuildCancelledException cancelledException) {
                 console.updateStatus('Build stopped.')
             } catch (UserInterruptException e) {
                 exitInteractiveMode()
+            } catch (EndOfFileException e) {
+                exitInteractiveMode()
             } catch (Throwable e) {
                 console.error("Caught exception ${e.message}", e)
             }
         }
     }
 
-    private Boolean handleCommandWithCancellationSupport(GrailsConsole 
console, String commandLine, ExecutorService commandExecutor, 
NonBlockingInputStream nonBlockingInput) {
+    private Boolean handleCommandWithCancellationSupport(GrailsConsole 
console, String commandLine, ExecutorService commandExecutor) {
         ExecutionContext executionContext = 
createExecutionContext(cliParser.parseString(commandLine))
         Future<?> commandFuture = commandExecutor.submit({ 
handleCommand(executionContext) } as Callable<Boolean>)
-        def terminal = console.reader.terminal
-        if (terminal instanceof UnixTerminal) {
-            ((UnixTerminal) terminal).disableInterruptCharacter()
-        }
+        NonBlockingReader reader = console?.terminal?.reader()
+        boolean maskedInterruptCharacter = false
+        int originalInterruptChar = -1
         try {
+            if (console?.terminal) {
+                def attributes = console.terminal.getAttributes()
+                originalInterruptChar = 
attributes.getControlChar(org.jline.terminal.Attributes.ControlChar.VINTR)
+                if (originalInterruptChar != 0) {
+                    
attributes.setControlChar(org.jline.terminal.Attributes.ControlChar.VINTR, 0)
+                    console.terminal.setAttributes(attributes)
+                    maskedInterruptCharacter = true
+                }
+            }
             while (!commandFuture.done) {
-                if (nonBlockingInput.nonBlockingEnabled) {
-                    int peeked = nonBlockingInput.peek(100L)
+                if (reader != null) {
+                    int peeked = reader.peek(100L)
                     if (peeked > 0) {
-                        // read peeked character from buffer
-                        nonBlockingInput.read(1L)
                         if (peeked == KEYPRESS_CTRL_C || peeked == 
KEYPRESS_ESC) {
+                            reader.read()
                             executionContext.console.log('  ')
                             executionContext.console.updateStatus('Stopping 
build. Please wait...')
                             executionContext.cancel()
+                        } else {

Review Comment:
   The CTRL+C handling logic consumes all keyboard input during command 
execution (line 475), not just CTRL+C and ESC keypresses. This means any keys 
the user types while a command is running will be silently discarded. 
   
   While this might be intentional to prevent input buffering during command 
execution, it could be confusing for users who expect their input to be queued. 
Consider either:
   1. Buffering non-control keypresses for after command completion
   2. Providing visual feedback that input is being ignored during execution
   3. Adding a comment explaining why all input is consumed
   
   This is a UX consideration rather than a bug, but worth documenting the 
intended behavior.
   ```suggestion
                           } else {
                               // Intentionally consume all other keypresses 
while a command is running.
                               // This avoids buffering input that would 
otherwise be processed only after
                               // the current command completes, which could be 
confusing in interactive use.
   ```



-- 
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