Skip to content

Commit 565ec43

Browse files
committed
Make pdb completion work
1 parent a085ae5 commit 565ec43

File tree

3 files changed

+115
-21
lines changed

3 files changed

+115
-21
lines changed

graalpython/com.oracle.graal.python.shell/src/META-INF/native-image/org.graalvm.python.shell/python-launcher/reflect-config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@
1313
"parameterTypes": [
1414
"java.lang.String"
1515
]
16+
},
17+
{
18+
"name": "getCompletionState",
19+
"parameterTypes": []
1620
}
1721
]
22+
},
23+
{
24+
"name": "com.oracle.graal.python.shell.JLineConsoleHandler$CompletionState",
25+
"allPublicFields": true
1826
}
1927
]

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

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public class JLineConsoleHandler extends ConsoleHandler {
7575
private final OutputStream outputStream;
7676
private LineReader reader;
7777
private final LinkedList<String> lineBuffer = new LinkedList<>();
78+
private CompletionState completionState;
7879

7980
JLineConsoleHandler(InputStream inputStream, OutputStream outputStream) {
8081
this.inputStream = inputStream;
@@ -113,33 +114,32 @@ public void initializeReadline(Value readlineModule) {
113114
if (word != null) {
114115
Value completer = getCompleter.execute();
115116
if (completer.canExecute()) {
116-
int i = 0;
117-
while (true) {
118-
Value completerResult = completer.execute(word, i++);
119-
if (completerResult.isString()) {
120-
String completion = completerResult.asString();
121-
candidates.add(new Candidate(completion, completion, null, null, null, null, false));
122-
} else {
123-
break;
117+
try {
118+
completionState = new CompletionState();
119+
completionState.line = pl.line();
120+
completionState.beginIdx = pl.cursor() - pl.word().length();
121+
completionState.endIdx = pl.cursor();
122+
int i = 0;
123+
while (true) {
124+
Value completerResult = completer.execute(word, i++);
125+
if (completerResult.isString()) {
126+
String completion = completerResult.asString();
127+
candidates.add(new Candidate(completion, completion, null, null, null, null, false));
128+
} else {
129+
break;
130+
}
124131
}
132+
} finally {
133+
completionState = null;
125134
}
126135
}
127136
}
128137
});
129138

130-
builder.parser(new DefaultParser() {
131-
@Override
132-
public boolean isDelimiterChar(CharSequence buffer, int pos) {
133-
// Never count a last character of a char sequence as delimiter. The REPL completer
134-
// implemented by `rlcompleter.py` adds a trailing whitespace to keywords,
135-
// e.g. 'raise '. The default DefaultParser implementation always escaped this
136-
// whitespace leading to wrong completions like 'raise\ '.
137-
if (pos == buffer.length() - 1) {
138-
return false;
139-
}
140-
return Character.isWhitespace(buffer.charAt(pos));
141-
}
142-
});
139+
DefaultParser parser = new DefaultParser();
140+
// Disable escaping
141+
parser.escapeChars(new char[0]);
142+
builder.parser(parser);
143143

144144
reader = builder.build();
145145
reader.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true);
@@ -166,6 +166,13 @@ public boolean isDelimiterChar(CharSequence buffer, int pos) {
166166
binding.bind(new Macro(KeyMap.translate("/")), KeyMap.translate("^[OQ"));
167167
}
168168

169+
public static class CompletionState {
170+
public String line;
171+
public int beginIdx;
172+
public int endIdx;
173+
}
174+
175+
// Used via interop
169176
@Override
170177
public String readLine(String prompt) {
171178
if (lineBuffer.isEmpty()) {
@@ -183,6 +190,12 @@ public String readLine(String prompt) {
183190
return lineBuffer.poll();
184191
}
185192

193+
// Used via interop
194+
@SuppressWarnings("unused")
195+
public CompletionState getCompletionState() {
196+
return completionState;
197+
}
198+
186199
private static class HistoryImpl implements History {
187200
private final BooleanSupplier shouldRecord;
188201
private final IntSupplier getSize;

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import static com.oracle.graal.python.nodes.BuiltinNames.J_READLINE;
4444
import static com.oracle.graal.python.nodes.BuiltinNames.T_READLINE;
4545
import static com.oracle.graal.python.nodes.BuiltinNames.T_SYS;
46+
import static com.oracle.graal.python.nodes.StringLiterals.T_EMPTY_STRING;
4647
import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached;
4748
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;
4849

@@ -80,6 +81,7 @@
8081
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
8182
import com.oracle.truffle.api.dsl.NodeFactory;
8283
import com.oracle.truffle.api.dsl.Specialization;
84+
import com.oracle.truffle.api.interop.InteropException;
8385
import com.oracle.truffle.api.interop.InteropLibrary;
8486
import com.oracle.truffle.api.nodes.Node;
8587
import com.oracle.truffle.api.strings.TruffleString;
@@ -386,4 +388,75 @@ static Object getCompleterDelims(PythonModule self) {
386388
return (data.completerDelims != null) ? data.completerDelims : PNone.NONE;
387389
}
388390
}
391+
392+
@TruffleBoundary
393+
static Object getCompletionStateMember(PythonModule self, String member) {
394+
Object consoleHandler = self.getModuleState(LocalData.class).consoleHandler;
395+
if (consoleHandler != null) {
396+
try {
397+
InteropLibrary lib = InteropLibrary.getUncached();
398+
Object completionState = lib.invokeMember(consoleHandler, "getCompletionState");
399+
if (!lib.isNull(completionState)) {
400+
Object obj = lib.readMember(completionState, member);
401+
if (!lib.isNull(obj)) {
402+
return obj;
403+
}
404+
}
405+
} catch (InteropException e) {
406+
// Fall through
407+
}
408+
}
409+
return null;
410+
}
411+
412+
@TruffleBoundary
413+
static int getCompletionStateIntMember(PythonModule self, String member, int defaultValue) {
414+
Object idx = getCompletionStateMember(self, member);
415+
if (idx != null) {
416+
try {
417+
return InteropLibrary.getUncached().asInt(idx);
418+
} catch (InteropException e) {
419+
// Fall through
420+
}
421+
}
422+
return defaultValue;
423+
}
424+
425+
@Builtin(name = "get_line_buffer", minNumOfPositionalArgs = 1, declaresExplicitSelf = true)
426+
@GenerateNodeFactory
427+
abstract static class GetLineBuffer extends PythonUnaryBuiltinNode {
428+
@Specialization
429+
@TruffleBoundary
430+
static Object get(PythonModule self) {
431+
Object lineObj = getCompletionStateMember(self, "line");
432+
if (lineObj != null) {
433+
try {
434+
return InteropLibrary.getUncached().asTruffleString(lineObj);
435+
} catch (InteropException e) {
436+
// Fall through
437+
}
438+
}
439+
return T_EMPTY_STRING;
440+
}
441+
}
442+
443+
@Builtin(name = "get_begidx", minNumOfPositionalArgs = 1, declaresExplicitSelf = true)
444+
@GenerateNodeFactory
445+
abstract static class GetBegidx extends PythonUnaryBuiltinNode {
446+
@Specialization
447+
@TruffleBoundary
448+
static Object get(PythonModule self) {
449+
return getCompletionStateIntMember(self, "beginIdx", 0);
450+
}
451+
}
452+
453+
@Builtin(name = "get_endidx", minNumOfPositionalArgs = 1, declaresExplicitSelf = true)
454+
@GenerateNodeFactory
455+
abstract static class GetEndidx extends PythonUnaryBuiltinNode {
456+
@Specialization
457+
@TruffleBoundary
458+
static Object get(PythonModule self) {
459+
return getCompletionStateIntMember(self, "endIdx", 0);
460+
}
461+
}
389462
}

0 commit comments

Comments
 (0)