Implement ^W in readline.c, by Michal Hanselmann.
[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;
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         for(i = 0; i < nb_completions; i++) {
331             len = strlen(completions[i]);
332             if (len > max_width)
333                 max_width = len;
334         }
335         max_width += 2;
336         if (max_width < 10)
337             max_width = 10;
338         else if (max_width > 80)
339             max_width = 80;
340         nb_cols = 80 / max_width;
341         j = 0;
342         for(i = 0; i < nb_completions; i++) {
343             term_printf("%-*s", max_width, completions[i]);
344             if (++j == nb_cols || i == (nb_completions - 1)) {
345                 term_printf("\n");
346                 j = 0;
347             }
348         }
349         term_show_prompt2();
350     }
351 }
352
353 /* return true if command handled */
354 void readline_handle_byte(int ch)
355 {
356     switch(term_esc_state) {
357     case IS_NORM:
358         switch(ch) {
359         case 1:
360             term_bol();
361             break;
362         case 4:
363             term_delete_char();
364             break;
365         case 5:
366             term_eol();
367             break;
368         case 9:
369             term_completion();
370             break;
371         case 10:
372         case 13:
373             term_cmd_buf[term_cmd_buf_size] = '\0';
374             if (!term_is_password)
375                 term_hist_add(term_cmd_buf);
376             term_printf("\n");
377             /* NOTE: readline_start can be called here */
378             term_readline_func(term_readline_opaque, term_cmd_buf);
379             break;
380         case 23:
381             /* ^W */
382             term_backword();
383             break;
384         case 27:
385             term_esc_state = IS_ESC;
386             break;
387         case 127:
388         case 8:
389             term_backspace();
390             break;
391         case 155:
392             term_esc_state = IS_CSI;
393             break;
394         default:
395             if (ch >= 32) {
396                 term_insert_char(ch);
397             }
398             break;
399         }
400         break;
401     case IS_ESC:
402         if (ch == '[') {
403             term_esc_state = IS_CSI;
404             term_esc_param = 0;
405         } else {
406             term_esc_state = IS_NORM;
407         }
408         break;
409     case IS_CSI:
410         switch(ch) {
411         case 'A':
412         case 'F':
413             term_up_char();
414             break;
415         case 'B':
416         case 'E':
417             term_down_char();
418             break;
419         case 'D':
420             term_backward_char();
421             break;
422         case 'C':
423             term_forward_char();
424             break;
425         case '0' ... '9':
426             term_esc_param = term_esc_param * 10 + (ch - '0');
427             goto the_end;
428         case '~':
429             switch(term_esc_param) {
430             case 1:
431                 term_bol();
432                 break;
433             case 3:
434                 term_delete_char();
435                 break;
436             case 4:
437                 term_eol();
438                 break;
439             }
440             break;
441         default:
442             break;
443         }
444         term_esc_state = IS_NORM;
445     the_end:
446         break;
447     }
448     term_update();
449 }
450
451 void readline_start(const char *prompt, int is_password,
452                     ReadLineFunc *readline_func, void *opaque)
453 {
454     pstrcpy(term_prompt, sizeof(term_prompt), prompt);
455     term_readline_func = readline_func;
456     term_readline_opaque = opaque;
457     term_is_password = is_password;
458     term_show_prompt();
459 }
460
461 const char *readline_get_history(unsigned int index)
462 {
463     if (index >= TERM_MAX_CMDS)
464         return NULL;
465     return term_history[index];
466 }
467
468