1818 */
1919package grails .build .logging ;
2020
21- import java .io .ByteArrayOutputStream ;
2221import java .io .File ;
23- import java .io .Flushable ;
2422import java .io .IOException ;
2523import java .io .InputStream ;
2624import java .io .PrintStream ;
2725import java .io .PrintWriter ;
2826import java .io .StringWriter ;
27+ import java .nio .file .Path ;
2928import java .util .Collection ;
3029import java .util .List ;
3130import java .util .Stack ;
3433import org .codehaus .groovy .runtime .StackTraceUtils ;
3534import org .codehaus .groovy .runtime .typehandling .NumberMath ;
3635
37- import jline .Terminal ;
38- import jline .TerminalFactory ;
39- import jline .UnixTerminal ;
40- import jline .console .ConsoleReader ;
41- import jline .console .completer .Completer ;
42- import jline .console .history .FileHistory ;
43- import jline .console .history .History ;
44- import jline .internal .Log ;
45- import jline .internal .ShutdownHooks ;
46- import jline .internal .TerminalLineSettings ;
36+ import org .jline .reader .Completer ;
37+ import org .jline .reader .History ;
38+ import org .jline .reader .LineReader ;
39+ import org .jline .reader .LineReaderBuilder ;
40+ import org .jline .reader .impl .history .DefaultHistory ;
41+ import org .jline .terminal .Terminal ;
42+ import org .jline .terminal .TerminalBuilder ;
43+ import org .jline .utils .AttributedString ;
44+ import org .jline .utils .AttributedStyle ;
4745import org .apache .tools .ant .BuildException ;
4846import org .fusesource .jansi .Ansi ;
4947import org .fusesource .jansi .Ansi .Color ;
5048import org .fusesource .jansi .AnsiConsole ;
5149
5250import grails .util .Environment ;
53- import org .grails .build .interactive .CandidateListCompletionHandler ;
5451import org .grails .build .logging .GrailsConsoleErrorPrintStream ;
5552import org .grails .build .logging .GrailsConsolePrintStream ;
5653
@@ -114,7 +111,7 @@ public class GrailsConsole implements ConsoleLogger {
114111 /**
115112 * The reader to read info from the console
116113 */
117- ConsoleReader reader ;
114+ LineReader reader ;
118115
119116 Terminal terminal ;
120117
@@ -179,8 +176,8 @@ protected GrailsConsole() throws IOException {
179176 * @throws IOException
180177 */
181178 public void reinitialize (InputStream systemIn , PrintStream systemOut , PrintStream systemErr ) throws IOException {
182- if (reader != null ) {
183- reader . shutdown ();
179+ if (terminal != null ) {
180+ terminal . close ();
184181 }
185182 initialize (systemIn , systemOut , systemErr );
186183 }
@@ -190,19 +187,12 @@ protected void initialize(InputStream systemIn, PrintStream systemOut, PrintStre
190187
191188 redirectSystemOutAndErr (true );
192189
193- System .setProperty (ShutdownHooks .JLINE_SHUTDOWNHOOK , "false" );
194-
195190 if (isInteractiveEnabled ()) {
196- reader = createConsoleReader (systemIn );
197- reader .setBellEnabled (false );
198- reader .setCompletionHandler (new CandidateListCompletionHandler ());
199- if (isActivateTerminal ()) {
200- terminal = createTerminal ();
201- }
202-
191+ terminal = createTerminal ();
192+ reader = createLineReader (terminal );
203193 history = prepareHistory ();
204194 if (history != null ) {
205- reader .setHistory ( history );
195+ reader .setVariable ( LineReader . HISTORY_FILE , new File ( System . getProperty ( "user.home" ), HISTORYFILE ). toPath () );
206196 }
207197 } else if (isActivateTerminal ()) {
208198 terminal = createTerminal ();
@@ -251,51 +241,50 @@ private boolean readPropOrTrue(String prop) {
251241 return property == null ? true : Boolean .valueOf (property );
252242 }
253243
254- protected ConsoleReader createConsoleReader (InputStream systemIn ) throws IOException {
255- // need to swap out the output to avoid logging during init
256- final PrintStream nullOutput = new PrintStream (new ByteArrayOutputStream ());
257- final PrintStream originalOut = Log .getOutput ();
258- try {
259- Log .setOutput (nullOutput );
260- ConsoleReader consoleReader = new ConsoleReader (systemIn , out );
261- consoleReader .setExpandEvents (false );
262- return consoleReader ;
263- } finally {
264- Log .setOutput (originalOut );
265- }
244+ protected LineReader createLineReader (Terminal terminal ) throws IOException {
245+ LineReader lineReader = LineReaderBuilder .builder ()
246+ .terminal (terminal )
247+ .option (LineReader .Option .DISABLE_EVENT_EXPANSION , true )
248+ .build ();
249+ return lineReader ;
266250 }
267251
268252 /**
269- * Creates the instance of Terminal used directly in GrailsConsole. Note that there is also
270- * another terminal instance created implicitly inside of ConsoleReader. That instance
271- * is controlled by the jline.terminal system property.
253+ * Creates the instance of Terminal used directly in GrailsConsole.
272254 */
273- protected Terminal createTerminal () {
274- terminal = TerminalFactory .create ();
275- if (isWindows ()) {
276- terminal .setEchoEnabled (true );
277- }
255+ protected Terminal createTerminal () throws IOException {
256+ Terminal terminal = TerminalBuilder .builder ()
257+ .system (true )
258+ .build ();
278259 return terminal ;
279260 }
280261
281262 public void resetCompleters () {
282- final ConsoleReader reader = getReader ();
283- if (reader != null ) {
284- Collection <Completer > completers = reader .getCompleters ();
285- for (Completer completer : completers ) {
286- reader .removeCompleter (completer );
287- }
263+ // In JLine 3, completers are set at LineReader creation time or via setCompleter
264+ // We'll handle this differently - completers are managed via the LineReader
265+ }
288266
289- // for some unknown reason / bug in JLine you have to iterate over twice to clear the completers (WTF)
290- completers = reader .getCompleters ();
291- for (Completer completer : completers ) {
292- reader .removeCompleter (completer );
267+ public void addCompleter (Completer completer ) {
268+ // In JLine 3, we need to recreate the LineReader with the new completer
269+ // or use an AggregateCompleter. For now, this is a simplified implementation.
270+ if (terminal != null ) {
271+ try {
272+ reader = LineReaderBuilder .builder ()
273+ .terminal (terminal )
274+ .completer (completer )
275+ .option (LineReader .Option .DISABLE_EVENT_EXPANSION , true )
276+ .build ();
277+ if (history != null ) {
278+ reader .setVariable (LineReader .HISTORY_FILE , new File (System .getProperty ("user.home" ), HISTORYFILE ).toPath ());
279+ }
280+ } catch (Exception e ) {
281+ // ignore
293282 }
294283 }
295284 }
296285
297286 /**
298- * Prepares a history file to be used by the ConsoleReader . This file
287+ * Prepares a history file to be used by the LineReader . This file
299288 * will live in the home directory of the user.
300289 */
301290 protected History prepareHistory () throws IOException {
@@ -307,7 +296,7 @@ protected History prepareHistory() throws IOException {
307296 // can't create the file, so no history for you
308297 }
309298 }
310- return file .canWrite () ? new FileHistory ( file ) : null ;
299+ return file .canWrite () ? new DefaultHistory ( ) : null ;
311300 }
312301
313302 public boolean isWindows () {
@@ -334,8 +323,12 @@ public static synchronized void removeInstance() {
334323 if (instance != null ) {
335324 instance .removeShutdownHook ();
336325 instance .restoreOriginalSystemOutAndErr ();
337- if (instance .getReader () != null ) {
338- instance .getReader ().shutdown ();
326+ if (instance .terminal != null ) {
327+ try {
328+ instance .terminal .close ();
329+ } catch (IOException e ) {
330+ // ignore
331+ }
339332 }
340333 instance = null ;
341334 }
@@ -348,24 +341,18 @@ public void beforeShutdown() {
348341
349342 protected void restoreTerminal () {
350343 try {
351- terminal .restore ();
344+ if (terminal != null ) {
345+ terminal .close ();
346+ }
352347 } catch (Exception e ) {
353348 // ignore
354349 }
355- if (terminal instanceof UnixTerminal ) {
356- // workaround for GRAILS-11494
357- try {
358- new TerminalLineSettings ().set ("sane" );
359- } catch (Exception e ) {
360- // ignore
361- }
362- }
363350 }
364351
365352 protected void persistHistory () {
366- if (history instanceof Flushable ) {
353+ if (history != null && reader != null ) {
367354 try {
368- (( Flushable ) history ). flush ();
355+ history . save ();
369356 } catch (Throwable e ) {
370357 // ignore exception
371358 }
@@ -442,7 +429,7 @@ public boolean isStacktrace() {
442429 */
443430 public InputStream getInput () {
444431 assertAllowInput ();
445- return reader . getInput () ;
432+ return terminal != null ? terminal . input () : System . in ;
446433 }
447434
448435 private void assertAllowInput () {
@@ -471,7 +458,7 @@ public void setLastMessage(String lastMessage) {
471458 this .lastMessage = lastMessage ;
472459 }
473460
474- public ConsoleReader getReader () {
461+ public LineReader getReader () {
475462 return reader ;
476463 }
477464
@@ -674,7 +661,7 @@ private void logSimpleError(String msg) {
674661 }
675662
676663 public boolean isAnsiEnabled () {
677- return Ansi .isEnabled () && (terminal != null && terminal .isAnsiSupported () ) && ansiEnabled ;
664+ return Ansi .isEnabled () && (terminal != null && terminal .getType () != Terminal . TYPE_DUMB ) && ansiEnabled ;
678665 }
679666
680667 /**
@@ -875,10 +862,15 @@ private String readLine(String prompt, boolean secure) {
875862 assertAllowInput (prompt );
876863 userInputActive = true ;
877864 try {
878- Character inputMask = secure ? SECURE_MASK_CHAR : defaultInputMask ;
879- return reader .readLine (prompt , inputMask );
880- } catch (IOException e ) {
881- throw new RuntimeException ("Error reading input: " + e .getMessage ());
865+ if (secure ) {
866+ return reader .readLine (prompt , SECURE_MASK_CHAR );
867+ } else {
868+ return reader .readLine (prompt , defaultInputMask , (String ) null );
869+ }
870+ } catch (org .jline .reader .UserInterruptException e ) {
871+ return null ;
872+ } catch (org .jline .reader .EndOfFileException e ) {
873+ return null ;
882874 } finally {
883875 userInputActive = false ;
884876 }
@@ -1041,4 +1033,12 @@ public Character getDefaultInputMask() {
10411033 public void setDefaultInputMask (Character defaultInputMask ) {
10421034 this .defaultInputMask = defaultInputMask ;
10431035 }
1036+
1037+ /**
1038+ * Gets the history for the LineReader
1039+ * @return the history
1040+ */
1041+ public History getHistory () {
1042+ return history ;
1043+ }
10441044}
0 commit comments