Improve completion in monitor, by Pascal Terjan.
[qemu] / readline.c
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_backword(void)
160 {
161     int start;
162
163     if (term_cmd_buf_index == 0 || term_cmd_buf_index > term_cmd_buf_size) {
164         return;
165     }
166
167     start = term_cmd_buf_index - 1;
168
169     /* find first word (backwards) */
170     while (start > 0) {
171         if (!isspace(term_cmd_buf[start])) {
172             break;
173         }
174
175         --start;
176     }
177
178     /* find first space (backwards) */
179     while (start > 0) {
180         if (isspace(term_cmd_buf[start])) {
181             ++start;
182             break;
183         }
184
185         --start;
186     }
187
188     /* remove word */
189     if (start < term_cmd_buf_index) {
190         memmove(term_cmd_buf + start,
191                 term_cmd_buf + term_cmd_buf_index,
192                 term_cmd_buf_size - term_cmd_buf_index);
193         term_cmd_buf_size -= term_cmd_buf_index - start;
194         term_cmd_buf_index = start;
195     }
196 }
197
198 static void term_bol(void)
199 {
200     term_cmd_buf_index = 0;
201 }
202
203 static void term_eol(void)
204 {
205     term_cmd_buf_index = term_cmd_buf_size;
206 }
207
208 static void term_up_char(void)
209 {
210     int idx;
211
212     if (term_hist_entry == 0)
213         return;
214     if (term_hist_entry == -1) {
215         /* Find latest entry */
216         for (idx = 0; idx < TERM_MAX_CMDS; idx++) {
217             if (term_history[idx] == NULL)
218                 break;
219         }
220         term_hist_entry = idx;
221     }
222     term_hist_entry--;
223     if (term_hist_entry >= 0) {
224         pstrcpy(term_cmd_buf, sizeof(term_cmd_buf),
225                 term_history[term_hist_entry]);
226         term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf);
227     }
228 }
229
230 static void term_down_char(void)
231 {
232     if (term_hist_entry == TERM_MAX_CMDS - 1 || term_hist_entry == -1)
233         return;
234     if (term_history[++term_hist_entry] != NULL) {
235         pstrcpy(term_cmd_buf, sizeof(term_cmd_buf),
236                 term_history[term_hist_entry]);
237     } else {
238         term_hist_entry = -1;
239     }
240     term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf);
241 }
242
243 static void term_hist_add(const char *cmdline)
244 {
245     char *hist_entry, *new_entry;
246     int idx;
247
248     if (cmdline[0] == '\0')
249         return;
250     new_entry = NULL;
251     if (term_hist_entry != -1) {
252         /* We were editing an existing history entry: replace it */
253         hist_entry = term_history[term_hist_entry];
254         idx = term_hist_entry;
255         if (strcmp(hist_entry, cmdline) == 0) {
256             goto same_entry;
257         }
258     }
259     /* Search cmdline in history buffers */
260     for (idx = 0; idx < TERM_MAX_CMDS; idx++) {
261         hist_entry = term_history[idx];
262         if (hist_entry == NULL)
263             break;
264         if (strcmp(hist_entry, cmdline) == 0) {
265         same_entry:
266             new_entry = hist_entry;
267             /* Put this entry at the end of history */
268             memmove(&term_history[idx], &term_history[idx + 1],
269                     &term_history[TERM_MAX_CMDS] - &term_history[idx + 1]);
270             term_history[TERM_MAX_CMDS - 1] = NULL;
271             for (; idx < TERM_MAX_CMDS; idx++) {
272                 if (term_history[idx] == NULL)
273                     break;
274             }
275             break;
276         }
277     }
278     if (idx == TERM_MAX_CMDS) {
279         /* Need to get one free slot */
280         free(term_history[0]);
281         memcpy(term_history, &term_history[1],
282                &term_history[TERM_MAX_CMDS] - &term_history[1]);
283         term_history[TERM_MAX_CMDS - 1] = NULL;
284         idx = TERM_MAX_CMDS - 1;
285     }
286     if (new_entry == NULL)
287         new_entry = strdup(cmdline);
288     term_history[idx] = new_entry;
289     term_hist_entry = -1;
290 }
291
292 /* completion support */
293
294 void add_completion(const char *str)
295 {
296     if (nb_completions < NB_COMPLETIONS_MAX) {
297         completions[nb_completions++] = qemu_strdup(str);
298     }
299 }
300
301 static void term_completion(void)
302 {
303     int len, i, j, max_width, nb_cols, max_prefix;
304     char *cmdline;
305
306     nb_completions = 0;
307
308     cmdline = qemu_malloc(term_cmd_buf_index + 1);
309     if (!cmdline)
310         return;
311     memcpy(cmdline, term_cmd_buf, term_cmd_buf_index);
312     cmdline[term_cmd_buf_index] = '\0';
313     readline_find_completion(cmdline);
314     qemu_free(cmdline);
315
316     /* no completion found */
317     if (nb_completions <= 0)
318         return;
319     if (nb_completions == 1) {
320         len = strlen(completions[0]);
321         for(i = completion_index; i < len; i++) {
322             term_insert_char(completions[0][i]);
323         }
324         /* extra space for next argument. XXX: make it more generic */
325         if (len > 0 && completions[0][len - 1] != '/')
326             term_insert_char(' ');
327     } else {
328         term_printf("\n");
329         max_width = 0;
330         max_prefix = 0; 
331         for(i = 0; i < nb_completions; i++) {
332             len = strlen(completions[i]);
333             if (i==0) {
334                 max_prefix = len;
335             } else {
336                 if (len < max_prefix)
337                     max_prefix = len;
338                 for(j=0; j<max_prefix; j++) {
339                     if (completions[i][j] != completions[0][j])
340                         max_prefix = j;
341                 }
342             }
343             if (len > max_width)
344                 max_width = len;
345         }
346         if (max_prefix > 0) 
347             for(i = completion_index; i < max_prefix; i++) {
348                 term_insert_char(completions[0][i]);
349             }
350         max_width += 2;
351         if (max_width < 10)
352             max_width = 10;
353         else if (max_width > 80)
354             max_width = 80;
355         nb_cols = 80 / max_width;
356         j = 0;
357         for(i = 0; i < nb_completions; i++) {
358             term_printf("%-*s", max_width, completions[i]);
359             if (++j == nb_cols || i == (nb_completions - 1)) {
360                 term_printf("\n");
361                 j = 0;
362             }
363         }
364         term_show_prompt2();
365     }
366 }
367
368 /* return true if command handled */
369 void readline_handle_byte(int ch)
370 {
371     switch(term_esc_state) {
372     case IS_NORM:
373         switch(ch) {
374         case 1:
375             term_bol();
376             break;
377         case 4:
378             term_delete_char();
379             break;
380         case 5:
381             term_eol();
382             break;
383         case 9:
384             term_completion();
385             break;
386         case 10:
387         case 13:
388             term_cmd_buf[term_cmd_buf_size] = '\0';
389             if (!term_is_password)
390                 term_hist_add(term_cmd_buf);
391             term_printf("\n");
392             term_cmd_buf_index = 0;
393             term_cmd_buf_size = 0;
394             term_last_cmd_buf_index = 0;
395             term_last_cmd_buf_size = 0;
396             /* NOTE: readline_start can be called here */
397             term_readline_func(term_readline_opaque, term_cmd_buf);
398             break;
399         case 23:
400             /* ^W */
401             term_backword();
402             break;
403         case 27:
404             term_esc_state = IS_ESC;
405             break;
406         case 127:
407         case 8:
408             term_backspace();
409             break;
410         case 155:
411             term_esc_state = IS_CSI;
412             break;
413         default:
414             if (ch >= 32) {
415                 term_insert_char(ch);
416             }
417             break;
418         }
419         break;
420     case IS_ESC:
421         if (ch == '[') {
422             term_esc_state = IS_CSI;
423             term_esc_param = 0;
424         } else {
425             term_esc_state = IS_NORM;
426         }
427         break;
428     case IS_CSI:
429         switch(ch) {
430         case 'A':
431         case 'F':
432             term_up_char();
433             break;
434         case 'B':
435         case 'E':
436             term_down_char();
437             break;
438         case 'D':
439             term_backward_char();
440             break;
441         case 'C':
442             term_forward_char();
443             break;
444         case '0' ... '9':
445             term_esc_param = term_esc_param * 10 + (ch - '0');
446             goto the_end;
447         case '~':
448             switch(term_esc_param) {
449             case 1:
450                 term_bol();
451                 break;
452             case 3:
453                 term_delete_char();
454                 break;
455             case 4:
456                 term_eol();
457                 break;
458             }
459             break;
460         default:
461             break;
462         }
463         term_esc_state = IS_NORM;
464     the_end:
465         break;
466     }
467     term_update();
468 }
469
470 void readline_start(const char *prompt, int is_password,
471                     ReadLineFunc *readline_func, void *opaque)
472 {
473     pstrcpy(term_prompt, sizeof(term_prompt), prompt);
474     term_readline_func = readline_func;
475     term_readline_opaque = opaque;
476     term_is_password = is_password;
477     term_show_prompt();
478 }
479
480 const char *readline_get_history(unsigned int index)
481 {
482     if (index >= TERM_MAX_CMDS)
483         return NULL;
484     return term_history[index];
485 }
486
487