Skip to content

Commit a9b42a0

Browse files
authored
Merge pull request #4548 from tyomitch/patch-1
[repl] Autocomplete builtin modules
2 parents e1e4d21 + 59fa9b0 commit a9b42a0

File tree

1 file changed

+146
-112
lines changed

1 file changed

+146
-112
lines changed

py/repl.c

Lines changed: 146 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include <string.h>
2828
#include "py/obj.h"
29+
#include "py/objmodule.h"
2930
#include "py/runtime.h"
3031
#include "py/builtin.h"
3132
#include "py/repl.h"
@@ -143,6 +144,93 @@ bool mp_repl_continue_with_input(const char *input) {
143144
return false;
144145
}
145146

147+
STATIC bool test_qstr(mp_obj_t obj, qstr name) {
148+
if (obj) {
149+
// try object member
150+
mp_obj_t dest[2];
151+
mp_load_method_protected(obj, name, dest, true);
152+
return dest[0] != MP_OBJ_NULL;
153+
} else {
154+
// try builtin module
155+
return mp_map_lookup((mp_map_t *)&mp_builtin_module_map,
156+
MP_OBJ_NEW_QSTR(name), MP_MAP_LOOKUP);
157+
}
158+
}
159+
160+
STATIC const char *find_completions(const char *s_start, size_t s_len,
161+
mp_obj_t obj, size_t *match_len, qstr *q_first, qstr *q_last) {
162+
163+
const char *match_str = NULL;
164+
*match_len = 0;
165+
*q_first = *q_last = 0;
166+
size_t nqstr = QSTR_TOTAL();
167+
for (qstr q = MP_QSTR_ + 1; q < nqstr; ++q) {
168+
size_t d_len;
169+
const char *d_str = (const char *)qstr_data(q, &d_len);
170+
if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
171+
if (test_qstr(obj, q)) {
172+
// special case; filter out words that begin with underscore
173+
// unless there's already a partial match
174+
if (s_len == 0 && d_str[0] == '_') {
175+
continue;
176+
}
177+
if (match_str == NULL) {
178+
match_str = d_str;
179+
*match_len = d_len;
180+
} else {
181+
// search for longest common prefix of match_str and d_str
182+
// (assumes these strings are null-terminated)
183+
for (size_t j = s_len; j <= *match_len && j <= d_len; ++j) {
184+
if (match_str[j] != d_str[j]) {
185+
*match_len = j;
186+
break;
187+
}
188+
}
189+
}
190+
if (*q_first == 0) {
191+
*q_first = q;
192+
}
193+
*q_last = q;
194+
}
195+
}
196+
}
197+
return match_str;
198+
}
199+
200+
STATIC void print_completions(const mp_print_t *print,
201+
const char *s_start, size_t s_len,
202+
mp_obj_t obj, qstr q_first, qstr q_last) {
203+
204+
#define WORD_SLOT_LEN (16)
205+
#define MAX_LINE_LEN (4 * WORD_SLOT_LEN)
206+
207+
int line_len = MAX_LINE_LEN; // force a newline for first word
208+
for (qstr q = q_first; q <= q_last; ++q) {
209+
size_t d_len;
210+
const char *d_str = (const char *)qstr_data(q, &d_len);
211+
if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
212+
if (test_qstr(obj, q)) {
213+
int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len;
214+
if (gap < 2) {
215+
gap += WORD_SLOT_LEN;
216+
}
217+
if (line_len + gap + d_len <= MAX_LINE_LEN) {
218+
// TODO optimise printing of gap?
219+
for (int j = 0; j < gap; ++j) {
220+
mp_print_str(print, " ");
221+
}
222+
mp_print_str(print, d_str);
223+
line_len += gap + d_len;
224+
} else {
225+
mp_printf(print, "\n%s", d_str);
226+
line_len = d_len;
227+
}
228+
}
229+
}
230+
}
231+
mp_print_str(print, "\n");
232+
}
233+
146234
size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print, const char **compl_str) {
147235
// scan backwards to find start of "a.b.c" chain
148236
const char *org_str = str;
@@ -155,137 +243,83 @@ size_t mp_repl_autocomplete(const char *str, size_t len, const mp_print_t *print
155243
}
156244
}
157245

158-
size_t nqstr = QSTR_TOTAL();
159-
160246
// begin search in outer global dict which is accessed from __main__
161247
mp_obj_t obj = MP_OBJ_FROM_PTR(&mp_module___main__);
162248
mp_obj_t dest[2];
163249

