Contents of /tags/mkinitrd-6_1_2/busybox/libbb/lineedit.c
Parent Directory | Revision Log
Revision 844 -
(show annotations)
(download)
Mon May 4 17:23:09 2009 UTC (15 years, 4 months ago) by niro
File MIME type: text/plain
File size: 46565 byte(s)
Mon May 4 17:23:09 2009 UTC (15 years, 4 months ago) by niro
File MIME type: text/plain
File size: 46565 byte(s)
tagged 'mkinitrd-6_1_2'
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Termios command line History and Editing. |
4 | * |
5 | * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license. |
6 | * Written by: Vladimir Oleynik <dzo@simtreas.ru> |
7 | * |
8 | * Used ideas: |
9 | * Adam Rogoyski <rogoyski@cs.utexas.edu> |
10 | * Dave Cinege <dcinege@psychosis.com> |
11 | * Jakub Jelinek (c) 1995 |
12 | * Erik Andersen <andersen@codepoet.org> (Majorly adjusted for busybox) |
13 | * |
14 | * This code is 'as is' with no warranty. |
15 | */ |
16 | |
17 | /* |
18 | * Usage and known bugs: |
19 | * Terminal key codes are not extensive, and more will probably |
20 | * need to be added. This version was created on Debian GNU/Linux 2.x. |
21 | * Delete, Backspace, Home, End, and the arrow keys were tested |
22 | * to work in an Xterm and console. Ctrl-A also works as Home. |
23 | * Ctrl-E also works as End. |
24 | * |
25 | * lineedit does not know that the terminal escape sequences do not |
26 | * take up space on the screen. The redisplay code assumes, unless |
27 | * told otherwise, that each character in the prompt is a printable |
28 | * character that takes up one character position on the screen. |
29 | * You need to tell lineedit that some sequences of characters |
30 | * in the prompt take up no screen space. Compatibly with readline, |
31 | * use the \[ escape to begin a sequence of non-printing characters, |
32 | * and the \] escape to signal the end of such a sequence. Example: |
33 | * |
34 | * PS1='\[\033[01;32m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\] ' |
35 | */ |
36 | |
37 | #include "libbb.h" |
38 | |
39 | |
40 | /* FIXME: obsolete CONFIG item? */ |
41 | #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0 |
42 | |
43 | |
44 | #ifdef TEST |
45 | |
46 | #define ENABLE_FEATURE_EDITING 0 |
47 | #define ENABLE_FEATURE_TAB_COMPLETION 0 |
48 | #define ENABLE_FEATURE_USERNAME_COMPLETION 0 |
49 | #define ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT 0 |
50 | |
51 | #endif /* TEST */ |
52 | |
53 | |
54 | /* Entire file (except TESTing part) sits inside this #if */ |
55 | #if ENABLE_FEATURE_EDITING |
56 | |
57 | #if ENABLE_LOCALE_SUPPORT |
58 | #define Isprint(c) isprint(c) |
59 | #else |
60 | #define Isprint(c) ((c) >= ' ' && (c) != ((unsigned char)'\233')) |
61 | #endif |
62 | |
63 | #define ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR \ |
64 | (ENABLE_FEATURE_USERNAME_COMPLETION || ENABLE_FEATURE_EDITING_FANCY_PROMPT) |
65 | #define USE_FEATURE_GETUSERNAME_AND_HOMEDIR(...) |
66 | #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR |
67 | #undef USE_FEATURE_GETUSERNAME_AND_HOMEDIR |
68 | #define USE_FEATURE_GETUSERNAME_AND_HOMEDIR(...) __VA_ARGS__ |
69 | #endif |
70 | |
71 | enum { |
72 | /* We use int16_t for positions, need to limit line len */ |
73 | MAX_LINELEN = CONFIG_FEATURE_EDITING_MAX_LEN < 0x7ff0 |
74 | ? CONFIG_FEATURE_EDITING_MAX_LEN |
75 | : 0x7ff0 |
76 | }; |
77 | |
78 | #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR |
79 | static const char null_str[] ALIGN1 = ""; |
80 | #endif |
81 | |
82 | /* We try to minimize both static and stack usage. */ |
83 | struct lineedit_statics { |
84 | line_input_t *state; |
85 | |
86 | volatile unsigned cmdedit_termw; /* = 80; */ /* actual terminal width */ |
87 | sighandler_t previous_SIGWINCH_handler; |
88 | |
89 | unsigned cmdedit_x; /* real x terminal position */ |
90 | unsigned cmdedit_y; /* pseudoreal y terminal position */ |
91 | unsigned cmdedit_prmt_len; /* length of prompt (without colors etc) */ |
92 | |
93 | unsigned cursor; |
94 | unsigned command_len; |
95 | char *command_ps; |
96 | |
97 | const char *cmdedit_prompt; |
98 | #if ENABLE_FEATURE_EDITING_FANCY_PROMPT |
99 | int num_ok_lines; /* = 1; */ |
100 | #endif |
101 | |
102 | #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR |
103 | char *user_buf; |
104 | char *home_pwd_buf; /* = (char*)null_str; */ |
105 | #endif |
106 | |
107 | #if ENABLE_FEATURE_TAB_COMPLETION |
108 | char **matches; |
109 | unsigned num_matches; |
110 | #endif |
111 | |
112 | #if ENABLE_FEATURE_EDITING_VI |
113 | #define DELBUFSIZ 128 |
114 | char *delptr; |
115 | smallint newdelflag; /* whether delbuf should be reused yet */ |
116 | char delbuf[DELBUFSIZ]; /* a place to store deleted characters */ |
117 | #endif |
118 | |
119 | /* Formerly these were big buffers on stack: */ |
120 | #if ENABLE_FEATURE_TAB_COMPLETION |
121 | char exe_n_cwd_tab_completion__dirbuf[MAX_LINELEN]; |
122 | char input_tab__matchBuf[MAX_LINELEN]; |
123 | int16_t find_match__int_buf[MAX_LINELEN + 1]; /* need to have 9 bits at least */ |
124 | int16_t find_match__pos_buf[MAX_LINELEN + 1]; |
125 | #endif |
126 | }; |
127 | |
128 | /* See lineedit_ptr_hack.c */ |
129 | extern struct lineedit_statics *const lineedit_ptr_to_statics; |
130 | |
131 | #define S (*lineedit_ptr_to_statics) |
132 | #define state (S.state ) |
133 | #define cmdedit_termw (S.cmdedit_termw ) |
134 | #define previous_SIGWINCH_handler (S.previous_SIGWINCH_handler) |
135 | #define cmdedit_x (S.cmdedit_x ) |
136 | #define cmdedit_y (S.cmdedit_y ) |
137 | #define cmdedit_prmt_len (S.cmdedit_prmt_len) |
138 | #define cursor (S.cursor ) |
139 | #define command_len (S.command_len ) |
140 | #define command_ps (S.command_ps ) |
141 | #define cmdedit_prompt (S.cmdedit_prompt ) |
142 | #define num_ok_lines (S.num_ok_lines ) |
143 | #define user_buf (S.user_buf ) |
144 | #define home_pwd_buf (S.home_pwd_buf ) |
145 | #define matches (S.matches ) |
146 | #define num_matches (S.num_matches ) |
147 | #define delptr (S.delptr ) |
148 | #define newdelflag (S.newdelflag ) |
149 | #define delbuf (S.delbuf ) |
150 | |
151 | #define INIT_S() do { \ |
152 | (*(struct lineedit_statics**)&lineedit_ptr_to_statics) = xzalloc(sizeof(S)); \ |
153 | barrier(); \ |
154 | cmdedit_termw = 80; \ |
155 | USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines = 1;) \ |
156 | USE_FEATURE_GETUSERNAME_AND_HOMEDIR(home_pwd_buf = (char*)null_str;) \ |
157 | } while (0) |
158 | static void deinit_S(void) |
159 | { |
160 | #if ENABLE_FEATURE_EDITING_FANCY_PROMPT |
161 | /* This one is allocated only if FANCY_PROMPT is on |
162 | * (otherwise it points to verbatim prompt (NOT malloced) */ |
163 | free((char*)cmdedit_prompt); |
164 | #endif |
165 | #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR |
166 | free(user_buf); |
167 | if (home_pwd_buf != null_str) |
168 | free(home_pwd_buf); |
169 | #endif |
170 | free(lineedit_ptr_to_statics); |
171 | } |
172 | #define DEINIT_S() deinit_S() |
173 | |
174 | |
175 | /* Put 'command_ps[cursor]', cursor++. |
176 | * Advance cursor on screen. If we reached right margin, scroll text up |
177 | * and remove terminal margin effect by printing 'next_char' */ |
178 | #define HACK_FOR_WRONG_WIDTH 1 |
179 | #if HACK_FOR_WRONG_WIDTH |
180 | static void cmdedit_set_out_char(void) |
181 | #define cmdedit_set_out_char(next_char) cmdedit_set_out_char() |
182 | #else |
183 | static void cmdedit_set_out_char(int next_char) |
184 | #endif |
185 | { |
186 | int c = (unsigned char)command_ps[cursor]; |
187 | |
188 | if (c == '\0') { |
189 | /* erase character after end of input string */ |
190 | c = ' '; |
191 | } |
192 | #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT |
193 | /* Display non-printable characters in reverse */ |
194 | if (!Isprint(c)) { |
195 | if (c >= 128) |
196 | c -= 128; |
197 | if (c < ' ') |
198 | c += '@'; |
199 | if (c == 127) |
200 | c = '?'; |
201 | printf("\033[7m%c\033[0m", c); |
202 | } else |
203 | #endif |
204 | { |
205 | bb_putchar(c); |
206 | } |
207 | if (++cmdedit_x >= cmdedit_termw) { |
208 | /* terminal is scrolled down */ |
209 | cmdedit_y++; |
210 | cmdedit_x = 0; |
211 | #if HACK_FOR_WRONG_WIDTH |
212 | /* This works better if our idea of term width is wrong |
213 | * and it is actually wider (often happens on serial lines). |
214 | * Printing CR,LF *forces* cursor to next line. |
215 | * OTOH if terminal width is correct AND terminal does NOT |
216 | * have automargin (IOW: it is moving cursor to next line |
217 | * by itself (which is wrong for VT-10x terminals)), |
218 | * this will break things: there will be one extra empty line */ |
219 | puts("\r"); /* + implicit '\n' */ |
220 | #else |
221 | /* Works ok only if cmdedit_termw is correct */ |
222 | /* destroy "(auto)margin" */ |
223 | bb_putchar(next_char); |
224 | bb_putchar('\b'); |
225 | #endif |
226 | } |
227 | // Huh? What if command_ps[cursor] == '\0' (we are at the end already?) |
228 | cursor++; |
229 | } |
230 | |
231 | /* Move to end of line (by printing all chars till the end) */ |
232 | static void input_end(void) |
233 | { |
234 | while (cursor < command_len) |
235 | cmdedit_set_out_char(' '); |
236 | } |
237 | |
238 | /* Go to the next line */ |
239 | static void goto_new_line(void) |
240 | { |
241 | input_end(); |
242 | if (cmdedit_x) |
243 | bb_putchar('\n'); |
244 | } |
245 | |
246 | |
247 | static void out1str(const char *s) |
248 | { |
249 | if (s) |
250 | fputs(s, stdout); |
251 | } |
252 | |
253 | static void beep(void) |
254 | { |
255 | bb_putchar('\007'); |
256 | } |
257 | |
258 | /* Move back one character */ |
259 | /* (optimized for slow terminals) */ |
260 | static void input_backward(unsigned num) |
261 | { |
262 | int count_y; |
263 | |
264 | if (num > cursor) |
265 | num = cursor; |
266 | if (!num) |
267 | return; |
268 | cursor -= num; |
269 | |
270 | if (cmdedit_x >= num) { |
271 | cmdedit_x -= num; |
272 | if (num <= 4) { |
273 | /* This is longer by 5 bytes on x86. |
274 | * Also gets miscompiled for ARM users |
275 | * (busybox.net/bugs/view.php?id=2274). |
276 | * printf(("\b\b\b\b" + 4) - num); |
277 | * return; |
278 | */ |
279 | do { |
280 | bb_putchar('\b'); |
281 | } while (--num); |
282 | return; |
283 | } |
284 | printf("\033[%uD", num); |
285 | return; |
286 | } |
287 | |
288 | /* Need to go one or more lines up */ |
289 | num -= cmdedit_x; |
290 | { |
291 | unsigned w = cmdedit_termw; /* volatile var */ |
292 | count_y = 1 + (num / w); |
293 | cmdedit_y -= count_y; |
294 | cmdedit_x = w * count_y - num; |
295 | } |
296 | /* go to 1st column; go up; go to correct column */ |
297 | printf("\r" "\033[%dA" "\033[%dC", count_y, cmdedit_x); |
298 | } |
299 | |
300 | static void put_prompt(void) |
301 | { |
302 | out1str(cmdedit_prompt); |
303 | cursor = 0; |
304 | { |
305 | unsigned w = cmdedit_termw; /* volatile var */ |
306 | cmdedit_y = cmdedit_prmt_len / w; /* new quasireal y */ |
307 | cmdedit_x = cmdedit_prmt_len % w; |
308 | } |
309 | } |
310 | |
311 | /* draw prompt, editor line, and clear tail */ |
312 | static void redraw(int y, int back_cursor) |
313 | { |
314 | if (y > 0) /* up to start y */ |
315 | printf("\033[%dA", y); |
316 | bb_putchar('\r'); |
317 | put_prompt(); |
318 | input_end(); /* rewrite */ |
319 | printf("\033[J"); /* erase after cursor */ |
320 | input_backward(back_cursor); |
321 | } |
322 | |
323 | /* Delete the char in front of the cursor, optionally saving it |
324 | * for later putback */ |
325 | #if !ENABLE_FEATURE_EDITING_VI |
326 | static void input_delete(void) |
327 | #define input_delete(save) input_delete() |
328 | #else |
329 | static void input_delete(int save) |
330 | #endif |
331 | { |
332 | int j = cursor; |
333 | |
334 | if (j == (int)command_len) |
335 | return; |
336 | |
337 | #if ENABLE_FEATURE_EDITING_VI |
338 | if (save) { |
339 | if (newdelflag) { |
340 | delptr = delbuf; |
341 | newdelflag = 0; |
342 | } |
343 | if ((delptr - delbuf) < DELBUFSIZ) |
344 | *delptr++ = command_ps[j]; |
345 | } |
346 | #endif |
347 | |
348 | overlapping_strcpy(command_ps + j, command_ps + j + 1); |
349 | command_len--; |
350 | input_end(); /* rewrite new line */ |
351 | cmdedit_set_out_char(' '); /* erase char */ |
352 | input_backward(cursor - j); /* back to old pos cursor */ |
353 | } |
354 | |
355 | #if ENABLE_FEATURE_EDITING_VI |
356 | static void put(void) |
357 | { |
358 | int ocursor; |
359 | int j = delptr - delbuf; |
360 | |
361 | if (j == 0) |
362 | return; |
363 | ocursor = cursor; |
364 | /* open hole and then fill it */ |
365 | memmove(command_ps + cursor + j, command_ps + cursor, command_len - cursor + 1); |
366 | strncpy(command_ps + cursor, delbuf, j); |
367 | command_len += j; |
368 | input_end(); /* rewrite new line */ |
369 | input_backward(cursor - ocursor - j + 1); /* at end of new text */ |
370 | } |
371 | #endif |
372 | |
373 | /* Delete the char in back of the cursor */ |
374 | static void input_backspace(void) |
375 | { |
376 | if (cursor > 0) { |
377 | input_backward(1); |
378 | input_delete(0); |
379 | } |
380 | } |
381 | |
382 | /* Move forward one character */ |
383 | static void input_forward(void) |
384 | { |
385 | if (cursor < command_len) |
386 | cmdedit_set_out_char(command_ps[cursor + 1]); |
387 | } |
388 | |
389 | #if ENABLE_FEATURE_TAB_COMPLETION |
390 | |
391 | static void free_tab_completion_data(void) |
392 | { |
393 | if (matches) { |
394 | while (num_matches) |
395 | free(matches[--num_matches]); |
396 | free(matches); |
397 | matches = NULL; |
398 | } |
399 | } |
400 | |
401 | static void add_match(char *matched) |
402 | { |
403 | matches = xrealloc_vector(matches, 4, num_matches); |
404 | matches[num_matches] = matched; |
405 | num_matches++; |
406 | } |
407 | |
408 | #if ENABLE_FEATURE_USERNAME_COMPLETION |
409 | static void username_tab_completion(char *ud, char *with_shash_flg) |
410 | { |
411 | struct passwd *entry; |
412 | int userlen; |
413 | |
414 | ud++; /* ~user/... to user/... */ |
415 | userlen = strlen(ud); |
416 | |
417 | if (with_shash_flg) { /* "~/..." or "~user/..." */ |
418 | char *sav_ud = ud - 1; |
419 | char *home = NULL; |
420 | |
421 | if (*ud == '/') { /* "~/..." */ |
422 | home = home_pwd_buf; |
423 | } else { |
424 | /* "~user/..." */ |
425 | char *temp; |
426 | temp = strchr(ud, '/'); |
427 | *temp = '\0'; /* ~user\0 */ |
428 | entry = getpwnam(ud); |
429 | *temp = '/'; /* restore ~user/... */ |
430 | ud = temp; |
431 | if (entry) |
432 | home = entry->pw_dir; |
433 | } |
434 | if (home) { |
435 | if ((userlen + strlen(home) + 1) < MAX_LINELEN) { |
436 | /* /home/user/... */ |
437 | sprintf(sav_ud, "%s%s", home, ud); |
438 | } |
439 | } |
440 | } else { |
441 | /* "~[^/]*" */ |
442 | /* Using _r function to avoid pulling in static buffers */ |
443 | char line_buff[256]; |
444 | struct passwd pwd; |
445 | struct passwd *result; |
446 | |
447 | setpwent(); |
448 | while (!getpwent_r(&pwd, line_buff, sizeof(line_buff), &result)) { |
449 | /* Null usernames should result in all users as possible completions. */ |
450 | if (/*!userlen || */ strncmp(ud, pwd.pw_name, userlen) == 0) { |
451 | add_match(xasprintf("~%s/", pwd.pw_name)); |
452 | } |
453 | } |
454 | endpwent(); |
455 | } |
456 | } |
457 | #endif /* FEATURE_COMMAND_USERNAME_COMPLETION */ |
458 | |
459 | enum { |
460 | FIND_EXE_ONLY = 0, |
461 | FIND_DIR_ONLY = 1, |
462 | FIND_FILE_ONLY = 2, |
463 | }; |
464 | |
465 | static int path_parse(char ***p, int flags) |
466 | { |
467 | int npth; |
468 | const char *pth; |
469 | char *tmp; |
470 | char **res; |
471 | |
472 | /* if not setenv PATH variable, to search cur dir "." */ |
473 | if (flags != FIND_EXE_ONLY) |
474 | return 1; |
475 | |
476 | if (state->flags & WITH_PATH_LOOKUP) |
477 | pth = state->path_lookup; |
478 | else |
479 | pth = getenv("PATH"); |
480 | /* PATH=<empty> or PATH=:<empty> */ |
481 | if (!pth || !pth[0] || LONE_CHAR(pth, ':')) |
482 | return 1; |
483 | |
484 | tmp = (char*)pth; |
485 | npth = 1; /* path component count */ |
486 | while (1) { |
487 | tmp = strchr(tmp, ':'); |
488 | if (!tmp) |
489 | break; |
490 | if (*++tmp == '\0') |
491 | break; /* :<empty> */ |
492 | npth++; |
493 | } |
494 | |
495 | res = xmalloc(npth * sizeof(char*)); |
496 | res[0] = tmp = xstrdup(pth); |
497 | npth = 1; |
498 | while (1) { |
499 | tmp = strchr(tmp, ':'); |
500 | if (!tmp) |
501 | break; |
502 | *tmp++ = '\0'; /* ':' -> '\0' */ |
503 | if (*tmp == '\0') |
504 | break; /* :<empty> */ |
505 | res[npth++] = tmp; |
506 | } |
507 | *p = res; |
508 | return npth; |
509 | } |
510 | |
511 | static void exe_n_cwd_tab_completion(char *command, int type) |
512 | { |
513 | DIR *dir; |
514 | struct dirent *next; |
515 | struct stat st; |
516 | char *path1[1]; |
517 | char **paths = path1; |
518 | int npaths; |
519 | int i; |
520 | char *found; |
521 | char *pfind = strrchr(command, '/'); |
522 | /* char dirbuf[MAX_LINELEN]; */ |
523 | #define dirbuf (S.exe_n_cwd_tab_completion__dirbuf) |
524 | |
525 | npaths = 1; |
526 | path1[0] = (char*)"."; |
527 | |
528 | if (pfind == NULL) { |
529 | /* no dir, if flags==EXE_ONLY - get paths, else "." */ |
530 | npaths = path_parse(&paths, type); |
531 | pfind = command; |
532 | } else { |
533 | /* dirbuf = ".../.../.../" */ |
534 | safe_strncpy(dirbuf, command, (pfind - command) + 2); |
535 | #if ENABLE_FEATURE_USERNAME_COMPLETION |
536 | if (dirbuf[0] == '~') /* ~/... or ~user/... */ |
537 | username_tab_completion(dirbuf, dirbuf); |
538 | #endif |
539 | paths[0] = dirbuf; |
540 | /* point to 'l' in "..../last_component" */ |
541 | pfind++; |
542 | } |
543 | |
544 | for (i = 0; i < npaths; i++) { |
545 | dir = opendir(paths[i]); |
546 | if (!dir) |
547 | continue; /* don't print an error */ |
548 | |
549 | while ((next = readdir(dir)) != NULL) { |
550 | int len1; |
551 | const char *str_found = next->d_name; |
552 | |
553 | /* matched? */ |
554 | if (strncmp(str_found, pfind, strlen(pfind))) |
555 | continue; |
556 | /* not see .name without .match */ |
557 | if (*str_found == '.' && *pfind == '\0') { |
558 | if (NOT_LONE_CHAR(paths[i], '/') || str_found[1]) |
559 | continue; |
560 | str_found = ""; /* only "/" */ |
561 | } |
562 | found = concat_path_file(paths[i], str_found); |
563 | /* hmm, remove in progress? */ |
564 | /* NB: stat() first so that we see is it a directory; |
565 | * but if that fails, use lstat() so that |
566 | * we still match dangling links */ |
567 | if (stat(found, &st) && lstat(found, &st)) |
568 | goto cont; |
569 | /* find with dirs? */ |
570 | if (paths[i] != dirbuf) |
571 | strcpy(found, next->d_name); /* only name */ |
572 | |
573 | len1 = strlen(found); |
574 | found = xrealloc(found, len1 + 2); |
575 | found[len1] = '\0'; |
576 | found[len1+1] = '\0'; |
577 | |
578 | if (S_ISDIR(st.st_mode)) { |
579 | /* name is a directory */ |
580 | if (found[len1-1] != '/') { |
581 | found[len1] = '/'; |
582 | } |
583 | } else { |
584 | /* not put found file if search only dirs for cd */ |
585 | if (type == FIND_DIR_ONLY) |
586 | goto cont; |
587 | } |
588 | /* Add it to the list */ |
589 | add_match(found); |
590 | continue; |
591 | cont: |
592 | free(found); |
593 | } |
594 | closedir(dir); |
595 | } |
596 | if (paths != path1) { |
597 | free(paths[0]); /* allocated memory is only in first member */ |
598 | free(paths); |
599 | } |
600 | #undef dirbuf |
601 | } |
602 | |
603 | #define QUOT (UCHAR_MAX+1) |
604 | |
605 | #define collapse_pos(is, in) do { \ |
606 | memmove(int_buf+(is), int_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \ |
607 | memmove(pos_buf+(is), pos_buf+(in), (MAX_LINELEN+1-(is)-(in)) * sizeof(pos_buf[0])); \ |
608 | } while (0) |
609 | |
610 | static int find_match(char *matchBuf, int *len_with_quotes) |
611 | { |
612 | int i, j; |
613 | int command_mode; |
614 | int c, c2; |
615 | /* int16_t int_buf[MAX_LINELEN + 1]; */ |
616 | /* int16_t pos_buf[MAX_LINELEN + 1]; */ |
617 | #define int_buf (S.find_match__int_buf) |
618 | #define pos_buf (S.find_match__pos_buf) |
619 | |
620 | /* set to integer dimension characters and own positions */ |
621 | for (i = 0;; i++) { |
622 | int_buf[i] = (unsigned char)matchBuf[i]; |
623 | if (int_buf[i] == 0) { |
624 | pos_buf[i] = -1; /* indicator end line */ |
625 | break; |
626 | } |
627 | pos_buf[i] = i; |
628 | } |
629 | |
630 | /* mask \+symbol and convert '\t' to ' ' */ |
631 | for (i = j = 0; matchBuf[i]; i++, j++) |
632 | if (matchBuf[i] == '\\') { |
633 | collapse_pos(j, j + 1); |
634 | int_buf[j] |= QUOT; |
635 | i++; |
636 | #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT |
637 | if (matchBuf[i] == '\t') /* algorithm equivalent */ |
638 | int_buf[j] = ' ' | QUOT; |
639 | #endif |
640 | } |
641 | #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT |
642 | else if (matchBuf[i] == '\t') |
643 | int_buf[j] = ' '; |
644 | #endif |
645 | |
646 | /* mask "symbols" or 'symbols' */ |
647 | c2 = 0; |
648 | for (i = 0; int_buf[i]; i++) { |
649 | c = int_buf[i]; |
650 | if (c == '\'' || c == '"') { |
651 | if (c2 == 0) |
652 | c2 = c; |
653 | else { |
654 | if (c == c2) |
655 | c2 = 0; |
656 | else |
657 | int_buf[i] |= QUOT; |
658 | } |
659 | } else if (c2 != 0 && c != '$') |
660 | int_buf[i] |= QUOT; |
661 | } |
662 | |
663 | /* skip commands with arguments if line has commands delimiters */ |
664 | /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */ |
665 | for (i = 0; int_buf[i]; i++) { |
666 | c = int_buf[i]; |
667 | c2 = int_buf[i + 1]; |
668 | j = i ? int_buf[i - 1] : -1; |
669 | command_mode = 0; |
670 | if (c == ';' || c == '&' || c == '|') { |
671 | command_mode = 1 + (c == c2); |
672 | if (c == '&') { |
673 | if (j == '>' || j == '<') |
674 | command_mode = 0; |
675 | } else if (c == '|' && j == '>') |
676 | command_mode = 0; |
677 | } |
678 | if (command_mode) { |
679 | collapse_pos(0, i + command_mode); |
680 | i = -1; /* hack incremet */ |
681 | } |
682 | } |
683 | /* collapse `command...` */ |
684 | for (i = 0; int_buf[i]; i++) |
685 | if (int_buf[i] == '`') { |
686 | for (j = i + 1; int_buf[j]; j++) |
687 | if (int_buf[j] == '`') { |
688 | collapse_pos(i, j + 1); |
689 | j = 0; |
690 | break; |
691 | } |
692 | if (j) { |
693 | /* not found close ` - command mode, collapse all previous */ |
694 | collapse_pos(0, i + 1); |
695 | break; |
696 | } else |
697 | i--; /* hack incremet */ |
698 | } |
699 | |
700 | /* collapse (command...(command...)...) or {command...{command...}...} */ |
701 | c = 0; /* "recursive" level */ |
702 | c2 = 0; |
703 | for (i = 0; int_buf[i]; i++) |
704 | if (int_buf[i] == '(' || int_buf[i] == '{') { |
705 | if (int_buf[i] == '(') |
706 | c++; |
707 | else |
708 | c2++; |
709 | collapse_pos(0, i + 1); |
710 | i = -1; /* hack incremet */ |
711 | } |
712 | for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++) |
713 | if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) { |
714 | if (int_buf[i] == ')') |
715 | c--; |
716 | else |
717 | c2--; |
718 | collapse_pos(0, i + 1); |
719 | i = -1; /* hack incremet */ |
720 | } |
721 | |
722 | /* skip first not quote space */ |
723 | for (i = 0; int_buf[i]; i++) |
724 | if (int_buf[i] != ' ') |
725 | break; |
726 | if (i) |
727 | collapse_pos(0, i); |
728 | |
729 | /* set find mode for completion */ |
730 | command_mode = FIND_EXE_ONLY; |
731 | for (i = 0; int_buf[i]; i++) |
732 | if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') { |
733 | if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY |
734 | && matchBuf[pos_buf[0]] == 'c' |
735 | && matchBuf[pos_buf[1]] == 'd' |
736 | ) { |
737 | command_mode = FIND_DIR_ONLY; |
738 | } else { |
739 | command_mode = FIND_FILE_ONLY; |
740 | break; |
741 | } |
742 | } |
743 | for (i = 0; int_buf[i]; i++) |
744 | /* "strlen" */; |
745 | /* find last word */ |
746 | for (--i; i >= 0; i--) { |
747 | c = int_buf[i]; |
748 | if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') { |
749 | collapse_pos(0, i + 1); |
750 | break; |
751 | } |
752 | } |
753 | /* skip first not quoted '\'' or '"' */ |
754 | for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++) |
755 | /*skip*/; |
756 | /* collapse quote or unquote // or /~ */ |
757 | while ((int_buf[i] & ~QUOT) == '/' |
758 | && ((int_buf[i+1] & ~QUOT) == '/' || (int_buf[i+1] & ~QUOT) == '~') |
759 | ) { |
760 | i++; |
761 | } |
762 | |
763 | /* set only match and destroy quotes */ |
764 | j = 0; |
765 | for (c = 0; pos_buf[i] >= 0; i++) { |
766 | matchBuf[c++] = matchBuf[pos_buf[i]]; |
767 | j = pos_buf[i] + 1; |
768 | } |
769 | matchBuf[c] = '\0'; |
770 | /* old length matchBuf with quotes symbols */ |
771 | *len_with_quotes = j ? j - pos_buf[0] : 0; |
772 | |
773 | return command_mode; |
774 | #undef int_buf |
775 | #undef pos_buf |
776 | } |
777 | |
778 | /* |
779 | * display by column (original idea from ls applet, |
780 | * very optimized by me :) |
781 | */ |
782 | static void showfiles(void) |
783 | { |
784 | int ncols, row; |
785 | int column_width = 0; |
786 | int nfiles = num_matches; |
787 | int nrows = nfiles; |
788 | int l; |
789 | |
790 | /* find the longest file name- use that as the column width */ |
791 | for (row = 0; row < nrows; row++) { |
792 | l = strlen(matches[row]); |
793 | if (column_width < l) |
794 | column_width = l; |
795 | } |
796 | column_width += 2; /* min space for columns */ |
797 | ncols = cmdedit_termw / column_width; |
798 | |
799 | if (ncols > 1) { |
800 | nrows /= ncols; |
801 | if (nfiles % ncols) |
802 | nrows++; /* round up fractionals */ |
803 | } else { |
804 | ncols = 1; |
805 | } |
806 | for (row = 0; row < nrows; row++) { |
807 | int n = row; |
808 | int nc; |
809 | |
810 | for (nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) { |
811 | printf("%s%-*s", matches[n], |
812 | (int)(column_width - strlen(matches[n])), ""); |
813 | } |
814 | puts(matches[n]); |
815 | } |
816 | } |
817 | |
818 | static char *add_quote_for_spec_chars(char *found) |
819 | { |
820 | int l = 0; |
821 | char *s = xmalloc((strlen(found) + 1) * 2); |
822 | |
823 | while (*found) { |
824 | if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found)) |
825 | s[l++] = '\\'; |
826 | s[l++] = *found++; |
827 | } |
828 | s[l] = 0; |
829 | return s; |
830 | } |
831 | |
832 | /* Do TAB completion */ |
833 | static void input_tab(smallint *lastWasTab) |
834 | { |
835 | if (!(state->flags & TAB_COMPLETION)) |
836 | return; |
837 | |
838 | if (!*lastWasTab) { |
839 | char *tmp, *tmp1; |
840 | size_t len_found; |
841 | /* char matchBuf[MAX_LINELEN]; */ |
842 | #define matchBuf (S.input_tab__matchBuf) |
843 | int find_type; |
844 | int recalc_pos; |
845 | |
846 | *lastWasTab = TRUE; /* flop trigger */ |
847 | |
848 | /* Make a local copy of the string -- up |
849 | * to the position of the cursor */ |
850 | tmp = strncpy(matchBuf, command_ps, cursor); |
851 | tmp[cursor] = '\0'; |
852 | |
853 | find_type = find_match(matchBuf, &recalc_pos); |
854 | |
855 | /* Free up any memory already allocated */ |
856 | free_tab_completion_data(); |
857 | |
858 | #if ENABLE_FEATURE_USERNAME_COMPLETION |
859 | /* If the word starts with `~' and there is no slash in the word, |
860 | * then try completing this word as a username. */ |
861 | if (state->flags & USERNAME_COMPLETION) |
862 | if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0) |
863 | username_tab_completion(matchBuf, NULL); |
864 | #endif |
865 | /* Try to match any executable in our path and everything |
866 | * in the current working directory */ |
867 | if (!matches) |
868 | exe_n_cwd_tab_completion(matchBuf, find_type); |
869 | /* Sort, then remove any duplicates found */ |
870 | if (matches) { |
871 | unsigned i; |
872 | int n = 0; |
873 | qsort_string_vector(matches, num_matches); |
874 | for (i = 0; i < num_matches - 1; ++i) { |
875 | if (matches[i] && matches[i+1]) { /* paranoia */ |
876 | if (strcmp(matches[i], matches[i+1]) == 0) { |
877 | free(matches[i]); |
878 | matches[i] = NULL; /* paranoia */ |
879 | } else { |
880 | matches[n++] = matches[i]; |
881 | } |
882 | } |
883 | } |
884 | matches[n] = matches[i]; |
885 | num_matches = n + 1; |
886 | } |
887 | /* Did we find exactly one match? */ |
888 | if (!matches || num_matches > 1) { |
889 | beep(); |
890 | if (!matches) |
891 | return; /* not found */ |
892 | /* find minimal match */ |
893 | tmp1 = xstrdup(matches[0]); |
894 | for (tmp = tmp1; *tmp; tmp++) |
895 | for (len_found = 1; len_found < num_matches; len_found++) |
896 | if (matches[len_found][(tmp - tmp1)] != *tmp) { |
897 | *tmp = '\0'; |
898 | break; |
899 | } |
900 | if (*tmp1 == '\0') { /* have unique */ |
901 | free(tmp1); |
902 | return; |
903 | } |
904 | tmp = add_quote_for_spec_chars(tmp1); |
905 | free(tmp1); |
906 | } else { /* one match */ |
907 | tmp = add_quote_for_spec_chars(matches[0]); |
908 | /* for next completion current found */ |
909 | *lastWasTab = FALSE; |
910 | |
911 | len_found = strlen(tmp); |
912 | if (tmp[len_found-1] != '/') { |
913 | tmp[len_found] = ' '; |
914 | tmp[len_found+1] = '\0'; |
915 | } |
916 | } |
917 | len_found = strlen(tmp); |
918 | /* have space to placed match? */ |
919 | if ((len_found - strlen(matchBuf) + command_len) < MAX_LINELEN) { |
920 | /* before word for match */ |
921 | command_ps[cursor - recalc_pos] = '\0'; |
922 | /* save tail line */ |
923 | strcpy(matchBuf, command_ps + cursor); |
924 | /* add match */ |
925 | strcat(command_ps, tmp); |
926 | /* add tail */ |
927 | strcat(command_ps, matchBuf); |
928 | /* back to begin word for match */ |
929 | input_backward(recalc_pos); |
930 | /* new pos */ |
931 | recalc_pos = cursor + len_found; |
932 | /* new len */ |
933 | command_len = strlen(command_ps); |
934 | /* write out the matched command */ |
935 | redraw(cmdedit_y, command_len - recalc_pos); |
936 | } |
937 | free(tmp); |
938 | #undef matchBuf |
939 | } else { |
940 | /* Ok -- the last char was a TAB. Since they |
941 | * just hit TAB again, print a list of all the |
942 | * available choices... */ |
943 | if (matches && num_matches > 0) { |
944 | int sav_cursor = cursor; /* change goto_new_line() */ |
945 | |
946 | /* Go to the next line */ |
947 | goto_new_line(); |
948 | showfiles(); |
949 | redraw(0, command_len - sav_cursor); |
950 | } |
951 | } |
952 | } |
953 | |
954 | #endif /* FEATURE_COMMAND_TAB_COMPLETION */ |
955 | |
956 | |
957 | #if MAX_HISTORY > 0 |
958 | |
959 | static void save_command_ps_at_cur_history(void) |
960 | { |
961 | if (command_ps[0] != '\0') { |
962 | int cur = state->cur_history; |
963 | free(state->history[cur]); |
964 | state->history[cur] = xstrdup(command_ps); |
965 | } |
966 | } |
967 | |
968 | /* state->flags is already checked to be nonzero */ |
969 | static int get_previous_history(void) |
970 | { |
971 | if ((state->flags & DO_HISTORY) && state->cur_history) { |
972 | save_command_ps_at_cur_history(); |
973 | state->cur_history--; |
974 | return 1; |
975 | } |
976 | beep(); |
977 | return 0; |
978 | } |
979 | |
980 | static int get_next_history(void) |
981 | { |
982 | if (state->flags & DO_HISTORY) { |
983 | if (state->cur_history < state->cnt_history) { |
984 | save_command_ps_at_cur_history(); /* save the current history line */ |
985 | return ++state->cur_history; |
986 | } |
987 | } |
988 | beep(); |
989 | return 0; |
990 | } |
991 | |
992 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
993 | /* state->flags is already checked to be nonzero */ |
994 | static void load_history(const char *fromfile) |
995 | { |
996 | FILE *fp; |
997 | int hi; |
998 | |
999 | /* NB: do not trash old history if file can't be opened */ |
1000 | |
1001 | fp = fopen_for_read(fromfile); |
1002 | if (fp) { |
1003 | /* clean up old history */ |
1004 | for (hi = state->cnt_history; hi > 0;) { |
1005 | hi--; |
1006 | free(state->history[hi]); |
1007 | state->history[hi] = NULL; |
1008 | } |
1009 | |
1010 | for (hi = 0; hi < MAX_HISTORY;) { |
1011 | char *hl = xmalloc_fgetline(fp); |
1012 | int l; |
1013 | |
1014 | if (!hl) |
1015 | break; |
1016 | l = strlen(hl); |
1017 | if (l >= MAX_LINELEN) |
1018 | hl[MAX_LINELEN-1] = '\0'; |
1019 | if (l == 0) { |
1020 | free(hl); |
1021 | continue; |
1022 | } |
1023 | state->history[hi++] = hl; |
1024 | } |
1025 | fclose(fp); |
1026 | state->cnt_history = hi; |
1027 | } |
1028 | } |
1029 | |
1030 | /* state->flags is already checked to be nonzero */ |
1031 | static void save_history(const char *tofile) |
1032 | { |
1033 | FILE *fp; |
1034 | |
1035 | fp = fopen_for_write(tofile); |
1036 | if (fp) { |
1037 | int i; |
1038 | |
1039 | for (i = 0; i < state->cnt_history; i++) { |
1040 | fprintf(fp, "%s\n", state->history[i]); |
1041 | } |
1042 | fclose(fp); |
1043 | } |
1044 | } |
1045 | #else |
1046 | #define load_history(a) ((void)0) |
1047 | #define save_history(a) ((void)0) |
1048 | #endif /* FEATURE_COMMAND_SAVEHISTORY */ |
1049 | |
1050 | static void remember_in_history(const char *str) |
1051 | { |
1052 | int i; |
1053 | |
1054 | if (!(state->flags & DO_HISTORY)) |
1055 | return; |
1056 | if (str[0] == '\0') |
1057 | return; |
1058 | i = state->cnt_history; |
1059 | /* Don't save dupes */ |
1060 | if (i && strcmp(state->history[i-1], str) == 0) |
1061 | return; |
1062 | |
1063 | free(state->history[MAX_HISTORY]); /* redundant, paranoia */ |
1064 | state->history[MAX_HISTORY] = NULL; /* redundant, paranoia */ |
1065 | |
1066 | /* If history[] is full, remove the oldest command */ |
1067 | /* we need to keep history[MAX_HISTORY] empty, hence >=, not > */ |
1068 | if (i >= MAX_HISTORY) { |
1069 | free(state->history[0]); |
1070 | for (i = 0; i < MAX_HISTORY-1; i++) |
1071 | state->history[i] = state->history[i+1]; |
1072 | /* i == MAX_HISTORY-1 */ |
1073 | } |
1074 | /* i <= MAX_HISTORY-1 */ |
1075 | state->history[i++] = xstrdup(str); |
1076 | /* i <= MAX_HISTORY */ |
1077 | state->cur_history = i; |
1078 | state->cnt_history = i; |
1079 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
1080 | if ((state->flags & SAVE_HISTORY) && state->hist_file) |
1081 | save_history(state->hist_file); |
1082 | #endif |
1083 | USE_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;) |
1084 | } |
1085 | |
1086 | #else /* MAX_HISTORY == 0 */ |
1087 | #define remember_in_history(a) ((void)0) |
1088 | #endif /* MAX_HISTORY */ |
1089 | |
1090 | |
1091 | /* |
1092 | * This function is used to grab a character buffer |
1093 | * from the input file descriptor and allows you to |
1094 | * a string with full command editing (sort of like |
1095 | * a mini readline). |
1096 | * |
1097 | * The following standard commands are not implemented: |
1098 | * ESC-b -- Move back one word |
1099 | * ESC-f -- Move forward one word |
1100 | * ESC-d -- Delete back one word |
1101 | * ESC-h -- Delete forward one word |
1102 | * CTL-t -- Transpose two characters |
1103 | * |
1104 | * Minimalist vi-style command line editing available if configured. |
1105 | * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us> |
1106 | */ |
1107 | |
1108 | #if ENABLE_FEATURE_EDITING_VI |
1109 | static void |
1110 | vi_Word_motion(char *command, int eat) |
1111 | { |
1112 | while (cursor < command_len && !isspace(command[cursor])) |
1113 | input_forward(); |
1114 | if (eat) while (cursor < command_len && isspace(command[cursor])) |
1115 | input_forward(); |
1116 | } |
1117 | |
1118 | static void |
1119 | vi_word_motion(char *command, int eat) |
1120 | { |
1121 | if (isalnum(command[cursor]) || command[cursor] == '_') { |
1122 | while (cursor < command_len |
1123 | && (isalnum(command[cursor+1]) || command[cursor+1] == '_')) |
1124 | input_forward(); |
1125 | } else if (ispunct(command[cursor])) { |
1126 | while (cursor < command_len && ispunct(command[cursor+1])) |
1127 | input_forward(); |
1128 | } |
1129 | |
1130 | if (cursor < command_len) |
1131 | input_forward(); |
1132 | |
1133 | if (eat && cursor < command_len && isspace(command[cursor])) |
1134 | while (cursor < command_len && isspace(command[cursor])) |
1135 | input_forward(); |
1136 | } |
1137 | |
1138 | static void |
1139 | vi_End_motion(char *command) |
1140 | { |
1141 | input_forward(); |
1142 | while (cursor < command_len && isspace(command[cursor])) |
1143 | input_forward(); |
1144 | while (cursor < command_len-1 && !isspace(command[cursor+1])) |
1145 | input_forward(); |
1146 | } |
1147 | |
1148 | static void |
1149 | vi_end_motion(char *command) |
1150 | { |
1151 | if (cursor >= command_len-1) |
1152 | return; |
1153 | input_forward(); |
1154 | while (cursor < command_len-1 && isspace(command[cursor])) |
1155 | input_forward(); |
1156 | if (cursor >= command_len-1) |
1157 | return; |
1158 | if (isalnum(command[cursor]) || command[cursor] == '_') { |
1159 | while (cursor < command_len-1 |
1160 | && (isalnum(command[cursor+1]) || command[cursor+1] == '_') |
1161 | ) { |
1162 | input_forward(); |
1163 | } |
1164 | } else if (ispunct(command[cursor])) { |
1165 | while (cursor < command_len-1 && ispunct(command[cursor+1])) |
1166 | input_forward(); |
1167 | } |
1168 | } |
1169 | |
1170 | static void |
1171 | vi_Back_motion(char *command) |
1172 | { |
1173 | while (cursor > 0 && isspace(command[cursor-1])) |
1174 | input_backward(1); |
1175 | while (cursor > 0 && !isspace(command[cursor-1])) |
1176 | input_backward(1); |
1177 | } |
1178 | |
1179 | static void |
1180 | vi_back_motion(char *command) |
1181 | { |
1182 | if (cursor <= 0) |
1183 | return; |
1184 | input_backward(1); |
1185 | while (cursor > 0 && isspace(command[cursor])) |
1186 | input_backward(1); |
1187 | if (cursor <= 0) |
1188 | return; |
1189 | if (isalnum(command[cursor]) || command[cursor] == '_') { |
1190 | while (cursor > 0 |
1191 | && (isalnum(command[cursor-1]) || command[cursor-1] == '_') |
1192 | ) { |
1193 | input_backward(1); |
1194 | } |
1195 | } else if (ispunct(command[cursor])) { |
1196 | while (cursor > 0 && ispunct(command[cursor-1])) |
1197 | input_backward(1); |
1198 | } |
1199 | } |
1200 | #endif |
1201 | |
1202 | |
1203 | /* |
1204 | * read_line_input and its helpers |
1205 | */ |
1206 | |
1207 | #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT |
1208 | static void parse_and_put_prompt(const char *prmt_ptr) |
1209 | { |
1210 | cmdedit_prompt = prmt_ptr; |
1211 | cmdedit_prmt_len = strlen(prmt_ptr); |
1212 | put_prompt(); |
1213 | } |
1214 | #else |
1215 | static void parse_and_put_prompt(const char *prmt_ptr) |
1216 | { |
1217 | int prmt_len = 0; |
1218 | size_t cur_prmt_len = 0; |
1219 | char flg_not_length = '['; |
1220 | char *prmt_mem_ptr = xzalloc(1); |
1221 | char *cwd_buf = xrealloc_getcwd_or_warn(NULL); |
1222 | char cbuf[2]; |
1223 | char c; |
1224 | char *pbuf; |
1225 | |
1226 | cmdedit_prmt_len = 0; |
1227 | |
1228 | if (!cwd_buf) { |
1229 | cwd_buf = (char *)bb_msg_unknown; |
1230 | } |
1231 | |
1232 | cbuf[1] = '\0'; /* never changes */ |
1233 | |
1234 | while (*prmt_ptr) { |
1235 | char *free_me = NULL; |
1236 | |
1237 | pbuf = cbuf; |
1238 | c = *prmt_ptr++; |
1239 | if (c == '\\') { |
1240 | const char *cp = prmt_ptr; |
1241 | int l; |
1242 | |
1243 | c = bb_process_escape_sequence(&prmt_ptr); |
1244 | if (prmt_ptr == cp) { |
1245 | if (*cp == '\0') |
1246 | break; |
1247 | c = *prmt_ptr++; |
1248 | |
1249 | switch (c) { |
1250 | #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR |
1251 | case 'u': |
1252 | pbuf = user_buf ? user_buf : (char*)""; |
1253 | break; |
1254 | #endif |
1255 | case 'h': |
1256 | pbuf = free_me = safe_gethostname(); |
1257 | *strchrnul(pbuf, '.') = '\0'; |
1258 | break; |
1259 | case '$': |
1260 | c = (geteuid() == 0 ? '#' : '$'); |
1261 | break; |
1262 | #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR |
1263 | case 'w': |
1264 | /* /home/user[/something] -> ~[/something] */ |
1265 | pbuf = cwd_buf; |
1266 | l = strlen(home_pwd_buf); |
1267 | if (l != 0 |
1268 | && strncmp(home_pwd_buf, cwd_buf, l) == 0 |
1269 | && (cwd_buf[l]=='/' || cwd_buf[l]=='\0') |
1270 | && strlen(cwd_buf + l) < PATH_MAX |
1271 | ) { |
1272 | pbuf = free_me = xasprintf("~%s", cwd_buf + l); |
1273 | } |
1274 | break; |
1275 | #endif |
1276 | case 'W': |
1277 | pbuf = cwd_buf; |
1278 | cp = strrchr(pbuf, '/'); |
1279 | if (cp != NULL && cp != pbuf) |
1280 | pbuf += (cp-pbuf) + 1; |
1281 | break; |
1282 | case '!': |
1283 | pbuf = free_me = xasprintf("%d", num_ok_lines); |
1284 | break; |
1285 | case 'e': case 'E': /* \e \E = \033 */ |
1286 | c = '\033'; |
1287 | break; |
1288 | case 'x': case 'X': { |
1289 | char buf2[4]; |
1290 | for (l = 0; l < 3;) { |
1291 | unsigned h; |
1292 | buf2[l++] = *prmt_ptr; |
1293 | buf2[l] = '\0'; |
1294 | h = strtoul(buf2, &pbuf, 16); |
1295 | if (h > UCHAR_MAX || (pbuf - buf2) < l) { |
1296 | buf2[--l] = '\0'; |
1297 | break; |
1298 | } |
1299 | prmt_ptr++; |
1300 | } |
1301 | c = (char)strtoul(buf2, NULL, 16); |
1302 | if (c == 0) |
1303 | c = '?'; |
1304 | pbuf = cbuf; |
1305 | break; |
1306 | } |
1307 | case '[': case ']': |
1308 | if (c == flg_not_length) { |
1309 | flg_not_length = (flg_not_length == '[' ? ']' : '['); |
1310 | continue; |
1311 | } |
1312 | break; |
1313 | } /* switch */ |
1314 | } /* if */ |
1315 | } /* if */ |
1316 | cbuf[0] = c; |
1317 | cur_prmt_len = strlen(pbuf); |
1318 | prmt_len += cur_prmt_len; |
1319 | if (flg_not_length != ']') |
1320 | cmdedit_prmt_len += cur_prmt_len; |
1321 | prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf); |
1322 | free(free_me); |
1323 | } /* while */ |
1324 | |
1325 | if (cwd_buf != (char *)bb_msg_unknown) |
1326 | free(cwd_buf); |
1327 | cmdedit_prompt = prmt_mem_ptr; |
1328 | put_prompt(); |
1329 | } |
1330 | #endif |
1331 | |
1332 | static void cmdedit_setwidth(unsigned w, int redraw_flg) |
1333 | { |
1334 | cmdedit_termw = w; |
1335 | if (redraw_flg) { |
1336 | /* new y for current cursor */ |
1337 | int new_y = (cursor + cmdedit_prmt_len) / w; |
1338 | /* redraw */ |
1339 | redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor); |
1340 | fflush(stdout); |
1341 | } |
1342 | } |
1343 | |
1344 | static void win_changed(int nsig) |
1345 | { |
1346 | unsigned width; |
1347 | get_terminal_width_height(0, &width, NULL); |
1348 | cmdedit_setwidth(width, nsig /* - just a yes/no flag */); |
1349 | if (nsig == SIGWINCH) |
1350 | signal(SIGWINCH, win_changed); /* rearm ourself */ |
1351 | } |
1352 | |
1353 | /* |
1354 | * The emacs and vi modes share much of the code in the big |
1355 | * command loop. Commands entered when in vi's command mode (aka |
1356 | * "escape mode") get an extra bit added to distinguish them -- |
1357 | * this keeps them from being self-inserted. This clutters the |
1358 | * big switch a bit, but keeps all the code in one place. |
1359 | */ |
1360 | |
1361 | #define vbit 0x100 |
1362 | |
1363 | /* leave out the "vi-mode"-only case labels if vi editing isn't |
1364 | * configured. */ |
1365 | #define vi_case(caselabel) USE_FEATURE_EDITING(case caselabel) |
1366 | |
1367 | /* convert uppercase ascii to equivalent control char, for readability */ |
1368 | #undef CTRL |
1369 | #define CTRL(a) ((a) & ~0x40) |
1370 | |
1371 | /* Returns: |
1372 | * -1 on read errors or EOF, or on bare Ctrl-D, |
1373 | * 0 on ctrl-C (the line entered is still returned in 'command'), |
1374 | * >0 length of input string, including terminating '\n' |
1375 | */ |
1376 | int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, line_input_t *st) |
1377 | { |
1378 | int len; |
1379 | #if ENABLE_FEATURE_TAB_COMPLETION |
1380 | smallint lastWasTab = FALSE; |
1381 | #endif |
1382 | unsigned ic; |
1383 | unsigned char c; |
1384 | smallint break_out = 0; |
1385 | #if ENABLE_FEATURE_EDITING_VI |
1386 | smallint vi_cmdmode = 0; |
1387 | smalluint prevc; |
1388 | #endif |
1389 | struct termios initial_settings; |
1390 | struct termios new_settings; |
1391 | |
1392 | INIT_S(); |
1393 | |
1394 | if (tcgetattr(STDIN_FILENO, &initial_settings) < 0 |
1395 | || !(initial_settings.c_lflag & ECHO) |
1396 | ) { |
1397 | /* Happens when e.g. stty -echo was run before */ |
1398 | parse_and_put_prompt(prompt); |
1399 | fflush(stdout); |
1400 | if (fgets(command, maxsize, stdin) == NULL) |
1401 | len = -1; /* EOF or error */ |
1402 | else |
1403 | len = strlen(command); |
1404 | DEINIT_S(); |
1405 | return len; |
1406 | } |
1407 | |
1408 | // FIXME: audit & improve this |
1409 | if (maxsize > MAX_LINELEN) |
1410 | maxsize = MAX_LINELEN; |
1411 | |
1412 | /* With null flags, no other fields are ever used */ |
1413 | state = st ? st : (line_input_t*) &const_int_0; |
1414 | #if ENABLE_FEATURE_EDITING_SAVEHISTORY |
1415 | if ((state->flags & SAVE_HISTORY) && state->hist_file) |
1416 | load_history(state->hist_file); |
1417 | #endif |
1418 | #if MAX_HISTORY > 0 |
1419 | if (state->flags & DO_HISTORY) |
1420 | state->cur_history = state->cnt_history; |
1421 | #endif |
1422 | |
1423 | /* prepare before init handlers */ |
1424 | cmdedit_y = 0; /* quasireal y, not true if line > xt*yt */ |
1425 | command_len = 0; |
1426 | command_ps = command; |
1427 | command[0] = '\0'; |
1428 | |
1429 | new_settings = initial_settings; |
1430 | new_settings.c_lflag &= ~ICANON; /* unbuffered input */ |
1431 | /* Turn off echoing and CTRL-C, so we can trap it */ |
1432 | new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG); |
1433 | /* Hmm, in linux c_cc[] is not parsed if ICANON is off */ |
1434 | new_settings.c_cc[VMIN] = 1; |
1435 | new_settings.c_cc[VTIME] = 0; |
1436 | /* Turn off CTRL-C, so we can trap it */ |
1437 | #ifndef _POSIX_VDISABLE |
1438 | #define _POSIX_VDISABLE '\0' |
1439 | #endif |
1440 | new_settings.c_cc[VINTR] = _POSIX_VDISABLE; |
1441 | tcsetattr_stdin_TCSANOW(&new_settings); |
1442 | |
1443 | /* Now initialize things */ |
1444 | previous_SIGWINCH_handler = signal(SIGWINCH, win_changed); |
1445 | win_changed(0); /* do initial resizing */ |
1446 | #if ENABLE_FEATURE_GETUSERNAME_AND_HOMEDIR |
1447 | { |
1448 | struct passwd *entry; |
1449 | |
1450 | entry = getpwuid(geteuid()); |
1451 | if (entry) { |
1452 | user_buf = xstrdup(entry->pw_name); |
1453 | home_pwd_buf = xstrdup(entry->pw_dir); |
1454 | } |
1455 | } |
1456 | #endif |
1457 | |
1458 | #if 0 |
1459 | for (ic = 0; ic <= MAX_HISTORY; ic++) |
1460 | bb_error_msg("history[%d]:'%s'", ic, state->history[ic]); |
1461 | bb_error_msg("cur_history:%d cnt_history:%d", state->cur_history, state->cnt_history); |
1462 | #endif |
1463 | |
1464 | /* Print out the command prompt */ |
1465 | parse_and_put_prompt(prompt); |
1466 | |
1467 | while (1) { |
1468 | fflush(NULL); |
1469 | |
1470 | if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) { |
1471 | /* if we can't read input then exit */ |
1472 | goto prepare_to_die; |
1473 | } |
1474 | |
1475 | ic = c; |
1476 | |
1477 | #if ENABLE_FEATURE_EDITING_VI |
1478 | newdelflag = 1; |
1479 | if (vi_cmdmode) |
1480 | ic |= vbit; |
1481 | #endif |
1482 | switch (ic) { |
1483 | case '\n': |
1484 | case '\r': |
1485 | vi_case('\n'|vbit:) |
1486 | vi_case('\r'|vbit:) |
1487 | /* Enter */ |
1488 | goto_new_line(); |
1489 | break_out = 1; |
1490 | break; |
1491 | case CTRL('A'): |
1492 | vi_case('0'|vbit:) |
1493 | /* Control-a -- Beginning of line */ |
1494 | input_backward(cursor); |
1495 | break; |
1496 | case CTRL('B'): |
1497 | vi_case('h'|vbit:) |
1498 | vi_case('\b'|vbit:) |
1499 | vi_case('\x7f'|vbit:) /* DEL */ |
1500 | /* Control-b -- Move back one character */ |
1501 | input_backward(1); |
1502 | break; |
1503 | case CTRL('C'): |
1504 | vi_case(CTRL('C')|vbit:) |
1505 | /* Control-c -- stop gathering input */ |
1506 | goto_new_line(); |
1507 | command_len = 0; |
1508 | break_out = -1; /* "do not append '\n'" */ |
1509 | break; |
1510 | case CTRL('D'): |
1511 | /* Control-d -- Delete one character, or exit |
1512 | * if the len=0 and no chars to delete */ |
1513 | if (command_len == 0) { |
1514 | errno = 0; |
1515 | prepare_to_die: |
1516 | /* to control stopped jobs */ |
1517 | break_out = command_len = -1; |
1518 | break; |
1519 | } |
1520 | input_delete(0); |
1521 | break; |
1522 | |
1523 | case CTRL('E'): |
1524 | vi_case('$'|vbit:) |
1525 | /* Control-e -- End of line */ |
1526 | input_end(); |
1527 | break; |
1528 | case CTRL('F'): |
1529 | vi_case('l'|vbit:) |
1530 | vi_case(' '|vbit:) |
1531 | /* Control-f -- Move forward one character */ |
1532 | input_forward(); |
1533 | break; |
1534 | |
1535 | case '\b': |
1536 | case '\x7f': /* DEL */ |
1537 | /* Control-h and DEL */ |
1538 | input_backspace(); |
1539 | break; |
1540 | |
1541 | #if ENABLE_FEATURE_TAB_COMPLETION |
1542 | case '\t': |
1543 | input_tab(&lastWasTab); |
1544 | break; |
1545 | #endif |
1546 | |
1547 | case CTRL('K'): |
1548 | /* Control-k -- clear to end of line */ |
1549 | command[cursor] = 0; |
1550 | command_len = cursor; |
1551 | printf("\033[J"); |
1552 | break; |
1553 | case CTRL('L'): |
1554 | vi_case(CTRL('L')|vbit:) |
1555 | /* Control-l -- clear screen */ |
1556 | printf("\033[H"); |
1557 | redraw(0, command_len - cursor); |
1558 | break; |
1559 | |
1560 | #if MAX_HISTORY > 0 |
1561 | case CTRL('N'): |
1562 | vi_case(CTRL('N')|vbit:) |
1563 | vi_case('j'|vbit:) |
1564 | /* Control-n -- Get next command in history */ |
1565 | if (get_next_history()) |
1566 | goto rewrite_line; |
1567 | break; |
1568 | case CTRL('P'): |
1569 | vi_case(CTRL('P')|vbit:) |
1570 | vi_case('k'|vbit:) |
1571 | /* Control-p -- Get previous command from history */ |
1572 | if (get_previous_history()) |
1573 | goto rewrite_line; |
1574 | break; |
1575 | #endif |
1576 | |
1577 | case CTRL('U'): |
1578 | vi_case(CTRL('U')|vbit:) |
1579 | /* Control-U -- Clear line before cursor */ |
1580 | if (cursor) { |
1581 | overlapping_strcpy(command, command + cursor); |
1582 | command_len -= cursor; |
1583 | redraw(cmdedit_y, command_len); |
1584 | } |
1585 | break; |
1586 | case CTRL('W'): |
1587 | vi_case(CTRL('W')|vbit:) |
1588 | /* Control-W -- Remove the last word */ |
1589 | while (cursor > 0 && isspace(command[cursor-1])) |
1590 | input_backspace(); |
1591 | while (cursor > 0 && !isspace(command[cursor-1])) |
1592 | input_backspace(); |
1593 | break; |
1594 | |
1595 | #if ENABLE_FEATURE_EDITING_VI |
1596 | case 'i'|vbit: |
1597 | vi_cmdmode = 0; |
1598 | break; |
1599 | case 'I'|vbit: |
1600 | input_backward(cursor); |
1601 | vi_cmdmode = 0; |
1602 | break; |
1603 | case 'a'|vbit: |
1604 | input_forward(); |
1605 | vi_cmdmode = 0; |
1606 | break; |
1607 | case 'A'|vbit: |
1608 | input_end(); |
1609 | vi_cmdmode = 0; |
1610 | break; |
1611 | case 'x'|vbit: |
1612 | input_delete(1); |
1613 | break; |
1614 | case 'X'|vbit: |
1615 | if (cursor > 0) { |
1616 | input_backward(1); |
1617 | input_delete(1); |
1618 | } |
1619 | break; |
1620 | case 'W'|vbit: |
1621 | vi_Word_motion(command, 1); |
1622 | break; |
1623 | case 'w'|vbit: |
1624 | vi_word_motion(command, 1); |
1625 | break; |
1626 | case 'E'|vbit: |
1627 | vi_End_motion(command); |
1628 | break; |
1629 | case 'e'|vbit: |
1630 | vi_end_motion(command); |
1631 | break; |
1632 | case 'B'|vbit: |
1633 | vi_Back_motion(command); |
1634 | break; |
1635 | case 'b'|vbit: |
1636 | vi_back_motion(command); |
1637 | break; |
1638 | case 'C'|vbit: |
1639 | vi_cmdmode = 0; |
1640 | /* fall through */ |
1641 | case 'D'|vbit: |
1642 | goto clear_to_eol; |
1643 | |
1644 | case 'c'|vbit: |
1645 | vi_cmdmode = 0; |
1646 | /* fall through */ |
1647 | case 'd'|vbit: { |
1648 | int nc, sc; |
1649 | sc = cursor; |
1650 | prevc = ic; |
1651 | if (safe_read(STDIN_FILENO, &c, 1) < 1) |
1652 | goto prepare_to_die; |
1653 | if (c == (prevc & 0xff)) { |
1654 | /* "cc", "dd" */ |
1655 | input_backward(cursor); |
1656 | goto clear_to_eol; |
1657 | break; |
1658 | } |
1659 | switch (c) { |
1660 | case 'w': |
1661 | case 'W': |
1662 | case 'e': |
1663 | case 'E': |
1664 | switch (c) { |
1665 | case 'w': /* "dw", "cw" */ |
1666 | vi_word_motion(command, vi_cmdmode); |
1667 | break; |
1668 | case 'W': /* 'dW', 'cW' */ |
1669 | vi_Word_motion(command, vi_cmdmode); |
1670 | break; |
1671 | case 'e': /* 'de', 'ce' */ |
1672 | vi_end_motion(command); |
1673 | input_forward(); |
1674 | break; |
1675 | case 'E': /* 'dE', 'cE' */ |
1676 | vi_End_motion(command); |
1677 | input_forward(); |
1678 | break; |
1679 | } |
1680 | nc = cursor; |
1681 | input_backward(cursor - sc); |
1682 | while (nc-- > cursor) |
1683 | input_delete(1); |
1684 | break; |
1685 | case 'b': /* "db", "cb" */ |
1686 | case 'B': /* implemented as B */ |
1687 | if (c == 'b') |
1688 | vi_back_motion(command); |
1689 | else |
1690 | vi_Back_motion(command); |
1691 | while (sc-- > cursor) |
1692 | input_delete(1); |
1693 | break; |
1694 | case ' ': /* "d ", "c " */ |
1695 | input_delete(1); |
1696 | break; |
1697 | case '$': /* "d$", "c$" */ |
1698 | clear_to_eol: |
1699 | while (cursor < command_len) |
1700 | input_delete(1); |
1701 | break; |
1702 | } |
1703 | break; |
1704 | } |
1705 | case 'p'|vbit: |
1706 | input_forward(); |
1707 | /* fallthrough */ |
1708 | case 'P'|vbit: |
1709 | put(); |
1710 | break; |
1711 | case 'r'|vbit: |
1712 | if (safe_read(STDIN_FILENO, &c, 1) < 1) |
1713 | goto prepare_to_die; |
1714 | if (c == 0) |
1715 | beep(); |
1716 | else { |
1717 | *(command + cursor) = c; |
1718 | bb_putchar(c); |
1719 | bb_putchar('\b'); |
1720 | } |
1721 | break; |
1722 | #endif /* FEATURE_COMMAND_EDITING_VI */ |
1723 | |
1724 | case '\x1b': /* ESC */ |
1725 | |
1726 | #if ENABLE_FEATURE_EDITING_VI |
1727 | if (state->flags & VI_MODE) { |
1728 | /* ESC: insert mode --> command mode */ |
1729 | vi_cmdmode = 1; |
1730 | input_backward(1); |
1731 | break; |
1732 | } |
1733 | #endif |
1734 | /* escape sequence follows */ |
1735 | if (safe_read(STDIN_FILENO, &c, 1) < 1) |
1736 | goto prepare_to_die; |
1737 | /* different vt100 emulations */ |
1738 | if (c == '[' || c == 'O') { |
1739 | vi_case('['|vbit:) |
1740 | vi_case('O'|vbit:) |
1741 | if (safe_read(STDIN_FILENO, &c, 1) < 1) |
1742 | goto prepare_to_die; |
1743 | } |
1744 | if (c >= '1' && c <= '9') { |
1745 | unsigned char dummy; |
1746 | |
1747 | if (safe_read(STDIN_FILENO, &dummy, 1) < 1) |
1748 | goto prepare_to_die; |
1749 | if (dummy != '~') |
1750 | c = '\0'; |
1751 | } |
1752 | |
1753 | switch (c) { |
1754 | #if ENABLE_FEATURE_TAB_COMPLETION |
1755 | case '\t': /* Alt-Tab */ |
1756 | input_tab(&lastWasTab); |
1757 | break; |
1758 | #endif |
1759 | #if MAX_HISTORY > 0 |
1760 | case 'A': |
1761 | /* Up Arrow -- Get previous command from history */ |
1762 | if (get_previous_history()) |
1763 | goto rewrite_line; |
1764 | beep(); |
1765 | break; |
1766 | case 'B': |
1767 | /* Down Arrow -- Get next command in history */ |
1768 | if (!get_next_history()) |
1769 | break; |
1770 | rewrite_line: |
1771 | /* Rewrite the line with the selected history item */ |
1772 | /* change command */ |
1773 | command_len = strlen(strcpy(command, state->history[state->cur_history] ? : "")); |
1774 | /* redraw and go to eol (bol, in vi */ |
1775 | redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0); |
1776 | break; |
1777 | #endif |
1778 | case 'C': |
1779 | /* Right Arrow -- Move forward one character */ |
1780 | input_forward(); |
1781 | break; |
1782 | case 'D': |
1783 | /* Left Arrow -- Move back one character */ |
1784 | input_backward(1); |
1785 | break; |
1786 | case '3': |
1787 | /* Delete */ |
1788 | input_delete(0); |
1789 | break; |
1790 | case '1': // vt100? linux vt? or what? |
1791 | case '7': // vt100? linux vt? or what? |
1792 | case 'H': /* xterm's <Home> */ |
1793 | input_backward(cursor); |
1794 | break; |
1795 | case '4': // vt100? linux vt? or what? |
1796 | case '8': // vt100? linux vt? or what? |
1797 | case 'F': /* xterm's <End> */ |
1798 | input_end(); |
1799 | break; |
1800 | default: |
1801 | c = '\0'; |
1802 | beep(); |
1803 | } |
1804 | break; |
1805 | |
1806 | default: /* If it's regular input, do the normal thing */ |
1807 | |
1808 | /* Control-V -- force insert of next char */ |
1809 | if (c == CTRL('V')) { |
1810 | if (safe_read(STDIN_FILENO, &c, 1) < 1) |
1811 | goto prepare_to_die; |
1812 | if (c == 0) { |
1813 | beep(); |
1814 | break; |
1815 | } |
1816 | } |
1817 | |
1818 | #if ENABLE_FEATURE_EDITING_VI |
1819 | if (vi_cmdmode) /* Don't self-insert */ |
1820 | break; |
1821 | #endif |
1822 | if ((int)command_len >= (maxsize - 2)) /* Need to leave space for enter */ |
1823 | break; |
1824 | |
1825 | command_len++; |
1826 | if (cursor == (command_len - 1)) { /* Append if at the end of the line */ |
1827 | command[cursor] = c; |
1828 | command[cursor+1] = '\0'; |
1829 | cmdedit_set_out_char(' '); |
1830 | } else { /* Insert otherwise */ |
1831 | int sc = cursor; |
1832 | |
1833 | memmove(command + sc + 1, command + sc, command_len - sc); |
1834 | command[sc] = c; |
1835 | sc++; |
1836 | /* rewrite from cursor */ |
1837 | input_end(); |
1838 | /* to prev x pos + 1 */ |
1839 | input_backward(cursor - sc); |
1840 | } |
1841 | break; |
1842 | } |
1843 | if (break_out) /* Enter is the command terminator, no more input. */ |
1844 | break; |
1845 | |
1846 | #if ENABLE_FEATURE_TAB_COMPLETION |
1847 | if (c != '\t') |
1848 | lastWasTab = FALSE; |
1849 | #endif |
1850 | } |
1851 | |
1852 | if (command_len > 0) |
1853 | remember_in_history(command); |
1854 | |
1855 | if (break_out > 0) { |
1856 | command[command_len++] = '\n'; |
1857 | command[command_len] = '\0'; |
1858 | } |
1859 | |
1860 | #if ENABLE_FEATURE_TAB_COMPLETION |
1861 | free_tab_completion_data(); |
1862 | #endif |
1863 | |
1864 | /* restore initial_settings */ |
1865 | tcsetattr_stdin_TCSANOW(&initial_settings); |
1866 | /* restore SIGWINCH handler */ |
1867 | signal(SIGWINCH, previous_SIGWINCH_handler); |
1868 | fflush(stdout); |
1869 | |
1870 | len = command_len; |
1871 | DEINIT_S(); |
1872 | |
1873 | return len; /* can't return command_len, DEINIT_S() destroys it */ |
1874 | } |
1875 | |
1876 | line_input_t* FAST_FUNC new_line_input_t(int flags) |
1877 | { |
1878 | line_input_t *n = xzalloc(sizeof(*n)); |
1879 | n->flags = flags; |
1880 | return n; |
1881 | } |
1882 | |
1883 | #else |
1884 | |
1885 | #undef read_line_input |
1886 | int FAST_FUNC read_line_input(const char* prompt, char* command, int maxsize) |
1887 | { |
1888 | fputs(prompt, stdout); |
1889 | fflush(stdout); |
1890 | fgets(command, maxsize, stdin); |
1891 | return strlen(command); |
1892 | } |
1893 | |
1894 | #endif /* FEATURE_COMMAND_EDITING */ |
1895 | |
1896 | |
1897 | /* |
1898 | * Testing |
1899 | */ |
1900 | |
1901 | #ifdef TEST |
1902 | |
1903 | #include <locale.h> |
1904 | |
1905 | const char *applet_name = "debug stuff usage"; |
1906 | |
1907 | int main(int argc, char **argv) |
1908 | { |
1909 | char buff[MAX_LINELEN]; |
1910 | char *prompt = |
1911 | #if ENABLE_FEATURE_EDITING_FANCY_PROMPT |
1912 | "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:" |
1913 | "\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] " |
1914 | "\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]"; |
1915 | #else |
1916 | "% "; |
1917 | #endif |
1918 | |
1919 | #if ENABLE_FEATURE_NONPRINTABLE_INVERSE_PUT |
1920 | setlocale(LC_ALL, ""); |
1921 | #endif |
1922 | while (1) { |
1923 | int l; |
1924 | l = read_line_input(prompt, buff); |
1925 | if (l <= 0 || buff[l-1] != '\n') |
1926 | break; |
1927 | buff[l-1] = 0; |
1928 | printf("*** read_line_input() returned line =%s=\n", buff); |
1929 | } |
1930 | printf("*** read_line_input() detect ^D\n"); |
1931 | return 0; |
1932 | } |
1933 | |
1934 | #endif /* TEST */ |