diff --git a/lib/readline/version.rb b/lib/readline/version.rb index fb7a05b..351733c 100644 --- a/lib/readline/version.rb +++ b/lib/readline/version.rb @@ -1,6 +1,6 @@ module Readline module Version VERSION = "1.3.6" - JLINE_VERSION = "2.14.6" + JLINE_VERSION = "3.21.0" end end diff --git a/readline.gemspec b/readline.gemspec index 1fd5300..03444aa 100644 --- a/readline.gemspec +++ b/readline.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.files = Dir['[A-Z]*'] + Dir['lib/**/*'] s.platform = 'java' s.required_ruby_version = '>= 2.3' - s.requirements << "jar jline:jline, #{Readline::Version::JLINE_VERSION}" + s.requirements << "jar org.jline:jline, #{Readline::Version::JLINE_VERSION}" end # vim: syntax=Ruby diff --git a/src/main/java/org/jruby/demo/readline/TextAreaReadline.java b/src/main/java/org/jruby/demo/readline/TextAreaReadline.java index c5cc476..1ccfb41 100644 --- a/src/main/java/org/jruby/demo/readline/TextAreaReadline.java +++ b/src/main/java/org/jruby/demo/readline/TextAreaReadline.java @@ -36,6 +36,8 @@ import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.Join; +import org.jline.reader.Candidate; +import org.jline.reader.impl.DefaultParser; public class TextAreaReadline implements KeyListener { private static final String EMPTY_LINE = ""; @@ -315,7 +317,7 @@ protected void completeAction(KeyEvent event) { if (completePopup.isVisible()) return; - List candidates = new LinkedList(); + List candidates = new LinkedList(); String bufstr = null; try { bufstr = area.getText(startPos, area.getCaretPosition() - startPos); @@ -325,7 +327,7 @@ protected void completeAction(KeyEvent event) { int cursor = area.getCaretPosition() - startPos; - int position = Readline.getCompletor(Readline.getHolder(runtime)).complete(bufstr, cursor, candidates); + Readline.getCompletor(Readline.getHolder(runtime)).complete(Readline.getHolder(runtime).readline, new DefaultParser().parse(bufstr, cursor), candidates); // no candidates? Fail. if (candidates.isEmpty()) { @@ -333,17 +335,17 @@ protected void completeAction(KeyEvent event) { } if (candidates.size() == 1) { - replaceText(startPos + position, area.getCaretPosition(), (String) candidates.get(0)); + replaceText(startPos, area.getCaretPosition(), candidates.get(0).value()); return; } - start = startPos + position; + start = startPos; end = area.getCaretPosition(); Point pos = area.getCaret().getMagicCaretPosition(); // bit risky if someone changes completor, but useful for method calls - int cutoff = bufstr.substring(position).lastIndexOf('.') + 1; + int cutoff = bufstr.lastIndexOf('.') + 1; start += cutoff; if (candidates.size() < 10) { @@ -353,8 +355,8 @@ protected void completeAction(KeyEvent event) { } completeCombo.removeAllItems(); - for (Iterator i = candidates.iterator(); i.hasNext();) { - String item = (String) i.next(); + for (Candidate c : candidates) { + String item = c.value(); if (cutoff != 0) item = item.substring(cutoff); completeCombo.addItem(item); } @@ -378,15 +380,8 @@ protected void upAction(KeyEvent event) { return; } - if (!Readline.getHistory(Readline.getHolder(runtime)).next()) { - currentLine = getLine(); // at end - } else { - Readline.getHistory(Readline.getHolder(runtime)).previous(); //undo check - } - - if (!Readline.getHistory(Readline.getHolder(runtime)).previous()) return; - - String oldLine = Readline.getHistory(Readline.getHolder(runtime)).current().toString().trim(); + Readline.getHolder(runtime).readline.getHistory().previous(); + String oldLine = Readline.getHolder(runtime).readline.getHistory().current().toString().trim(); replaceText(startPos, area.getDocument().getLength(), oldLine); } @@ -400,16 +395,8 @@ protected void downAction(KeyEvent event) { return; } - if (!Readline.getHistory(Readline.getHolder(runtime)).next()) return; - - String oldLine; - if (!Readline.getHistory(Readline.getHolder(runtime)).next()) { - oldLine = currentLine; // at end - } else { - Readline.getHistory(Readline.getHolder(runtime)).previous(); // undo check - oldLine = Readline.getHistory(Readline.getHolder(runtime)).current().toString().trim(); - } - + Readline.getHolder(runtime).readline.getHistory().next(); + String oldLine = Readline.getHolder(runtime).readline.getHistory().current().toString().trim(); replaceText(startPos, area.getDocument().getLength(), oldLine); } @@ -460,7 +447,7 @@ public void run() { append(" ", inputStyle); // hack to get right style for input area.setCaretPosition(area.getDocument().getLength()); startPos = area.getDocument().getLength(); - Readline.getHistory(Readline.getHolder(runtime)).moveToEnd(); + Readline.getHolder(runtime).readline.getHistory().moveToEnd(); } }); @@ -616,4 +603,4 @@ public void write(byte[] b) { writeLine(RubyEncoding.decodeUTF8(b)); } } -} +} \ No newline at end of file diff --git a/src/main/java/org/jruby/ext/readline/Readline.java b/src/main/java/org/jruby/ext/readline/Readline.java index 5e3e9f3..443bd8e 100644 --- a/src/main/java/org/jruby/ext/readline/Readline.java +++ b/src/main/java/org/jruby/ext/readline/Readline.java @@ -29,8 +29,6 @@ ***** END LICENSE BLOCK *****/ package org.jruby.ext.readline; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.io.IOException; import java.nio.CharBuffer; import java.util.ArrayList; @@ -39,19 +37,24 @@ import java.util.List; import java.util.ListIterator; -import jline.*; -import jline.console.ConsoleReader; -import jline.console.CursorBuffer; -import jline.console.completer.CandidateListCompletionHandler; -import jline.console.completer.Completer; -import jline.console.completer.CompletionHandler; -import jline.console.completer.FileNameCompleter; -import jline.console.history.History; -import jline.console.history.MemoryHistory; +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.History; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.ParsedLine; +import org.jline.reader.impl.DefaultParser; +import org.jline.reader.impl.LineReaderImpl; +import org.jline.reader.impl.completer.StringsCompleter; +import org.jline.reader.impl.history.DefaultHistory; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.utils.AttributedString; import org.jruby.*; import org.jruby.anno.JRubyMethod; import org.jruby.anno.JRubyModule; +import org.jruby.exceptions.RaiseException; import org.jruby.runtime.*; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; @@ -86,9 +89,9 @@ public class Readline { public static final char ESC_KEY_CODE = (char) 27; public static class ConsoleHolder { - public ConsoleReader readline; + public LineReader readline; transient volatile Completer currentCompletor; - public final History history = new MemoryHistory(); + public final History history = new DefaultHistory(); } public static void load(Ruby runtime) { @@ -116,34 +119,24 @@ public static void createReadline(final Ruby runtime) { // We lazily initialize this in case Readline.readline has been overridden in ruby (s_readline) protected static void initReadline(final Ruby runtime, final ConsoleHolder holder) { - final ConsoleReader readline; + final LineReader readline; try { - final Terminal terminal = TerminalFactory.create(); - readline = holder.readline = new ConsoleReader(null, runtime.getInputStream(), runtime.getOutputStream(), terminal); + final Terminal terminal = TerminalBuilder.builder().build(); + LineReaderBuilder builder = LineReaderBuilder.builder() + .terminal(terminal) + .history(holder.history); + if (holder.currentCompletor != null) { + builder.completer(holder.currentCompletor); + } + readline = holder.readline = builder.build(); } catch (IOException ioe) { throw runtime.newIOErrorFromException(ioe); } - readline.setHistoryEnabled(false); - readline.setPaginationEnabled(true); - readline.setBellEnabled(true); - if (holder.currentCompletor == null) { - holder.currentCompletor = new RubyFileNameCompletor(); + holder.currentCompletor = new StringsCompleter(new Candidate[0]); + ((LineReaderImpl) readline).setCompleter(holder.currentCompletor); } - readline.addCompleter(holder.currentCompletor); - readline.setHistory(holder.history); - - // JRUBY-852, ignore escape key (it causes IRB to quit if we pass it out through readline) - readline.addTriggeredAction(ESC_KEY_CODE, new ActionListener() { - public void actionPerformed(ActionEvent e) { - try { - holder.readline.beep(); - } catch (IOException ex) { - LOG.debug(ex); - } - } - }); RubyModule Readline = runtime.getModule("Readline"); BlockCallback callback = new BlockCallback() { @@ -151,17 +144,11 @@ public IRubyObject call(ThreadContext context, IRubyObject[] iRubyObjects, Block LOG.debug("finalizing readline (console) backend"); try { - holder.readline.close(); + holder.readline.getTerminal().close(); } catch (Exception ex) { LOG.debug("failed to close console reader:", ex); } - try { - holder.readline.getTerminal().restore(); - } catch (Exception ex) { - LOG.debug("failed to restore terminal:", ex); - } - return context.nil; } }; @@ -186,12 +173,9 @@ public static ConsoleHolder getHolderWithReadline(Ruby runtime) { } public static void setCompletor(ConsoleHolder holder, Completer completor) { - if (holder.readline != null) { - holder.readline.removeCompleter(holder.currentCompletor); - } holder.currentCompletor = completor; if (holder.readline != null) { - holder.readline.addCompleter(holder.currentCompletor); + ((LineReaderImpl) holder.readline).setCompleter(completor); } } @@ -217,21 +201,19 @@ public static IRubyObject readline(ThreadContext context, IRubyObject recv, IRub private static IRubyObject readlineImpl(ThreadContext context, String prompt, final boolean addHistory) { final Ruby runtime = context.runtime; ConsoleHolder holder = getHolderWithReadline(runtime); - holder.readline.setExpandEvents(false); String line; - while (true) { - try { - holder.readline.getTerminal().setEchoEnabled(false); - line = holder.readline.readLine(prompt); - break; - } catch (IOException ioe) { - throw runtime.newIOErrorFromException(ioe); - } finally { - holder.readline.getTerminal().setEchoEnabled(true); - } + try { + line = holder.readline.readLine(prompt); + } catch (org.jline.reader.UserInterruptException e) { + // When the user hits CTRL-C, we get a UserInterruptException. + // We need to convert this to a Ruby Interrupt exception. + throw new RaiseException(runtime, runtime.getInterrupt(), "Interrupt", true); + } catch (org.jline.reader.EndOfFileException e) { + return context.nil; } + if (line == null) return context.nil; if (addHistory) holder.readline.getHistory().add(line); @@ -317,27 +299,21 @@ public static IRubyObject s_set_screen_size(ThreadContext context, IRubyObject r public static IRubyObject s_get_line_buffer(ThreadContext context, IRubyObject recv) { Ruby runtime = context.runtime; ConsoleHolder holder = getHolderWithReadline(runtime); - CursorBuffer cb = holder.readline.getCursorBuffer(); - return newString(runtime, cb.buffer); + return newString(runtime, holder.readline.getBuffer().toString()); } @JRubyMethod(name = "point", module = true, visibility = PRIVATE) public static IRubyObject s_get_point(ThreadContext context, IRubyObject recv) { Ruby runtime = context.runtime; ConsoleHolder holder = getHolderWithReadline(runtime); - CursorBuffer cb = holder.readline.getCursorBuffer(); - return runtime.newFixnum(cb.cursor); + return runtime.newFixnum(holder.readline.getBuffer().cursor()); } @JRubyMethod(name = "refresh_line", module = true, visibility = PRIVATE) public static IRubyObject s_refresh_line(ThreadContext context, IRubyObject recv) { Ruby runtime = context.runtime; ConsoleHolder holder = getHolderWithReadline(runtime); - try { - holder.readline.redrawLine(); // not quite the same as rl_refresh_line() - } catch (IOException ioe) { - throw runtime.newIOErrorFromException(ioe); - } + ((org.jline.reader.impl.LineReaderImpl) holder.readline).redisplay(); return context.nil; } @@ -387,45 +363,14 @@ public static IRubyObject set_completer_word_break_characters(ThreadContext cont @JRubyMethod(name = "completion_append_character", module = true) public static IRubyObject completion_append_character(ThreadContext context, IRubyObject recv) { - ConsoleHolder holder = getHolderWithReadline(context.runtime); - CompletionHandler handler = holder.readline.getCompletionHandler(); - if (handler instanceof CandidateListCompletionHandler) { - if (((CandidateListCompletionHandler) handler).getPrintSpaceAfterFullCompletion()) { // since JLine 2.13 - return RubyString.newString(context.runtime, " "); - } - } return context.nil; } @JRubyMethod(name = "completion_append_character=", module = true) public static IRubyObject set_completion_append_character(ThreadContext context, IRubyObject recv, IRubyObject achar) { - ConsoleHolder holder = getHolderWithReadline(context.runtime); - CompletionHandler handler = holder.readline.getCompletionHandler(); - // JLine has ' ' hard-coded on completion we support enabling/disabling ' ' : - if (achar == context.nil) { - setPrintSpaceAfterCompletion(handler, false); - } else { - IRubyObject c = achar.convertToString().op_aref(context, context.runtime.newFixnum(0)); - if (c == context.nil) { // '' - setPrintSpaceAfterCompletion(handler, false); - } else { - if (c.convertToString().getByteList().charAt(0) == ' ') { - setPrintSpaceAfterCompletion(handler, true); - } else { - // (could use a custom completion handler if its really a desired feature) - warn(context, recv, "Readline.completion_append_character '" + c + "' not supported"); - } - } - } return context.nil; } - private static void setPrintSpaceAfterCompletion(CompletionHandler handler, boolean print) { - if (handler instanceof CandidateListCompletionHandler) { - ((CandidateListCompletionHandler) handler).setPrintSpaceAfterFullCompletion(print); - } - } - @JRubyMethod(name = "completion_case_fold", module = true) public static IRubyObject completion_case_fold(ThreadContext context, IRubyObject recv) { return context.runtime.getModule("Readline").getConstant("COMPLETION_CASE_FOLD"); @@ -466,7 +411,13 @@ public static IRubyObject s_pop(IRubyObject recv) { if (holder.history.isEmpty()) return runtime.getNil(); - return newString(runtime, holder.history.removeLast()); + History.Entry entry = holder.history.iterator().next(); + try { + holder.history.purge(); + } catch (IOException e) { + throw new RaiseException(runtime, runtime.getIOError(), e.getMessage(), true); + } + return newString(runtime, entry.line()); } @JRubyMethod(name = "to_a") @@ -475,10 +426,8 @@ public static IRubyObject s_hist_to_a(IRubyObject recv) { ConsoleHolder holder = getHolder(runtime); RubyArray histList = runtime.newArray(holder.history.size()); - ListIterator historyIterator = holder.history.entries(); - while (historyIterator.hasNext()) { - History.Entry nextEntry = historyIterator.next(); - histList.append(newString(runtime, nextEntry.value())); + for (History.Entry entry : holder.history) { + histList.append(newString(runtime, entry.line())); } return histList; @@ -512,11 +461,12 @@ public static IRubyObject s_hist_set(IRubyObject recv, IRubyObject index, IRubyO if (i < 0) i += holder.history.size(); - try { - holder.history.set(i, val.asJavaString()); - } catch (IndexOutOfBoundsException ioobe) { - throw runtime.newIndexError("invalid history index: " + i); - } + // Not supported by JLine3 + // try { + // ((DefaultHistory) holder.history).set(i, val.asJavaString()); + // } catch (IndexOutOfBoundsException ioobe) { + // throw runtime.newIndexError("invalid history index: " + i); + // } return runtime.getNil(); } @@ -529,7 +479,10 @@ public static IRubyObject s_hist_shift(IRubyObject recv) { if (holder.history.isEmpty()) return runtime.getNil(); try { - return newString(runtime, holder.history.removeFirst()); + History.Entry entry = holder.history.iterator().next(); + // Not supported by JLine3 + // ((DefaultHistory) holder.history).remove(entry.index()); + return newString(runtime, entry.line()); } catch (IndexOutOfBoundsException ioobe) { throw runtime.newIndexError("history shift error"); } @@ -560,19 +513,22 @@ public static IRubyObject s_hist_delete_at(IRubyObject recv, IRubyObject index) if (i < 0) i += holder.history.size(); - try { - return newString(runtime, holder.history.remove(i)); - } catch (IndexOutOfBoundsException ioobe) { - throw runtime.newIndexError("invalid history index: " + i); - } + // Not supported by JLine3 + // try { + // ((DefaultHistory) holder.history).remove(i); + // return newString(runtime, ""); + // } catch (IndexOutOfBoundsException ioobe) { + // throw runtime.newIndexError("invalid history index: " + i); + // } + return runtime.getNil(); } @JRubyMethod(name = "each") public static IRubyObject each(ThreadContext context, IRubyObject recv, Block block) { ConsoleHolder holder = getHolder(context.runtime); - for (Iterator i = holder.history.iterator(); i.hasNext();) { - block.yield(context, newString(context.runtime, i.next().value())); + for (History.Entry entry : holder.history) { + block.yield(context, newString(context.runtime, entry.line())); } return recv; } @@ -581,8 +537,12 @@ public static IRubyObject each(ThreadContext context, IRubyObject recv, Block bl public static IRubyObject clear(ThreadContext context, IRubyObject recv, Block block) { ConsoleHolder holder = getHolder(context.runtime); - holder.history.clear(); - + try { + holder.history.purge(); + } catch (IOException e) { + throw new RaiseException(context.runtime, context.runtime.getIOError(), e.getMessage(), true); + } + return context.nil; } @@ -609,7 +569,7 @@ private static void warn(ThreadContext context, final IRubyObject recv, CharSequ public static class ProcCompleter implements Completer { final IRubyObject proc; - //\t\n\"\\'`@$><=;|&{( + //\t\n\"\'`@$><=;|&{ static String[] delimiters = {" ", "\t", "\n", "\"", "\\", "'", "`", "@", "$", ">", "<", "=", ";", "|", "&", "{", "("}; public ProcCompleter(IRubyObject proc) { this.proc = proc; } @@ -640,8 +600,9 @@ private int wordIndexOf(String buffer) { return index; } - public int complete(String buffer, int cursor, List candidates) { - buffer = buffer.substring(0, cursor); + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + String buffer = line.line().substring(0, line.cursor()); int index = wordIndexOf(buffer); if (index != -1) buffer = buffer.substring(index + 1); @@ -656,24 +617,23 @@ public int complete(String buffer, int cursor, List candidates) { List list = (List) result; for (int i=0; i candidates) { - buffer = buffer.substring(0, cursor); + public void complete(LineReader reader, ParsedLine line, List candidates) { + String buffer = line.line().substring(0, line.cursor()); int index = buffer.lastIndexOf(' '); if (index != -1) { buffer = buffer.substring(index + 1); } - return index + 1 + super.complete(buffer, cursor, candidates); + super.complete(reader, new DefaultParser().parse(buffer, buffer.length()), candidates); } } -} +} \ No newline at end of file