blob: 649ade161beec6bad7cb57ec3509087afd48b89e [file] [log] [blame]
Damien George04b91472014-05-03 23:27:38 +01001/*
2 * This file is part of the Micro Python project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
Damien Georgea1a2c412015-04-26 17:55:31 +01006 * Copyright (c) 2013-2015 Damien P. George
Damien George04b91472014-05-03 23:27:38 +01007 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
Damien Georgea1a2c412015-04-26 17:55:31 +010027#include <string.h>
28#include "py/obj.h"
29#include "py/runtime.h"
Damien George51dfcb42015-01-01 20:27:54 +000030#include "py/repl.h"
Damien92c06562013-10-22 22:32:27 +010031
Damien George58ebde42014-05-21 20:32:59 +010032#if MICROPY_HELPER_REPL
Damien George136f6752014-01-07 14:54:15 +000033
Damien George969a6b32014-12-10 22:07:04 +000034STATIC bool str_startswith_word(const char *str, const char *head) {
Damien George42f3de92014-10-03 17:44:14 +000035 mp_uint_t i;
Damien92c06562013-10-22 22:32:27 +010036 for (i = 0; str[i] && head[i]; i++) {
37 if (str[i] != head[i]) {
38 return false;
39 }
40 }
Damien George8cc96a32013-12-30 18:23:50 +000041 return head[i] == '\0' && (str[i] == '\0' || !unichar_isalpha(str[i]));
Damien92c06562013-10-22 22:32:27 +010042}
43
Damien George97790452014-04-08 11:04:29 +000044bool mp_repl_continue_with_input(const char *input) {
45 // check for blank input
46 if (input[0] == '\0') {
47 return false;
Damien92c06562013-10-22 22:32:27 +010048 }
49
Damien George97790452014-04-08 11:04:29 +000050 // check if input starts with a certain keyword
51 bool starts_with_compound_keyword =
52 input[0] == '@'
53 || str_startswith_word(input, "if")
54 || str_startswith_word(input, "while")
55 || str_startswith_word(input, "for")
56 || str_startswith_word(input, "try")
57 || str_startswith_word(input, "with")
58 || str_startswith_word(input, "def")
59 || str_startswith_word(input, "class")
60 ;
61
62 // check for unmatched open bracket or triple quote
63 // TODO don't look at triple quotes inside single quotes
Damien92c06562013-10-22 22:32:27 +010064 int n_paren = 0;
65 int n_brack = 0;
66 int n_brace = 0;
Damien Georgea28507a2014-04-07 13:01:30 +010067 int in_triple_quote = 0;
Damien George97790452014-04-08 11:04:29 +000068 const char *i;
69 for (i = input; *i; i++) {
70 switch (*i) {
Damien92c06562013-10-22 22:32:27 +010071 case '(': n_paren += 1; break;
72 case ')': n_paren -= 1; break;
73 case '[': n_brack += 1; break;
74 case ']': n_brack -= 1; break;
75 case '{': n_brace += 1; break;
76 case '}': n_brace -= 1; break;
Damien George97790452014-04-08 11:04:29 +000077 case '\'':
78 if (in_triple_quote != '"' && i[1] == '\'' && i[2] == '\'') {
79 i += 2;
80 in_triple_quote = '\'' - in_triple_quote;
81 }
82 break;
Damien Georgea28507a2014-04-07 13:01:30 +010083 case '"':
Damien George97790452014-04-08 11:04:29 +000084 if (in_triple_quote != '\'' && i[1] == '"' && i[2] == '"') {
85 i += 2;
86 in_triple_quote = '"' - in_triple_quote;
Damien Georgea28507a2014-04-07 13:01:30 +010087 }
88 break;
Damien92c06562013-10-22 22:32:27 +010089 }
90 }
Damien George97790452014-04-08 11:04:29 +000091
92 // continue if unmatched brackets or quotes
93 if (n_paren > 0 || n_brack > 0 || n_brace > 0 || in_triple_quote != 0) {
94 return true;
95 }
96
Damien George73c79b92014-04-08 11:33:28 +000097 // continue if last character was backslash (for line continuation)
98 if (i[-1] == '\\') {
99 return true;
100 }
101
Damien George97790452014-04-08 11:04:29 +0000102 // continue if compound keyword and last line was not empty
103 if (starts_with_compound_keyword && i[-1] != '\n') {
104 return true;
105 }
106
107 // otherwise, don't continue
108 return false;
Damien92c06562013-10-22 22:32:27 +0100109}
Damien George136f6752014-01-07 14:54:15 +0000110
Damien Georgea1a2c412015-04-26 17:55:31 +0100111mp_uint_t mp_repl_autocomplete(const char *str, mp_uint_t len, const mp_print_t *print, const char **compl_str) {
112 // scan backwards to find start of "a.b.c" chain
113 const char *top = str + len;
114 for (const char *s = top; --s >= str;) {
115 if (!(unichar_isalpha(*s) || unichar_isdigit(*s) || *s == '_' || *s == '.')) {
116 ++s;
117 str = s;
118 break;
119 }
120 }
121
122 // begin search in locals dict
123 mp_obj_dict_t *dict = mp_locals_get();
124
125 for (;;) {
126 // get next word in string to complete
127 const char *s_start = str;
128 while (str < top && *str != '.') {
129 ++str;
130 }
131 mp_uint_t s_len = str - s_start;
132
133 if (str < top) {
134 // a complete word, lookup in current dict
135
136 mp_obj_t obj = MP_OBJ_NULL;
137 for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
138 if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
139 mp_uint_t d_len;
140 const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
141 if (s_len == d_len && strncmp(s_start, d_str, d_len) == 0) {
142 obj = dict->map.table[i].value;
143 break;
144 }
145 }
146 }
147
148 if (obj == MP_OBJ_NULL) {
149 // lookup failed
150 return 0;
151 }
152
153 // found an object of this name; try to get its dict
154 if (MP_OBJ_IS_TYPE(obj, &mp_type_module)) {
155 dict = mp_obj_module_get_globals(obj);
156 } else {
157 mp_obj_type_t *type;
158 if (MP_OBJ_IS_TYPE(obj, &mp_type_type)) {
159 type = obj;
160 } else {
161 type = mp_obj_get_type(obj);
162 }
163 if (type->locals_dict != MP_OBJ_NULL && MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)) {
164 dict = type->locals_dict;
165 } else {
166 // obj has no dict
167 return 0;
168 }
169 }
170
171 // skip '.' to move to next word
172 ++str;
173
174 } else {
175 // end of string, do completion on this partial name
176
177 // look for matches
178 int n_found = 0;
179 const char *match_str = NULL;
180 mp_uint_t match_len = 0;
181 for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
182 if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
183 mp_uint_t d_len;
184 const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
185 if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
186 if (match_str == NULL) {
187 match_str = d_str;
188 match_len = d_len;
189 } else {
Damien Georgef27aa272015-04-29 01:01:48 +0100190 for (mp_uint_t j = s_len; j < match_len && j < d_len; ++j) {
191 if (match_str[j] != d_str[j]) {
192 match_len = j;
Damien Georgea1a2c412015-04-26 17:55:31 +0100193 break;
194 }
195 }
196 }
197 ++n_found;
198 }
199 }
200 }
201
202 // nothing found
203 if (n_found == 0) {
204 return 0;
205 }
206
207 // 1 match found, or multiple matches with a common prefix
208 if (n_found == 1 || match_len > s_len) {
209 *compl_str = match_str + s_len;
210 return match_len - s_len;
211 }
212
213 // multiple matches found, print them out
214
215 #define WORD_SLOT_LEN (16)
216 #define MAX_LINE_LEN (4 * WORD_SLOT_LEN)
217
218 int line_len = MAX_LINE_LEN; // force a newline for first word
219 for (mp_uint_t i = 0; i < dict->map.alloc; i++) {
220 if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
221 mp_uint_t d_len;
222 const char *d_str = mp_obj_str_get_data(dict->map.table[i].key, &d_len);
223 if (s_len <= d_len && strncmp(s_start, d_str, s_len) == 0) {
224 int gap = (line_len + WORD_SLOT_LEN - 1) / WORD_SLOT_LEN * WORD_SLOT_LEN - line_len;
225 if (gap < 2) {
226 gap += WORD_SLOT_LEN;
227 }
228 if (line_len + gap + d_len <= MAX_LINE_LEN) {
229 // TODO optimise printing of gap?
Damien Georgef27aa272015-04-29 01:01:48 +0100230 for (int j = 0; j < gap; ++j) {
Damien Georgea1a2c412015-04-26 17:55:31 +0100231 mp_print_str(print, " ");
232 }
233 mp_print_str(print, d_str);
234 line_len += gap + d_len;
235 } else {
236 mp_printf(print, "\n%s", d_str);
237 line_len = d_len;
238 }
239 }
240 }
241 }
242 mp_print_str(print, "\n");
243
244 return (mp_uint_t)(-1); // indicate many matches
245 }
246 }
247}
248
Damien George58ebde42014-05-21 20:32:59 +0100249#endif // MICROPY_HELPER_REPL