250+
const char *s_start;
251+
size_t s_len;
252+
164253
for (;;) {
165254
// get next word in string to complete
166-
const char *s_start = str;
255+
s_start = str;
167256
while (str < top && *str != '.') {
168257
++str;
169258
}
170-
size_t s_len = str - s_start;
171-
172-
if (str < top) {
173-
// a complete word, lookup in current object
174-
qstr q = qstr_find_strn(s_start, s_len);
175-
if (q == MP_QSTR_NULL) {
176-
// lookup will fail
177-
return 0;
178-
}
179-
mp_load_method_protected(obj, q, dest, true);
180-
obj = dest[0]; // attribute, method, or MP_OBJ_NULL if nothing found
181-
182-
if (obj == MP_OBJ_NULL) {
183-
// lookup failed
184-
return 0;
185-
}
259+
s_len = str - s_start;
186260

187-
// skip '.' to move to next word
188-
++str;
189-
190-
} else {
261+
if (str == top) {
191262
// end of string, do completion on this partial name
263+
break;
264+
}
192265

193-
// look for matches
194-
const char *match_str = NULL;
195-
size_t match_len = 0;
196-
qstr q_first = 0, q_last = 0;
197-
for (qstr q = MP_QSTR_ + 1; q < nqstr; ++q) {
198-
size_t d_len;
199-
const char *d_str = (const char *)qstr_data(q, &d_len);
200-
if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
201-
mp_load_method_protected(obj, q, dest, true);
202-
if (dest[0] != MP_OBJ_NULL) {
203-
// special case; filter out words that begin with underscore
204-
// unless there's already a partial match
205-
if (s_len == 0 && d_str[0] == '_') {
206-
continue;
207-
}
208-
if (match_str == NULL) {
209-
match_str = d_str;
210-
match_len = d_len;
211-
} else {
212-
// search for longest common prefix of match_str and d_str
213-
// (assumes these strings are null-terminated)
214-
for (size_t j = s_len; j <= match_len && j <= d_len; ++j) {
215-
if (match_str[j] != d_str[j]) {
216-
match_len = j;
217-
break;
218-
}
219-
}
220-
}
221-
if (q_first == 0) {
222-
q_first = q;
223-
}
224-
q_last = q;
225-
}
226-
}
227-
}
228-
229-
// nothing found
230-
if (q_first == 0) {
231-
if (s_len == 0) {
232-
*compl_str = " ";
233-
return 4;
234-
}
235-
// If there're no better alternatives, and if it's first word
236-
// in the line, try to complete "import".
237-
if (s_start == org_str) {
238-
static const char import_str[] = "import ";
239-
if (memcmp(s_start, import_str, s_len) == 0) {
240-
*compl_str = import_str + s_len;
241-
return sizeof(import_str) - 1 - s_len;
242-
}
243-
}
244-
245-
return 0;
246-
}
266+
// a complete word, lookup in current object
267+
qstr q = qstr_find_strn(s_start, s_len);
268+
if (q == MP_QSTR_NULL) {
269+
// lookup will fail
270+
return 0;
271+
}
272+
mp_load_method_protected(obj, q, dest, true);
273+
obj = dest[0]; // attribute, method, or MP_OBJ_NULL if nothing found
247274

248-
// 1 match found, or multiple matches with a common prefix
249-
if (q_first == q_last || match_len > s_len) {
250-
*compl_str = match_str + s_len;
251-
return match_len - s_len;
252-
}
275+
if (obj == MP_OBJ_NULL) {
276+
// lookup failed
277+
return 0;
278+
}
253279

254-
// multiple matches found, print them out
280+
// skip '.' to move to next word
281+
++str;
282+
}
255283

256-
#define WORD_SLOT_LEN (16)
257-
#define MAX_LINE_LEN (4 * WORD_SLOT_LEN)
284+
// look for matches
285+
size_t match_len;
286+
qstr q_first, q_last;
287+
const char *match_str =
288+
find_completions(s_start, s_len, obj, &match_len, &q_first, &q_last);
258289

259-
int line_len = MAX_LINE_LEN; // force a newline for first word
260-
for (qstr q = q_first; q <= q_last; ++q) {
261-
size_t d_len;
262-
const char *d_str = (const char *)qstr_data(q, &d_len);
263-
if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
264-
mp_load_method_protected(obj, q, dest, true);
265-
if (dest[0] != MP_OBJ_NULL) {
266-
int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len;
267-
if (gap < 2) {
268-
gap += WORD_SLOT_LEN;
269-
}
270-
if (line_len + gap + d_len <= MAX_LINE_LEN) {
271-
// TODO optimise printing of gap?
272-
for (int j = 0; j < gap; ++j) {
273-
mp_print_str(print, " ");
274-
}
275-
mp_print_str(print, d_str);
276-
line_len += gap + d_len;
277-
} else {
278-
mp_printf(print, "\n%s", d_str);
279-
line_len = d_len;
280-
}
281-
}
282-
}
290+
// nothing found
291+
if (q_first == 0) {
292+
// If there're no better alternatives, and if it's first word
293+
// in the line, try to complete "import".
294+
static const char import_str[] = "import ";
295+
if (s_start == org_str && s_len > 0) {
296+
if (memcmp(s_start, import_str, s_len) == 0) {
297+
*compl_str = import_str + s_len;
298+
return sizeof(import_str) - 1 - s_len;
283299
}
284-
mp_print_str(print, "\n");
285-
286-
return (size_t)(-1); // indicate many matches
300+
}
301+
// after "import", suggest built-in modules
302+
if (len >= 7 && !memcmp(org_str, import_str, 7)) {
303+
obj = NULL;
304+
match_str = find_completions(
305+
s_start, s_len, obj, &match_len, &q_first, &q_last);
306+
}
307+
if (q_first == 0) {
308+
*compl_str = " ";
309+
return s_len ? 0 : 4;
287310
}
288311
}
312+
313+
// 1 match found, or multiple matches with a common prefix
314+
if (q_first == q_last || match_len > s_len) {
315+
*compl_str = match_str + s_len;
316+
return match_len - s_len;
317+
}
318+
319+
// multiple matches found, print them out
320+
print_completions(print, s_start, s_len, obj, q_first, q_last);
321+
322+
return (size_t)(-1); // indicate many matches
289323
}
290324

291325
#endif // MICROPY_HELPER_REPL

0 commit comments

Comments
 (0)