Skip to content

Commit 02aa28f

Browse files
committed
use readline module for history and completion
1 parent 1caf8a0 commit 02aa28f

File tree

4 files changed

+293
-5
lines changed

4 files changed

+293
-5
lines changed

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/ConsoleHandler.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
import java.io.IOException;
4444
import java.io.InputStream;
4545
import java.nio.charset.StandardCharsets;
46+
import java.util.List;
47+
import java.util.function.BiConsumer;
48+
import java.util.function.Consumer;
49+
import java.util.function.Function;
50+
import java.util.function.Supplier;
4651

4752
import org.graalvm.polyglot.Context;
4853

@@ -59,10 +64,19 @@ public abstract class ConsoleHandler {
5964

6065
public abstract void setPrompt(String prompt);
6166

67+
public void addCompleter(@SuppressWarnings("unused") Function<String, List<String>> completer) {
68+
// ignore by default
69+
}
70+
6271
public void setContext(@SuppressWarnings("unused") Context context) {
6372
// ignore by default
6473
}
6574

75+
@SuppressWarnings("unused")
76+
public void setHistory(Supplier<Integer> getSize, Consumer<String> addItem, Function<Integer, String> getItem, BiConsumer<Integer, String> setItem, Consumer<Integer> removeItem, Runnable clear) {
77+
// ignore by default
78+
}
79+
6680
public InputStream createInputStream() {
6781
return new InputStream() {
6882
byte[] buffer = null;

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.graalvm.polyglot.PolyglotException.StackFrame;
4949
import org.graalvm.polyglot.Source;
5050
import org.graalvm.polyglot.SourceSection;
51+
import org.graalvm.polyglot.Value;
5152

5253
import jline.console.UserInterruptException;
5354

@@ -517,9 +518,12 @@ public ConsoleHandler createConsoleHandler(InputStream inStream, OutputStream ou
517518
public int readEvalPrint(Context context, ConsoleHandler consoleHandler) {
518519
int lastStatus = 0;
519520
try {
521+
setupReadline(context, consoleHandler);
522+
520523
while (true) { // processing inputs
521524
boolean doEcho = doEcho(context);
522525
consoleHandler.setPrompt(doEcho ? getPrompt(context) : null);
526+
523527
try {
524528
String input = consoleHandler.readLine();
525529
if (input == null) {
@@ -588,6 +592,36 @@ public int readEvalPrint(Context context, ConsoleHandler consoleHandler) {
588592
}
589593
}
590594

595+
private void setupReadline(Context context, ConsoleHandler consoleHandler) {
596+
final Value readline = context.eval(Source.newBuilder(getLanguageId(), "import readline; readline", "setup-interactive").interactive(true).buildLiteral());
597+
final Value completer = readline.getMember("get_completer").execute();
598+
final Value addHistory = readline.getMember("add_history");
599+
final Value getHistoryItem = readline.getMember("get_history_item");
600+
final Value setHistoryItem = readline.getMember("replace_history_item");
601+
final Value deleteHistoryItem = readline.getMember("remove_history_item");
602+
final Value clearHistory = readline.getMember("clear_history");
603+
final Value getHistorySize = readline.getMember("get_current_history_length");
604+
consoleHandler.setHistory(
605+
() -> getHistorySize.execute().asInt(),
606+
(item) -> addHistory.execute(item),
607+
(pos) -> getHistoryItem.execute(pos).asString(),
608+
(pos, item) -> setHistoryItem.execute(pos, item),
609+
(pos) -> deleteHistoryItem.execute(pos),
610+
() -> clearHistory.execute());
611+
612+
if (completer.canExecute()) {
613+
consoleHandler.addCompleter((buffer) -> {
614+
List<String> candidates = new ArrayList<>();
615+
Value candidate = completer.execute(buffer, candidates.size());
616+
while (candidate.isString()) {
617+
candidates.add(candidate.asString());
618+
candidate = completer.execute(buffer, candidates.size());
619+
}
620+
return candidates;
621+
});
622+
}
623+
}
624+
591625
private static final class ExitException extends RuntimeException {
592626
private static final long serialVersionUID = 1L;
593627
private final int code;

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/JLineConsoleHandler.java

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,26 @@
4040
*/
4141
package com.oracle.graal.python.shell;
4242

43+
import java.io.IOException;
44+
import java.io.InputStream;
45+
import java.io.OutputStream;
46+
import java.util.Iterator;
47+
import java.util.List;
48+
import java.util.ListIterator;
49+
import java.util.function.BiConsumer;
50+
import java.util.function.Consumer;
51+
import java.util.function.Function;
52+
import java.util.function.Supplier;
53+
54+
import org.graalvm.polyglot.Context;
55+
4356
import jline.console.ConsoleReader;
4457
import jline.console.UserInterruptException;
4558
import jline.console.completer.CandidateListCompletionHandler;
59+
import jline.console.completer.Completer;
4660
import jline.console.completer.CompletionHandler;
61+
import jline.console.history.History;
4762
import jline.console.history.MemoryHistory;
48-
import org.graalvm.polyglot.Context;
49-
50-
import java.io.IOException;
51-
import java.io.InputStream;
52-
import java.io.OutputStream;
5363

5464
public class JLineConsoleHandler extends ConsoleHandler {
5565
private final ConsoleReader console;
@@ -64,12 +74,154 @@ public JLineConsoleHandler(InputStream inStream, OutputStream outStream, boolean
6474
console.setHistory(history);
6575
console.setHandleUserInterrupt(true);
6676
console.setExpandEvents(false);
77+
console.setCommentBegin("#");
6778
} catch (IOException ex) {
6879
// TODO throw proper exception type
6980
throw new RuntimeException("unexpected error opening console reader", ex);
7081
}
7182
}
7283

84+
@Override
85+
public void addCompleter(Function<String, List<String>> completer) {
86+
console.addCompleter(new Completer() {
87+
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
88+
if (buffer != null) {
89+
candidates.addAll(completer.apply(buffer));
90+
}
91+
return candidates.isEmpty() ? -1 : 0;
92+
}
93+
});
94+
95+
}
96+
97+
@Override
98+
public void setHistory(Supplier<Integer> getSize, Consumer<String> addItem, Function<Integer, String> getItem, BiConsumer<Integer, String> setItem, Consumer<Integer> removeItem, Runnable clear) {
99+
console.setHistory(new History() {
100+
private int pos = getSize.get();
101+
102+
public int size() {
103+
return getSize.get();
104+
}
105+
106+
public void set(int arg0, CharSequence arg1) {
107+
setItem.accept(arg0, arg1.toString());
108+
}
109+
110+
public void replace(CharSequence arg0) {
111+
if (pos < 0 || pos >= size()) {
112+
return;
113+
}
114+
setItem.accept(pos, arg0.toString());
115+
}
116+
117+
public CharSequence removeLast() {
118+
int t = size() - 1;
119+
String s = getItem.apply(t);
120+
removeItem.accept(t);
121+
return s;
122+
}
123+
124+
public CharSequence removeFirst() {
125+
int t = size() - 1;
126+
String s = getItem.apply(t);
127+
removeItem.accept(0);
128+
return s;
129+
}
130+
131+
public CharSequence remove(int arg0) {
132+
int t = size() - 1;
133+
String s = getItem.apply(t);
134+
removeItem.accept(arg0);
135+
return s;
136+
}
137+
138+
public boolean previous() {
139+
if (pos >= 0) {
140+
pos--;
141+
return true;
142+
} else {
143+
return false;
144+
}
145+
}
146+
147+
public boolean next() {
148+
if (pos < size()) {
149+
pos++;
150+
return true;
151+
} else {
152+
return false;
153+
}
154+
}
155+
156+
public boolean moveToLast() {
157+
pos = size();
158+
return true;
159+
}
160+
161+
public boolean moveToFirst() {
162+
pos = 0;
163+
return true;
164+
}
165+
166+
public void moveToEnd() {
167+
moveToLast();
168+
}
169+
170+
public boolean moveTo(int arg0) {
171+
pos = arg0;
172+
int size = size();
173+
if (pos < 0 || pos >= size) {
174+
pos = pos % size;
175+
return false;
176+
}
177+
return true;
178+
}
179+
180+
public Iterator<Entry> iterator() {
181+
// TODO Auto-generated method stub
182+
return null;
183+
}
184+
185+
public boolean isEmpty() {
186+
return size() == 0;
187+
}
188+
189+
public int index() {
190+
return pos;
191+
}
192+
193+
public CharSequence get(int arg0) {
194+
return getItem.apply(arg0);
195+
}
196+
197+
public ListIterator<Entry> entries(int arg0) {
198+
// TODO Auto-generated method stub
199+
return null;
200+
}
201+
202+
public ListIterator<Entry> entries() {
203+
// TODO Auto-generated method stub
204+
return null;
205+
}
206+
207+
public CharSequence current() {
208+
if (pos < 0 || pos >= size()) {
209+
return "";
210+
}
211+
return getItem.apply(pos);
212+
}
213+
214+
public void clear() {
215+
clear.run();
216+
}
217+
218+
public void add(CharSequence arg0) {
219+
addItem.accept(arg0.toString());
220+
pos = size();
221+
}
222+
});
223+
}
224+
73225
@Override
74226
public void setContext(Context context) {
75227
CompletionHandler completionHandler = console.getCompletionHandler();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ReadlineModuleBuiltins.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
6060
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
6161
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
62+
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
6263
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
6364
import com.oracle.graal.python.runtime.PythonCore;
6465
import com.oracle.graal.python.runtime.exception.PythonErrorType;
@@ -161,6 +162,79 @@ int setCompleter(PythonModule self,
161162
}
162163
}
163164

165+
@Builtin(name = "get_history_item", fixedNumOfPositionalArgs = 2, declaresExplicitSelf = true)
166+
@GenerateNodeFactory
167+
abstract static class SetHistoryLengthNode extends PythonBinaryBuiltinNode {
168+
@Specialization
169+
@TruffleBoundary
170+
String setCompleter(PythonModule self, int index,
171+
@Cached("create()") ReadAttributeFromObjectNode readNode) {
172+
LocalData data = (LocalData) readNode.execute(self, DATA);
173+
try {
174+
return data.history.get(index);
175+
} catch (IndexOutOfBoundsException e) {
176+
throw raise(PythonErrorType.IndexError, "index out of bounds");
177+
}
178+
}
179+
}
180+
181+
@Builtin(name = "replace_history_item", fixedNumOfPositionalArgs = 3, declaresExplicitSelf = true)
182+
@GenerateNodeFactory
183+
abstract static class ReplaceItemNode extends PythonTernaryBuiltinNode {
184+
@Specialization
185+
String setCompleter(PythonModule self, int index, PString string,
186+
@Cached("create()") ReadAttributeFromObjectNode readNode) {
187+
return setCompleter(self, index, string.getValue(), readNode);
188+
}
189+
190+
@Specialization
191+
@TruffleBoundary
192+
String setCompleter(PythonModule self, int index, String string,
193+
@Cached("create()") ReadAttributeFromObjectNode readNode) {
194+
LocalData data = (LocalData) readNode.execute(self, DATA);
195+
try {
196+
return data.history.set(index, string);
197+
} catch (IndexOutOfBoundsException e) {
198+
throw raise(PythonErrorType.IndexError, "index out of bounds");
199+
}
200+
}
201+
}
202+
203+
@Builtin(name = "remove_history_item", fixedNumOfPositionalArgs = 2, declaresExplicitSelf = true)
204+
@GenerateNodeFactory
205+
abstract static class DeleteItemNode extends PythonBinaryBuiltinNode {
206+
@Specialization
207+
@TruffleBoundary
208+
String setCompleter(PythonModule self, int index,
209+
@Cached("create()") ReadAttributeFromObjectNode readNode) {
210+
LocalData data = (LocalData) readNode.execute(self, DATA);
211+
try {
212+
return data.history.remove(index);
213+
} catch (IndexOutOfBoundsException e) {
214+
throw raise(PythonErrorType.IndexError, "index out of bounds");
215+
}
216+
}
217+
}
218+
219+
@Builtin(name = "add_history", fixedNumOfPositionalArgs = 2, declaresExplicitSelf = true)
220+
@GenerateNodeFactory
221+
abstract static class AddHistoryNode extends PythonBinaryBuiltinNode {
222+
@Specialization
223+
PNone addHistory(PythonModule self, PString item,
224+
@Cached("create()") ReadAttributeFromObjectNode readNode) {
225+
return addHistory(self, item.getValue(), readNode);
226+
}
227+
228+
@Specialization
229+
@TruffleBoundary
230+
PNone addHistory(PythonModule self, String item,
231+
@Cached("create()") ReadAttributeFromObjectNode readNode) {
232+
LocalData data = (LocalData) readNode.execute(self, DATA);
233+
data.history.add(item);
234+
return PNone.NONE;
235+
}
236+
}
237+
164238
@Builtin(name = "read_history_file", fixedNumOfPositionalArgs = 2, declaresExplicitSelf = true)
165239
@GenerateNodeFactory
166240
abstract static class ReadHistoryFileNode extends PythonBinaryBuiltinNode {
@@ -181,6 +255,7 @@ PNone setCompleter(PythonModule self, String path,
181255
while ((line = reader.readLine()) != null) {
182256
data.history.add(line);
183257
}
258+
reader.close();
184259
} catch (IOException e) {
185260
throw raise(PythonErrorType.IOError, e.getMessage());
186261
}
@@ -208,13 +283,26 @@ PNone setCompleter(PythonModule self, String path,
208283
writer.write(l);
209284
writer.newLine();
210285
}
286+
writer.close();
211287
} catch (IOException e) {
212288
throw raise(PythonErrorType.IOError, e.getMessage());
213289
}
214290
return PNone.NONE;
215291
}
216292
}
217293

294+
@Builtin(name = "clear_history", fixedNumOfPositionalArgs = 1, declaresExplicitSelf = true)
295+
@GenerateNodeFactory
296+
abstract static class ClearNode extends PythonUnaryBuiltinNode {
297+
@Specialization
298+
PNone setCompleter(PythonModule self,
299+
@Cached("create()") ReadAttributeFromObjectNode readNode) {
300+
LocalData data = (LocalData) readNode.execute(self, DATA);
301+
data.history.clear();
302+
return PNone.NONE;
303+
}
304+
}
305+
218306
@Builtin(name = "insert_text", fixedNumOfPositionalArgs = 1)
219307
@GenerateNodeFactory
220308
abstract static class InsertTextNode extends PythonUnaryBuiltinNode {

0 commit comments

Comments
 (0)