py, readline: Add tab autocompletion for REPL.
Can complete names in the global namespace, as well as a chain of attributes, eg pyb.Pin.board.<tab> will give a list of all board pins. Costs 700 bytes ROM on Thumb2 arch, but greatly increases usability of REPL prompt.
This commit is contained in:
parent
b7a4f15b34
commit
a1a2c411b2
|
@ -29,6 +29,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "py/mpstate.h"
|
||||
#include "py/repl.h"
|
||||
#include "readline.h"
|
||||
#ifdef MICROPY_HAL_H
|
||||
#include MICROPY_HAL_H
|
||||
|
@ -134,6 +135,28 @@ int readline_process_char(int c) {
|
|||
redraw_step_back = 1;
|
||||
redraw_from_cursor = true;
|
||||
}
|
||||
#if MICROPY_HELPER_REPL
|
||||
} else if (c == 9) {
|
||||
// tab magic
|
||||
const char *compl_str;
|
||||
mp_uint_t compl_len = mp_repl_autocomplete(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len, &mp_plat_print, &compl_str);
|
||||
if (compl_len == 0) {
|
||||
// no match
|
||||
} else if (compl_len == (mp_uint_t)(-1)) {
|
||||
// many matches
|
||||
mp_hal_stdout_tx_str(rl.prompt);
|
||||
mp_hal_stdout_tx_strn(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
|
||||
redraw_from_cursor = true;
|
||||
} else {
|
||||
// one match
|
||||
for (int i = 0; i < compl_len; ++i) {
|
||||
vstr_ins_byte(rl.line, rl.cursor_pos + i, *compl_str++);
|
||||
}
|
||||
// set redraw parameters
|
||||
redraw_from_cursor = true;
|
||||
redraw_step_forward = compl_len;
|
||||
}
|
||||
#endif
|
||||
} else if (32 <= c && c <= 126) {
|
||||
// printable character
|
||||
vstr_ins_char(rl.line, rl.cursor_pos, c);
|
||||
|
|
143
py/repl.c
143
py/repl.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2013, 2014 Damien P. George
|
||||
* Copyright (c) 2013-2015 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -24,6 +24,9 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "py/obj.h"
|
||||
#include "py/runtime.h"
|
||||
#include "py/repl.h"
|
||||
|
||||
#if MICROPY_HELPER_REPL
|
||||
|
@ -105,4 +108,142 @@ bool mp_repl_continue_with_input(const char *input) {
|
|||
return false;
|
||||
}
|
||||
|
||||
mp_uint_t mp_repl_autocomplete(const char *str, mp_uint_t len, const mp_print_t *print, const char **compl_str) {
|
||||
// scan backwards to find start of "a.b.c" chain
|
||||
const char *top = str + len;
|
||||
for (const char *s = top; --s >= str;) {
|
||||
if (!(unichar_isalpha(*s) || unichar_isdigit(*s) || *s == '_' || *s == '.')) {
|
||||
++s;
|
||||
str = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// begin search in locals dict
|
||||
mp_obj_dict_t *dict = mp_locals_get();
|
||||
|
||||
for (;;) {
|
||||
// get next word in string to complete
|
||||
const char *s_start = str;
|
||||
while (str < top && *str != '.') {
|
||||
++str;
|
||||
}
|
||||
mp_uint_t s_len = str - s_start;
|
||||
|
||||
if (str < top) {
|
||||
// a complete word, lookup in current dict
|
||||
|
||||
mp_obj_t obj = MP_OBJ_NULL;
|
||||
for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
|
||||
if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
|
||||
mp_uint_t d_len;
|
||||
const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
|
||||
if (s_len == d_len && strncmp(s_start, d_str, d_len) == 0) {
|
||||
obj = dict->map.table[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj == MP_OBJ_NULL) {
|
||||
// lookup failed
|
||||
return 0;
|
||||
}
|
||||
|
||||
// found an object of this name; try to get its dict
|
||||
if (MP_OBJ_IS_TYPE(obj, &mp_type_module)) {
|
||||
dict = mp_obj_module_get_globals(obj);
|
||||
} else {
|
||||
mp_obj_type_t *type;
|
||||
if (MP_OBJ_IS_TYPE(obj, &mp_type_type)) {
|
||||
type = obj;
|
||||
} else {
|
||||
type = mp_obj_get_type(obj);
|
||||
}
|
||||
if (type->locals_dict != MP_OBJ_NULL && MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)) {
|
||||
dict = type->locals_dict;
|
||||
} else {
|
||||
// obj has no dict
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// skip '.' to move to next word
|
||||
++str;
|
||||
|
||||
} else {
|
||||
// end of string, do completion on this partial name
|
||||
|
||||
// look for matches
|
||||
int n_found = 0;
|
||||
const char *match_str = NULL;
|
||||
mp_uint_t match_len = 0;
|
||||
for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
|
||||
if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
|
||||
mp_uint_t d_len;
|
||||
const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
|
||||
if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
|
||||
if (match_str == NULL) {
|
||||
match_str = d_str;
|
||||
match_len = d_len;
|
||||
} else {
|
||||
for (mp_uint_t i = s_len; i < match_len && i < d_len; ++i) {
|
||||
if (match_str[i] != d_str[i]) {
|
||||
match_len = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
++n_found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nothing found
|
||||
if (n_found == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 1 match found, or multiple matches with a common prefix
|
||||
if (n_found == 1 || match_len > s_len) {
|
||||
*compl_str = match_str + s_len;
|
||||
return match_len - s_len;
|
||||
}
|
||||
|
||||
// multiple matches found, print them out
|
||||
|
||||
#define WORD_SLOT_LEN (16)
|
||||
#define MAX_LINE_LEN (4 * WORD_SLOT_LEN)
|
||||
|
||||
int line_len = MAX_LINE_LEN; // force a newline for first word
|
||||
for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
|
||||
if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
|
||||
mp_uint_t d_len;
|
||||
const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
|
||||
if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
|
||||
int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len;
|
||||
if (gap < 2) {
|
||||
gap += WORD_SLOT_LEN;
|
||||
}
|
||||
if (line_len + gap + d_len <= MAX_LINE_LEN) {
|
||||
// TODO optimise printing of gap?
|
||||
for (int i = 0; i < gap; ++i) {
|
||||
mp_print_str(print, " ");
|
||||
}
|
||||
mp_print_str(print, d_str);
|
||||
line_len += gap + d_len;
|
||||
} else {
|
||||
mp_printf(print, "\n%s", d_str);
|
||||
line_len = d_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mp_print_str(print, "\n");
|
||||
|
||||
return (mp_uint_t)(-1); // indicate many matches
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MICROPY_HELPER_REPL
|
||||
|
|
|
@ -28,9 +28,11 @@
|
|||
|
||||
#include "py/mpconfig.h"
|
||||
#include "py/misc.h"
|
||||
#include "py/mpprint.h"
|
||||
|
||||
#if MICROPY_HELPER_REPL
|
||||
bool mp_repl_continue_with_input(const char *input);
|
||||
mp_uint_t mp_repl_autocomplete(const char *str, mp_uint_t len, const mp_print_t *print, const char **compl_str);
|
||||
#endif
|
||||
|
||||
#endif // __MICROPY_INCLUDED_PY_REPL_H__
|
||||
|
|
Loading…
Reference in New Issue