1 | /* |
---|
2 | * QEMU readline utility |
---|
3 | * |
---|
4 | * Copyright (c) 2003-2004 Fabrice Bellard |
---|
5 | * |
---|
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
---|
7 | * of this software and associated documentation files (the "Software"), to deal |
---|
8 | * in the Software without restriction, including without limitation the rights |
---|
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
---|
10 | * copies of the Software, and to permit persons to whom the Software is |
---|
11 | * furnished to do so, subject to the following conditions: |
---|
12 | * |
---|
13 | * The above copyright notice and this permission notice shall be included in |
---|
14 | * all copies or substantial portions of the Software. |
---|
15 | * |
---|
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
---|
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
---|
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
---|
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
---|
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
---|
22 | * THE SOFTWARE. |
---|
23 | */ |
---|
24 | #include "vl.h" |
---|
25 | |
---|
26 | #define TERM_CMD_BUF_SIZE 4095 |
---|
27 | #define TERM_MAX_CMDS 64 |
---|
28 | #define NB_COMPLETIONS_MAX 256 |
---|
29 | |
---|
30 | #define IS_NORM 0 |
---|
31 | #define IS_ESC 1 |
---|
32 | #define IS_CSI 2 |
---|
33 | |
---|
34 | #define printf do_not_use_printf |
---|
35 | |
---|
36 | static char term_cmd_buf[TERM_CMD_BUF_SIZE + 1]; |
---|
37 | static int term_cmd_buf_index; |
---|
38 | static int term_cmd_buf_size; |
---|
39 | |
---|
40 | static char term_last_cmd_buf[TERM_CMD_BUF_SIZE + 1]; |
---|
41 | static int term_last_cmd_buf_index; |
---|
42 | static int term_last_cmd_buf_size; |
---|
43 | |
---|
44 | static int term_esc_state; |
---|
45 | static int term_esc_param; |
---|
46 | |
---|
47 | static char *term_history[TERM_MAX_CMDS]; |
---|
48 | static int term_hist_entry = -1; |
---|
49 | |
---|
50 | static int nb_completions; |
---|
51 | int completion_index; |
---|
52 | static char *completions[NB_COMPLETIONS_MAX]; |
---|
53 | |
---|
54 | static ReadLineFunc *term_readline_func; |
---|
55 | static int term_is_password; |
---|
56 | static char term_prompt[256]; |
---|
57 | static void *term_readline_opaque; |
---|
58 | |
---|
59 | static void term_show_prompt2(void) |
---|
60 | { |
---|
61 | term_printf("%s", term_prompt); |
---|
62 | term_flush(); |
---|
63 | term_last_cmd_buf_index = 0; |
---|
64 | term_last_cmd_buf_size = 0; |
---|
65 | term_esc_state = IS_NORM; |
---|
66 | } |
---|
67 | |
---|
68 | static void term_show_prompt(void) |
---|
69 | { |
---|
70 | term_show_prompt2(); |
---|
71 | term_cmd_buf_index = 0; |
---|
72 | term_cmd_buf_size = 0; |
---|
73 | } |
---|
74 | |
---|
75 | /* update the displayed command line */ |
---|
76 | static void term_update(void) |
---|
77 | { |
---|
78 | int i, delta, len; |
---|
79 | |
---|
80 | if (term_cmd_buf_size != term_last_cmd_buf_size || |
---|
81 | memcmp(term_cmd_buf, term_last_cmd_buf, term_cmd_buf_size) != 0) { |
---|
82 | for(i = 0; i < term_last_cmd_buf_index; i++) { |
---|
83 | term_printf("\033[D"); |
---|
84 | } |
---|
85 | term_cmd_buf[term_cmd_buf_size] = '\0'; |
---|
86 | if (term_is_password) { |
---|
87 | len = strlen(term_cmd_buf); |
---|
88 | for(i = 0; i < len; i++) |
---|
89 | term_printf("*"); |
---|
90 | } else { |
---|
91 | term_printf("%s", term_cmd_buf); |
---|
92 | } |
---|
93 | term_printf("\033[K"); |
---|
94 | memcpy(term_last_cmd_buf, term_cmd_buf, term_cmd_buf_size); |
---|
95 | term_last_cmd_buf_size = term_cmd_buf_size; |
---|
96 | term_last_cmd_buf_index = term_cmd_buf_size; |
---|
97 | } |
---|
98 | if (term_cmd_buf_index != term_last_cmd_buf_index) { |
---|
99 | delta = term_cmd_buf_index - term_last_cmd_buf_index; |
---|
100 | if (delta > 0) { |
---|
101 | for(i = 0;i < delta; i++) { |
---|
102 | term_printf("\033[C"); |
---|
103 | } |
---|
104 | } else { |
---|
105 | delta = -delta; |
---|
106 | for(i = 0;i < delta; i++) { |
---|
107 | term_printf("\033[D"); |
---|
108 | } |
---|
109 | } |
---|
110 | term_last_cmd_buf_index = term_cmd_buf_index; |
---|
111 | } |
---|
112 | term_flush(); |
---|
113 | } |
---|
114 | |
---|
115 | static void term_insert_char(int ch) |
---|
116 | { |
---|
117 | if (term_cmd_buf_index < TERM_CMD_BUF_SIZE) { |
---|
118 | memmove(term_cmd_buf + term_cmd_buf_index + 1, |
---|
119 | term_cmd_buf + term_cmd_buf_index, |
---|
120 | term_cmd_buf_size - term_cmd_buf_index); |
---|
121 | term_cmd_buf[term_cmd_buf_index] = ch; |
---|
122 | term_cmd_buf_size++; |
---|
123 | term_cmd_buf_index++; |
---|
124 | } |
---|
125 | } |
---|
126 | |
---|
127 | static void term_backward_char(void) |
---|
128 | { |
---|
129 | if (term_cmd_buf_index > 0) { |
---|
130 | term_cmd_buf_index--; |
---|
131 | } |
---|
132 | } |
---|
133 | |
---|
134 | static void term_forward_char(void) |
---|
135 | { |
---|
136 | if (term_cmd_buf_index < term_cmd_buf_size) { |
---|
137 | term_cmd_buf_index++; |
---|
138 | } |
---|
139 | } |
---|
140 | |
---|
141 | static void term_delete_char(void) |
---|
142 | { |
---|
143 | if (term_cmd_buf_index < term_cmd_buf_size) { |
---|
144 | memmove(term_cmd_buf + term_cmd_buf_index, |
---|
145 | term_cmd_buf + term_cmd_buf_index + 1, |
---|
146 | term_cmd_buf_size - term_cmd_buf_index - 1); |
---|
147 | term_cmd_buf_size--; |
---|
148 | } |
---|
149 | } |
---|
150 | |
---|
151 | static void term_backspace(void) |
---|
152 | { |
---|
153 | if (term_cmd_buf_index > 0) { |
---|
154 | term_backward_char(); |
---|
155 | term_delete_char(); |
---|
156 | } |
---|
157 | } |
---|
158 | |
---|
159 | static void term_bol(void) |
---|
160 | { |
---|
161 | term_cmd_buf_index = 0; |
---|
162 | } |
---|
163 | |
---|
164 | static void term_eol(void) |
---|
165 | { |
---|
166 | term_cmd_buf_index = term_cmd_buf_size; |
---|
167 | } |
---|
168 | |
---|
169 | static void term_up_char(void) |
---|
170 | { |
---|
171 | int idx; |
---|
172 | |
---|
173 | if (term_hist_entry == 0) |
---|
174 | return; |
---|
175 | if (term_hist_entry == -1) { |
---|
176 | /* Find latest entry */ |
---|
177 | for (idx = 0; idx < TERM_MAX_CMDS; idx++) { |
---|
178 | if (term_history[idx] == NULL) |
---|
179 | break; |
---|
180 | } |
---|
181 | term_hist_entry = idx; |
---|
182 | } |
---|
183 | term_hist_entry--; |
---|
184 | if (term_hist_entry >= 0) { |
---|
185 | pstrcpy(term_cmd_buf, sizeof(term_cmd_buf), |
---|
186 | term_history[term_hist_entry]); |
---|
187 | term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf); |
---|
188 | } |
---|
189 | } |
---|
190 | |
---|
191 | static void term_down_char(void) |
---|
192 | { |
---|
193 | if (term_hist_entry == TERM_MAX_CMDS - 1 || term_hist_entry == -1) |
---|
194 | return; |
---|
195 | if (term_history[++term_hist_entry] != NULL) { |
---|
196 | pstrcpy(term_cmd_buf, sizeof(term_cmd_buf), |
---|
197 | term_history[term_hist_entry]); |
---|
198 | } else { |
---|
199 | term_hist_entry = -1; |
---|
200 | } |
---|
201 | term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf); |
---|
202 | } |
---|
203 | |
---|
204 | static void term_hist_add(const char *cmdline) |
---|
205 | { |
---|
206 | char *hist_entry, *new_entry; |
---|
207 | int idx; |
---|
208 | |
---|
209 | if (cmdline[0] == '\0') |
---|
210 | return; |
---|
211 | new_entry = NULL; |
---|
212 | if (term_hist_entry != -1) { |
---|
213 | /* We were editing an existing history entry: replace it */ |
---|
214 | hist_entry = term_history[term_hist_entry]; |
---|
215 | idx = term_hist_entry; |
---|
216 | if (strcmp(hist_entry, cmdline) == 0) { |
---|
217 | goto same_entry; |
---|
218 | } |
---|
219 | } |
---|
220 | /* Search cmdline in history buffers */ |
---|
221 | for (idx = 0; idx < TERM_MAX_CMDS; idx++) { |
---|
222 | hist_entry = term_history[idx]; |
---|
223 | if (hist_entry == NULL) |
---|
224 | break; |
---|
225 | if (strcmp(hist_entry, cmdline) == 0) { |
---|
226 | same_entry: |
---|
227 | new_entry = hist_entry; |
---|
228 | /* Put this entry at the end of history */ |
---|
229 | memmove(&term_history[idx], &term_history[idx + 1], |
---|
230 | &term_history[TERM_MAX_CMDS] - &term_history[idx + 1]); |
---|
231 | term_history[TERM_MAX_CMDS - 1] = NULL; |
---|
232 | for (; idx < TERM_MAX_CMDS; idx++) { |
---|
233 | if (term_history[idx] == NULL) |
---|
234 | break; |
---|
235 | } |
---|
236 | break; |
---|
237 | } |
---|
238 | } |
---|
239 | if (idx == TERM_MAX_CMDS) { |
---|
240 | /* Need to get one free slot */ |
---|
241 | free(term_history[0]); |
---|
242 | memcpy(term_history, &term_history[1], |
---|
243 | &term_history[TERM_MAX_CMDS] - &term_history[1]); |
---|
244 | term_history[TERM_MAX_CMDS - 1] = NULL; |
---|
245 | idx = TERM_MAX_CMDS - 1; |
---|
246 | } |
---|
247 | if (new_entry == NULL) |
---|
248 | new_entry = strdup(cmdline); |
---|
249 | term_history[idx] = new_entry; |
---|
250 | term_hist_entry = -1; |
---|
251 | } |
---|
252 | |
---|
253 | /* completion support */ |
---|
254 | |
---|
255 | void add_completion(const char *str) |
---|
256 | { |
---|
257 | if (nb_completions < NB_COMPLETIONS_MAX) { |
---|
258 | completions[nb_completions++] = qemu_strdup(str); |
---|
259 | } |
---|
260 | } |
---|
261 | |
---|
262 | static void term_completion(void) |
---|
263 | { |
---|
264 | int len, i, j, max_width, nb_cols; |
---|
265 | char *cmdline; |
---|
266 | |
---|
267 | nb_completions = 0; |
---|
268 | |
---|
269 | cmdline = qemu_malloc(term_cmd_buf_index + 1); |
---|
270 | if (!cmdline) |
---|
271 | return; |
---|
272 | memcpy(cmdline, term_cmd_buf, term_cmd_buf_index); |
---|
273 | cmdline[term_cmd_buf_index] = '\0'; |
---|
274 | readline_find_completion(cmdline); |
---|
275 | qemu_free(cmdline); |
---|
276 | |
---|
277 | /* no completion found */ |
---|
278 | if (nb_completions <= 0) |
---|
279 | return; |
---|
280 | if (nb_completions == 1) { |
---|
281 | len = strlen(completions[0]); |
---|
282 | for(i = completion_index; i < len; i++) { |
---|
283 | term_insert_char(completions[0][i]); |
---|
284 | } |
---|
285 | /* extra space for next argument. XXX: make it more generic */ |
---|
286 | if (len > 0 && completions[0][len - 1] != '/') |
---|
287 | term_insert_char(' '); |
---|
288 | } else { |
---|
289 | term_printf("\n"); |
---|
290 | max_width = 0; |
---|
291 | for(i = 0; i < nb_completions; i++) { |
---|
292 | len = strlen(completions[i]); |
---|
293 | if (len > max_width) |
---|
294 | max_width = len; |
---|
295 | } |
---|
296 | max_width += 2; |
---|
297 | if (max_width < 10) |
---|
298 | max_width = 10; |
---|
299 | else if (max_width > 80) |
---|
300 | max_width = 80; |
---|
301 | nb_cols = 80 / max_width; |
---|
302 | j = 0; |
---|
303 | for(i = 0; i < nb_completions; i++) { |
---|
304 | term_printf("%-*s", max_width, completions[i]); |
---|
305 | if (++j == nb_cols || i == (nb_completions - 1)) { |
---|
306 | term_printf("\n"); |
---|
307 | j = 0; |
---|
308 | } |
---|
309 | } |
---|
310 | term_show_prompt2(); |
---|
311 | } |
---|
312 | } |
---|
313 | |
---|
314 | /* return true if command handled */ |
---|
315 | void readline_handle_byte(int ch) |
---|
316 | { |
---|
317 | switch(term_esc_state) { |
---|
318 | case IS_NORM: |
---|
319 | switch(ch) { |
---|
320 | case 1: |
---|
321 | term_bol(); |
---|
322 | break; |
---|
323 | case 4: |
---|
324 | term_delete_char(); |
---|
325 | break; |
---|
326 | case 5: |
---|
327 | term_eol(); |
---|
328 | break; |
---|
329 | case 9: |
---|
330 | term_completion(); |
---|
331 | break; |
---|
332 | case 10: |
---|
333 | case 13: |
---|
334 | term_cmd_buf[term_cmd_buf_size] = '\0'; |
---|
335 | if (!term_is_password) |
---|
336 | term_hist_add(term_cmd_buf); |
---|
337 | term_printf("\n"); |
---|
338 | /* NOTE: readline_start can be called here */ |
---|
339 | term_readline_func(term_readline_opaque, term_cmd_buf); |
---|
340 | break; |
---|
341 | case 27: |
---|
342 | term_esc_state = IS_ESC; |
---|
343 | break; |
---|
344 | case 127: |
---|
345 | case 8: |
---|
346 | term_backspace(); |
---|
347 | break; |
---|
348 | case 155: |
---|
349 | term_esc_state = IS_CSI; |
---|
350 | break; |
---|
351 | default: |
---|
352 | if (ch >= 32) { |
---|
353 | term_insert_char(ch); |
---|
354 | } |
---|
355 | break; |
---|
356 | } |
---|
357 | break; |
---|
358 | case IS_ESC: |
---|
359 | if (ch == '[') { |
---|
360 | term_esc_state = IS_CSI; |
---|
361 | term_esc_param = 0; |
---|
362 | } else { |
---|
363 | term_esc_state = IS_NORM; |
---|
364 | } |
---|
365 | break; |
---|
366 | case IS_CSI: |
---|
367 | switch(ch) { |
---|
368 | case 'A': |
---|
369 | case 'F': |
---|
370 | term_up_char(); |
---|
371 | break; |
---|
372 | case 'B': |
---|
373 | case 'E': |
---|
374 | term_down_char(); |
---|
375 | break; |
---|
376 | case 'D': |
---|
377 | term_backward_char(); |
---|
378 | break; |
---|
379 | case 'C': |
---|
380 | term_forward_char(); |
---|
381 | break; |
---|
382 | case '0' ... '9': |
---|
383 | term_esc_param = term_esc_param * 10 + (ch - '0'); |
---|
384 | goto the_end; |
---|
385 | case '~': |
---|
386 | switch(term_esc_param) { |
---|
387 | case 1: |
---|
388 | term_bol(); |
---|
389 | break; |
---|
390 | case 3: |
---|
391 | term_delete_char(); |
---|
392 | break; |
---|
393 | case 4: |
---|
394 | term_eol(); |
---|
395 | break; |
---|
396 | } |
---|
397 | break; |
---|
398 | default: |
---|
399 | break; |
---|
400 | } |
---|
401 | term_esc_state = IS_NORM; |
---|
402 | the_end: |
---|
403 | break; |
---|
404 | } |
---|
405 | term_update(); |
---|
406 | } |
---|
407 | |
---|
408 | void readline_start(const char *prompt, int is_password, |
---|
409 | ReadLineFunc *readline_func, void *opaque) |
---|
410 | { |
---|
411 | pstrcpy(term_prompt, sizeof(term_prompt), prompt); |
---|
412 | term_readline_func = readline_func; |
---|
413 | term_readline_opaque = opaque; |
---|
414 | term_is_password = is_password; |
---|
415 | term_show_prompt(); |
---|
416 | } |
---|
417 | |
---|
418 | const char *readline_get_history(unsigned int index) |
---|
419 | { |
---|
420 | if (index >= TERM_MAX_CMDS) |
---|
421 | return NULL; |
---|
422 | return term_history[index]; |
---|
423 | } |
---|
424 | |
---|
425 | |
---|