diff options
| author | Erik Andersen <andersen@codepoet.org> | 2000-03-19 05:28:55 +0000 |
|---|---|---|
| committer | Erik Andersen <andersen@codepoet.org> | 2000-03-19 05:28:55 +0000 |
| commit | c7c634bd88c57d910c6089de7f0091ca4e3d1843 (patch) | |
| tree | df2b1c18c27729603880dd797ee6bd51d192d9cd /shell | |
| parent | 6c41c448982827ef5bfac9bac14bc9e509f45bc4 (diff) | |
| download | busybox-w32-c7c634bd88c57d910c6089de7f0091ca4e3d1843.tar.gz busybox-w32-c7c634bd88c57d910c6089de7f0091ca4e3d1843.tar.bz2 busybox-w32-c7c634bd88c57d910c6089de7f0091ca4e3d1843.zip | |
Some more stuff.
-Erik
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/cmdedit.c | 919 | ||||
| -rw-r--r-- | shell/cmdedit.h | 2 | ||||
| -rw-r--r-- | shell/lash.c | 21 |
3 files changed, 483 insertions, 459 deletions
diff --git a/shell/cmdedit.c b/shell/cmdedit.c index 9e162c6ee..8a7a5fb03 100644 --- a/shell/cmdedit.c +++ b/shell/cmdedit.c | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | /* vi: set sw=4 ts=4: */ | ||
| 1 | /* | 2 | /* |
| 2 | * Termios command line History and Editting for NetBSD sh (ash) | 3 | * Termios command line History and Editting for NetBSD sh (ash) |
| 3 | * Copyright (c) 1999 | 4 | * Copyright (c) 1999 |
| @@ -40,10 +41,8 @@ | |||
| 40 | #include <ctype.h> | 41 | #include <ctype.h> |
| 41 | #include <signal.h> | 42 | #include <signal.h> |
| 42 | 43 | ||
| 43 | #include "cmdedit.h" | ||
| 44 | 44 | ||
| 45 | 45 | #define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */ | |
| 46 | #define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */ | ||
| 47 | 46 | ||
| 48 | #define ESC 27 | 47 | #define ESC 27 |
| 49 | #define DEL 127 | 48 | #define DEL 127 |
| @@ -55,142 +54,150 @@ static struct history *his_end = NULL; /* Last element in command line list */ | |||
| 55 | static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */ | 54 | static struct termio old_term, new_term; /* Current termio and the previous termio before starting ash */ |
| 56 | 55 | ||
| 57 | static int history_counter = 0; /* Number of commands in history list */ | 56 | static int history_counter = 0; /* Number of commands in history list */ |
| 58 | static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */ | 57 | static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */ |
| 59 | char *parsenextc; /* copy of parsefile->nextc */ | 58 | char *parsenextc; /* copy of parsefile->nextc */ |
| 60 | 59 | ||
| 61 | struct history { | 60 | struct history { |
| 62 | char *s; | 61 | char *s; |
| 63 | struct history *p; | 62 | struct history *p; |
| 64 | struct history *n; | 63 | struct history *n; |
| 65 | }; | 64 | }; |
| 66 | 65 | ||
| 67 | 66 | ||
| 68 | /* Version of write which resumes after a signal is caught. */ | 67 | /* Version of write which resumes after a signal is caught. */ |
| 69 | int xwrite(int fd, char *buf, int nbytes) | 68 | int xwrite(int fd, char *buf, int nbytes) |
| 70 | { | 69 | { |
| 71 | int ntry; | 70 | int ntry; |
| 72 | int i; | 71 | int i; |
| 73 | int n; | 72 | int n; |
| 74 | 73 | ||
| 75 | n = nbytes; | 74 | n = nbytes; |
| 76 | ntry = 0; | 75 | ntry = 0; |
| 77 | for (;;) { | 76 | for (;;) { |
| 78 | i = write(fd, buf, n); | 77 | i = write(fd, buf, n); |
| 79 | if (i > 0) { | 78 | if (i > 0) { |
| 80 | if ((n -= i) <= 0) | 79 | if ((n -= i) <= 0) |
| 81 | return nbytes; | 80 | return nbytes; |
| 82 | buf += i; | 81 | buf += i; |
| 83 | ntry = 0; | 82 | ntry = 0; |
| 84 | } else if (i == 0) { | 83 | } else if (i == 0) { |
| 85 | if (++ntry > 10) | 84 | if (++ntry > 10) |
| 86 | return nbytes - n; | 85 | return nbytes - n; |
| 87 | } else if (errno != EINTR) { | 86 | } else if (errno != EINTR) { |
| 88 | return -1; | 87 | return -1; |
| 88 | } | ||
| 89 | } | 89 | } |
| 90 | } | ||
| 91 | } | 90 | } |
| 92 | 91 | ||
| 93 | 92 | ||
| 94 | /* Version of ioctl that retries after a signal is caught. */ | 93 | /* Version of ioctl that retries after a signal is caught. */ |
| 95 | int xioctl(int fd, unsigned long request, char *arg) | 94 | int xioctl(int fd, unsigned long request, char *arg) |
| 96 | { | 95 | { |
| 97 | int i; | 96 | int i; |
| 98 | while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR); | 97 | |
| 99 | return i; | 98 | while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR); |
| 99 | return i; | ||
| 100 | } | 100 | } |
| 101 | 101 | ||
| 102 | 102 | ||
| 103 | void cmdedit_reset_term(void) | 103 | void cmdedit_reset_term(void) |
| 104 | { | 104 | { |
| 105 | if (reset_term) | 105 | if (reset_term) |
| 106 | xioctl(fileno(stdin), TCSETA, (void *) &old_term); | 106 | xioctl(fileno(stdin), TCSETA, (void *) &old_term); |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | void prepareToDie(int sig) | 109 | void prepareToDie(int sig) |
| 110 | { | 110 | { |
| 111 | cmdedit_reset_term(); | 111 | cmdedit_reset_term(); |
| 112 | fprintf(stdout, "\n"); | 112 | fprintf(stdout, "\n"); |
| 113 | exit(TRUE); | 113 | exit(TRUE); |
| 114 | } | 114 | } |
| 115 | 115 | ||
| 116 | void input_home(int outputFd, int *cursor) | 116 | void input_home(int outputFd, int *cursor) |
| 117 | { /* Command line input routines */ | 117 | { /* Command line input routines */ |
| 118 | while (*cursor > 0) { | 118 | while (*cursor > 0) { |
| 119 | xwrite(outputFd, "\b", 1); | 119 | xwrite(outputFd, "\b", 1); |
| 120 | --*cursor; | 120 | --*cursor; |
| 121 | } | 121 | } |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | 124 | ||
| 125 | void input_delete(int outputFd, int cursor) | 125 | void input_delete(int outputFd, int cursor) |
| 126 | { | 126 | { |
| 127 | int j = 0; | 127 | int j = 0; |
| 128 | 128 | ||
| 129 | memmove(parsenextc + cursor, parsenextc + cursor + 1, | 129 | memmove(parsenextc + cursor, parsenextc + cursor + 1, |
| 130 | BUFSIZ - cursor - 1); | 130 | BUFSIZ - cursor - 1); |
| 131 | for (j = cursor; j < (BUFSIZ - 1); j++) { | 131 | for (j = cursor; j < (BUFSIZ - 1); j++) { |
| 132 | if (!*(parsenextc + j)) | 132 | if (!*(parsenextc + j)) |
| 133 | break; | 133 | break; |
| 134 | else | 134 | else |
| 135 | xwrite(outputFd, (parsenextc + j), 1); | 135 | xwrite(outputFd, (parsenextc + j), 1); |
| 136 | } | 136 | } |
| 137 | 137 | ||
| 138 | xwrite(outputFd, " \b", 2); | 138 | xwrite(outputFd, " \b", 2); |
| 139 | 139 | ||
| 140 | while (j-- > cursor) | 140 | while (j-- > cursor) |
| 141 | xwrite(outputFd, "\b", 1); | 141 | xwrite(outputFd, "\b", 1); |
| 142 | } | 142 | } |
| 143 | 143 | ||
| 144 | 144 | ||
| 145 | void input_end(int outputFd, int *cursor, int len) | 145 | void input_end(int outputFd, int *cursor, int len) |
| 146 | { | 146 | { |
| 147 | while (*cursor < len) { | 147 | while (*cursor < len) { |
| 148 | xwrite(outputFd, "\033[C", 3); | 148 | xwrite(outputFd, "\033[C", 3); |
| 149 | ++*cursor; | 149 | ++*cursor; |
| 150 | } | 150 | } |
| 151 | } | 151 | } |
| 152 | 152 | ||
| 153 | 153 | ||
| 154 | void input_backspace(int outputFd, int *cursor, int *len) | 154 | void input_backspace(int outputFd, int *cursor, int *len) |
| 155 | { | 155 | { |
| 156 | int j = 0; | 156 | int j = 0; |
| 157 | 157 | ||
| 158 | if (*cursor > 0) { | 158 | if (*cursor > 0) { |
| 159 | xwrite(outputFd, "\b \b", 3); | 159 | xwrite(outputFd, "\b \b", 3); |
| 160 | --*cursor; | 160 | --*cursor; |
| 161 | memmove(parsenextc + *cursor, parsenextc + *cursor + 1, | 161 | memmove(parsenextc + *cursor, parsenextc + *cursor + 1, |
| 162 | BUFSIZ - *cursor + 1); | 162 | BUFSIZ - *cursor + 1); |
| 163 | 163 | ||
| 164 | for (j = *cursor; j < (BUFSIZ - 1); j++) { | 164 | for (j = *cursor; j < (BUFSIZ - 1); j++) { |
| 165 | if (!*(parsenextc + j)) | 165 | if (!*(parsenextc + j)) |
| 166 | break; | 166 | break; |
| 167 | else | 167 | else |
| 168 | xwrite(outputFd, (parsenextc + j), 1); | 168 | xwrite(outputFd, (parsenextc + j), 1); |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | xwrite(outputFd, " \b", 2); | 171 | xwrite(outputFd, " \b", 2); |
| 172 | 172 | ||
| 173 | while (j-- > *cursor) | 173 | while (j-- > *cursor) |
| 174 | xwrite(outputFd, "\b", 1); | 174 | xwrite(outputFd, "\b", 1); |
| 175 | 175 | ||
| 176 | --*len; | 176 | --*len; |
| 177 | } | 177 | } |
| 178 | } | 178 | } |
| 179 | 179 | ||
| 180 | char **username_completion_matches( char* matchBuf) | 180 | char** username_completion_matches(char* command, int *num_matches) |
| 181 | { | 181 | { |
| 182 | fprintf(stderr, "\nin username_completion_matches\n"); | 182 | char **matches = (char **) NULL; |
| 183 | return ( (char**) NULL); | 183 | *num_matches=0; |
| 184 | fprintf(stderr, "\nin username_completion_matches\n"); | ||
| 185 | return (matches); | ||
| 184 | } | 186 | } |
| 185 | char **command_completion_matches( char* matchBuf) | 187 | char** find_path_executable_n_cwd_matches(char* command, int *num_matches) |
| 186 | { | 188 | { |
| 187 | fprintf(stderr, "\nin command_completion_matches\n"); | 189 | char **matches = (char **) NULL; |
| 188 | return ( (char**) NULL); | 190 | matches = malloc(sizeof(char*)*100); |
| 189 | } | 191 | |
| 190 | char **directory_completion_matches( char* matchBuf) | 192 | matches[0] = malloc(sizeof(char)*50); |
| 191 | { | 193 | matches[1] = malloc(sizeof(char)*50); |
| 192 | fprintf(stderr, "\nin directory_completion_matches\n"); | 194 | |
| 193 | return ( (char**) NULL); | 195 | sprintf(matches[0], "Hello"); |
| 196 | sprintf(matches[1], "Howdy"); | ||
| 197 | *num_matches=2; | ||
| 198 | |||
| 199 | // fprintf(stderr, "\nin find_path_executable_n_cwd_matches\n"); | ||
| 200 | return (matches); | ||
| 194 | } | 201 | } |
| 195 | 202 | ||
| 196 | /* | 203 | /* |
| @@ -211,387 +218,401 @@ char **directory_completion_matches( char* matchBuf) | |||
| 211 | * TODO: implement TAB command completion. :) | 218 | * TODO: implement TAB command completion. :) |
| 212 | * | 219 | * |
| 213 | */ | 220 | */ |
| 214 | extern int cmdedit_read_input(int inputFd, int outputFd, | 221 | extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, |
| 215 | char command[BUFSIZ]) | 222 | char command[BUFSIZ]) |
| 216 | { | 223 | { |
| 217 | 224 | ||
| 218 | int nr = 0; | 225 | int nr = 0; |
| 219 | int len = 0; | 226 | int len = 0; |
| 220 | int j = 0; | 227 | int j = 0; |
| 221 | int cursor = 0; | 228 | int cursor = 0; |
| 222 | int break_out = 0; | 229 | int break_out = 0; |
| 223 | int ret = 0; | 230 | int ret = 0; |
| 224 | int lastWasTab = FALSE; | 231 | int lastWasTab = FALSE; |
| 225 | char **matches = (char **)NULL; | 232 | char c = 0; |
| 226 | char c = 0; | 233 | struct history *hp = his_end; |
| 227 | struct history *hp = his_end; | 234 | |
| 228 | 235 | memset(command, 0, sizeof(command)); | |
| 229 | memset(command, 0, sizeof(command)); | 236 | parsenextc = command; |
| 230 | parsenextc = command; | 237 | if (!reset_term) { |
| 231 | if (!reset_term) { | 238 | xioctl(inputFd, TCGETA, (void *) &old_term); |
| 232 | xioctl(inputFd, TCGETA, (void *) &old_term); | 239 | memcpy(&new_term, &old_term, sizeof(struct termio)); |
| 233 | memcpy(&new_term, &old_term, sizeof(struct termio)); | 240 | |
| 234 | new_term.c_cc[VMIN] = 1; | 241 | new_term.c_cc[VMIN] = 1; |
| 235 | new_term.c_cc[VTIME] = 0; | 242 | new_term.c_cc[VTIME] = 0; |
| 236 | new_term.c_lflag &= ~ICANON; /* unbuffered input */ | 243 | new_term.c_lflag &= ~ICANON; /* unbuffered input */ |
| 237 | new_term.c_lflag &= ~ECHO; | 244 | new_term.c_lflag &= ~ECHO; |
| 238 | xioctl(inputFd, TCSETA, (void *) &new_term); | 245 | xioctl(inputFd, TCSETA, (void *) &new_term); |
| 239 | reset_term = 1; | 246 | reset_term = 1; |
| 240 | } else { | 247 | } else { |
| 241 | xioctl(inputFd, TCSETA, (void *) &new_term); | 248 | xioctl(inputFd, TCSETA, (void *) &new_term); |
| 242 | } | 249 | } |
| 243 | |||
| 244 | memset(parsenextc, 0, BUFSIZ); | ||
| 245 | |||
| 246 | while (1) { | ||
| 247 | |||
| 248 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 249 | return ret; | ||
| 250 | |||
| 251 | switch (c) { | ||
| 252 | case 1: | ||
| 253 | /* Control-a -- Beginning of line */ | ||
| 254 | input_home(outputFd, &cursor); | ||
| 255 | case 5: | ||
| 256 | /* Control-e -- End of line */ | ||
| 257 | input_end(outputFd, &cursor, len); | ||
| 258 | break; | ||
| 259 | case 2: | ||
| 260 | /* Control-b -- Move back one character */ | ||
| 261 | if (cursor > 0) { | ||
| 262 | xwrite(outputFd, "\033[D", 3); | ||
| 263 | cursor--; | ||
| 264 | } | ||
| 265 | break; | ||
| 266 | case 6: | ||
| 267 | /* Control-f -- Move forward one character */ | ||
| 268 | if (cursor < len) { | ||
| 269 | xwrite(outputFd, "\033[C", 3); | ||
| 270 | cursor++; | ||
| 271 | } | ||
| 272 | break; | ||
| 273 | case 4: | ||
| 274 | /* Control-d -- Delete one character */ | ||
| 275 | if (cursor != len) { | ||
| 276 | input_delete(outputFd, cursor); | ||
| 277 | len--; | ||
| 278 | } else if (len == 0) { | ||
| 279 | prepareToDie(0); | ||
| 280 | exit(0); | ||
| 281 | } | ||
| 282 | break; | ||
| 283 | case 14: | ||
| 284 | /* Control-n -- Get next command */ | ||
| 285 | if (hp && hp->n && hp->n->s) { | ||
| 286 | free( hp->s); | ||
| 287 | hp->s = strdup(parsenextc); | ||
| 288 | hp = hp->n; | ||
| 289 | goto hop; | ||
| 290 | } | ||
| 291 | break; | ||
| 292 | case 16: | ||
| 293 | /* Control-p -- Get previous command */ | ||
| 294 | if (hp && hp->p) { | ||
| 295 | free( hp->s); | ||
| 296 | hp->s = strdup(parsenextc); | ||
| 297 | hp = hp->p; | ||
| 298 | goto hop; | ||
| 299 | } | ||
| 300 | break; | ||
| 301 | case '\t': | ||
| 302 | { | ||
| 303 | /* Do TAB completion */ | ||
| 304 | int in_command_position=0, ti=len-1; | ||
| 305 | |||
| 306 | if (lastWasTab == FALSE) { | ||
| 307 | char *tmp; | ||
| 308 | char *matchBuf; | ||
| 309 | |||
| 310 | if (matches) { | ||
| 311 | free(matches); | ||
| 312 | matches = (char **)NULL; | ||
| 313 | } | ||
| 314 | |||
| 315 | matchBuf = (char *) calloc(BUFSIZ, sizeof(char)); | ||
| 316 | |||
| 317 | /* Make a local copy of the string -- up | ||
| 318 | * to the the position of the cursor */ | ||
| 319 | strcpy( matchBuf, parsenextc); | ||
| 320 | matchBuf[cursor+1] = '\0'; | ||
| 321 | |||
| 322 | fprintf(stderr, "matchBuf='%s'\n", matchBuf); | ||
| 323 | |||
| 324 | /* skip leading white space */ | ||
| 325 | tmp = matchBuf; | ||
| 326 | while (*tmp && isspace(*tmp)) { | ||
| 327 | (tmp)++; | ||
| 328 | ti++; | ||
| 329 | } | ||
| 330 | |||
| 331 | /* Determine if this is a command word or not */ | ||
| 332 | //while ((ti > -1) && (whitespace (matchBuf[ti]))) { | ||
| 333 | //printf("\nti=%d\n", ti); | ||
| 334 | // ti--; | ||
| 335 | // } | ||
| 336 | printf("\nti=%d\n", ti); | ||
| 337 | |||
| 338 | if (ti < 0) { | ||
| 339 | in_command_position++; | ||
| 340 | } else if (member(matchBuf[ti], ";|&{(`")) { | ||
| 341 | int this_char, prev_char; | ||
| 342 | in_command_position++; | ||
| 343 | /* Handle the two character tokens `>&', `<&', and `>|'. | ||
| 344 | We are not in a command position after one of these. */ | ||
| 345 | this_char = matchBuf[ti]; | ||
| 346 | prev_char = matchBuf[ti - 1]; | ||
| 347 | |||
| 348 | if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || | ||
| 349 | (this_char == '|' && prev_char == '>')) { | ||
| 350 | in_command_position = 0; | ||
| 351 | } | ||
| 352 | /* For now, do not bother with catching quoted | ||
| 353 | * expressions and marking them as not in command | ||
| 354 | * positions. Some other day. Or not. | ||
| 355 | */ | ||
| 356 | //else if (char_is_quoted (matchBuf, ti)) { | ||
| 357 | // in_command_position = 0; | ||
| 358 | //} | ||
| 359 | } | ||
| 360 | printf("\nin_command_position=%d\n", in_command_position); | ||
| 361 | /* If the word starts in `~', and there is no slash in the word, | ||
| 362 | * then try completing this word as a username. */ | ||
| 363 | if (*matchBuf == '~' && !strchr (matchBuf, '/')) | ||
| 364 | matches = username_completion_matches(matchBuf); | ||
| 365 | |||
| 366 | /* If this word is in a command position, then complete over possible | ||
| 367 | * command names, including aliases, built-ins, and executables. */ | ||
| 368 | if (!matches && in_command_position) { | ||
| 369 | matches = command_completion_matches(matchBuf); | ||
| 370 | |||
| 371 | /* If we are attempting command completion and nothing matches, | ||
| 372 | * then try and match directories as a last resort... */ | ||
| 373 | if (!matches) | ||
| 374 | matches = directory_completion_matches(matchBuf); | ||
| 375 | } | ||
| 376 | } else { | ||
| 377 | printf("\nprinting match list\n"); | ||
| 378 | } | ||
| 379 | /* Rewrite the whole line (for debugging) */ | ||
| 380 | for (; cursor > 0; cursor--) | ||
| 381 | xwrite(outputFd, "\b", 1); | ||
| 382 | len = strlen(parsenextc); | ||
| 383 | xwrite(outputFd, parsenextc, len); | ||
| 384 | cursor = len; | ||
| 385 | break; | ||
| 386 | } | ||
| 387 | case '\b': | ||
| 388 | case DEL: | ||
| 389 | /* Backspace */ | ||
| 390 | input_backspace(outputFd, &cursor, &len); | ||
| 391 | break; | ||
| 392 | case '\n': | ||
| 393 | /* Enter */ | ||
| 394 | *(parsenextc + len++ + 1) = c; | ||
| 395 | xwrite(outputFd, &c, 1); | ||
| 396 | break_out = 1; | ||
| 397 | break; | ||
| 398 | case ESC: { | ||
| 399 | /* escape sequence follows */ | ||
| 400 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 401 | return ret; | ||
| 402 | |||
| 403 | if (c == '[') { /* 91 */ | ||
| 404 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 405 | return ret; | ||
| 406 | |||
| 407 | switch (c) { | ||
| 408 | case 'A': | ||
| 409 | /* Up Arrow -- Get previous command */ | ||
| 410 | if (hp && hp->p) { | ||
| 411 | free( hp->s); | ||
| 412 | hp->s = strdup(parsenextc); | ||
| 413 | hp = hp->p; | ||
| 414 | goto hop; | ||
| 415 | } | ||
| 416 | break; | ||
| 417 | case 'B': | ||
| 418 | /* Down Arrow -- Get next command */ | ||
| 419 | if (hp && hp->n && hp->n->s) { | ||
| 420 | free( hp->s); | ||
| 421 | hp->s = strdup(parsenextc); | ||
| 422 | hp = hp->n; | ||
| 423 | goto hop; | ||
| 424 | } | ||
| 425 | break; | ||
| 426 | |||
| 427 | /* This is where we rewrite the line | ||
| 428 | * using the selected history item */ | ||
| 429 | hop: | ||
| 430 | len = strlen(parsenextc); | ||
| 431 | |||
| 432 | /* return to begining of line */ | ||
| 433 | for (; cursor > 0; cursor--) | ||
| 434 | xwrite(outputFd, "\b", 1); | ||
| 435 | xwrite(outputFd, parsenextc, len); | ||
| 436 | 250 | ||
| 437 | /* erase old command */ | 251 | memset(parsenextc, 0, BUFSIZ); |
| 438 | for (j = 0; j < len; j++) | ||
| 439 | xwrite(outputFd, " ", 1); | ||
| 440 | 252 | ||
| 441 | /* return to begining of line */ | 253 | while (1) { |
| 442 | for (j = len; j > 0; j--) | ||
| 443 | xwrite(outputFd, "\b", 1); | ||
| 444 | 254 | ||
| 445 | memset(parsenextc, 0, BUFSIZ); | ||
| 446 | /* write new command */ | ||
| 447 | strcpy(parsenextc, hp->s); | ||
| 448 | len = strlen(hp->s); | ||
| 449 | xwrite(outputFd, parsenextc, len); | ||
| 450 | cursor = len; | ||
| 451 | break; | ||
| 452 | case 'C': | ||
| 453 | /* Right Arrow -- Move forward one character */ | ||
| 454 | if (cursor < len) { | ||
| 455 | xwrite(outputFd, "\033[C", 3); | ||
| 456 | cursor++; | ||
| 457 | } | ||
| 458 | break; | ||
| 459 | case 'D': | ||
| 460 | /* Left Arrow -- Move back one character */ | ||
| 461 | if (cursor > 0) { | ||
| 462 | xwrite(outputFd, "\033[D", 3); | ||
| 463 | cursor--; | ||
| 464 | } | ||
| 465 | break; | ||
| 466 | case '3': | ||
| 467 | /* Delete */ | ||
| 468 | if (cursor != len) { | ||
| 469 | input_delete(outputFd, cursor); | ||
| 470 | len--; | ||
| 471 | } | ||
| 472 | break; | ||
| 473 | case '1': | ||
| 474 | /* Home (Ctrl-A) */ | ||
| 475 | input_home(outputFd, &cursor); | ||
| 476 | break; | ||
| 477 | case '4': | ||
| 478 | /* End (Ctrl-E) */ | ||
| 479 | input_end(outputFd, &cursor, len); | ||
| 480 | break; | ||
| 481 | } | ||
| 482 | if (c == '1' || c == '3' || c == '4') | ||
| 483 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 484 | return ret; /* read 126 (~) */ | ||
| 485 | } | ||
| 486 | if (c == 'O') { | ||
| 487 | /* 79 */ | ||
| 488 | if ((ret = read(inputFd, &c, 1)) < 1) | 255 | if ((ret = read(inputFd, &c, 1)) < 1) |
| 489 | return ret; | 256 | return ret; |
| 257 | |||
| 490 | switch (c) { | 258 | switch (c) { |
| 491 | case 'H': | 259 | case 1: |
| 492 | /* Home (xterm) */ | 260 | /* Control-a -- Beginning of line */ |
| 493 | input_home(outputFd, &cursor); | 261 | input_home(outputFd, &cursor); |
| 494 | break; | 262 | case 5: |
| 495 | case 'F': | 263 | /* Control-e -- End of line */ |
| 496 | /* End (xterm) */ | 264 | input_end(outputFd, &cursor, len); |
| 497 | input_end(outputFd, &cursor, len); | 265 | break; |
| 498 | break; | 266 | case 2: |
| 499 | } | 267 | /* Control-b -- Move back one character */ |
| 500 | } | 268 | if (cursor > 0) { |
| 501 | c = 0; | 269 | xwrite(outputFd, "\033[D", 3); |
| 502 | break; | 270 | cursor--; |
| 503 | } | 271 | } |
| 272 | break; | ||
| 273 | case 6: | ||
| 274 | /* Control-f -- Move forward one character */ | ||
| 275 | if (cursor < len) { | ||
| 276 | xwrite(outputFd, "\033[C", 3); | ||
| 277 | cursor++; | ||
| 278 | } | ||
| 279 | break; | ||
| 280 | case 4: | ||
| 281 | /* Control-d -- Delete one character */ | ||
| 282 | if (cursor != len) { | ||
| 283 | input_delete(outputFd, cursor); | ||
| 284 | len--; | ||
| 285 | } else if (len == 0) { | ||
| 286 | prepareToDie(0); | ||
| 287 | exit(0); | ||
| 288 | } | ||
| 289 | break; | ||
| 290 | case 14: | ||
| 291 | /* Control-n -- Get next command */ | ||
| 292 | if (hp && hp->n && hp->n->s) { | ||
| 293 | free(hp->s); | ||
| 294 | hp->s = strdup(parsenextc); | ||
| 295 | hp = hp->n; | ||
| 296 | goto hop; | ||
| 297 | } | ||
| 298 | break; | ||
| 299 | case 16: | ||
| 300 | /* Control-p -- Get previous command */ | ||
| 301 | if (hp && hp->p) { | ||
| 302 | free(hp->s); | ||
| 303 | hp->s = strdup(parsenextc); | ||
| 304 | hp = hp->p; | ||
| 305 | goto hop; | ||
| 306 | } | ||
| 307 | break; | ||
| 308 | case '\t': | ||
| 309 | { | ||
| 310 | /* Do TAB completion */ | ||
| 311 | static int num_matches=0; | ||
| 312 | static char **matches = (char **) NULL; | ||
| 313 | int pos = cursor; | ||
| 314 | |||
| 315 | |||
| 316 | if (lastWasTab == FALSE) { | ||
| 317 | char *tmp, *tmp1, *matchBuf; | ||
| 318 | |||
| 319 | /* For now, we will not bother with trying to distinguish | ||
| 320 | * whether the cursor is in/at a command extression -- we | ||
| 321 | * will always try all possable matches. If you don't like | ||
| 322 | * that, feel free to fix it. | ||
| 323 | */ | ||
| 324 | |||
| 325 | /* Make a local copy of the string -- up | ||
| 326 | * to the the position of the cursor */ | ||
| 327 | matchBuf = (char *) calloc(BUFSIZ, sizeof(char)); | ||
| 328 | strncpy(matchBuf, parsenextc, cursor); | ||
| 329 | tmp=matchBuf; | ||
| 330 | |||
| 331 | /* skip past any command seperator tokens */ | ||
| 332 | while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) { | ||
| 333 | tmp=++tmp1; | ||
| 334 | /* skip any leading white space */ | ||
| 335 | while (*tmp && isspace(*tmp)) | ||
| 336 | ++tmp; | ||
| 337 | } | ||
| 338 | |||
| 339 | /* skip any leading white space */ | ||
| 340 | while (*tmp && isspace(*tmp)) | ||
| 341 | ++tmp; | ||
| 342 | |||
| 343 | /* Free up any memory already allocated */ | ||
| 344 | if (matches) { | ||
| 345 | free(matches); | ||
| 346 | matches = (char **) NULL; | ||
| 347 | } | ||
| 348 | |||
| 349 | /* If the word starts in `~', and there is no slash in the word, | ||
| 350 | * then try completing this word as a username. */ | ||
| 351 | if (*tmp == '~' && !strchr(tmp, '/')) | ||
| 352 | matches = username_completion_matches(tmp, &num_matches); | ||
| 353 | |||
| 354 | /* Try to match any executable in our patch, and everything | ||
| 355 | * in the current working directory that matches. | ||
| 356 | */ | ||
| 357 | if (!matches) | ||
| 358 | matches = find_path_executable_n_cwd_matches(tmp, &num_matches); | ||
| 359 | } else { | ||
| 360 | if ( matches && num_matches>0 ) { | ||
| 361 | int i, col; | ||
| 362 | |||
| 363 | fprintf(stderr, "\nTabbing...\n"); | ||
| 364 | |||
| 365 | /* Make a list of the matches */ | ||
| 366 | col += xwrite(outputFd, "\n", 1); | ||
| 367 | for (i=0,col=0; i<num_matches; i++) { | ||
| 368 | col += xwrite(outputFd, prompt, strlen(matches[i])); | ||
| 369 | if (col > 60 && matches[i+1] != NULL) { | ||
| 370 | xwrite(outputFd, "\n", 1); | ||
| 371 | col = 0; | ||
| 372 | } | ||
| 373 | } | ||
| 374 | xwrite(outputFd, "\n", 1); | ||
| 375 | |||
| 376 | len+=strlen(prompt); | ||
| 377 | fprintf(stderr, "len=%d\n", len); | ||
| 378 | |||
| 379 | /* Move to the beginning of the line */ | ||
| 380 | input_home(outputFd, &len); | ||
| 381 | |||
| 382 | /* erase everything */ | ||
| 383 | for (j = 0; j < len; j++) | ||
| 384 | xwrite(outputFd, " ", 1); | ||
| 385 | |||
| 386 | /* return to begining of line */ | ||
| 387 | input_home(outputFd, &cursor); | ||
| 388 | |||
| 389 | /* Rewrite the prompt) */ | ||
| 390 | xwrite(outputFd, prompt, strlen(prompt)); | ||
| 391 | |||
| 392 | /* Rewrite the command */ | ||
| 393 | len = strlen(parsenextc); | ||
| 394 | xwrite(outputFd, parsenextc, len); | ||
| 395 | |||
| 396 | /* Move back to where the cursor used to be */ | ||
| 397 | for (cursor=pos; cursor > 0; cursor--) | ||
| 398 | xwrite(outputFd, "\b", 1); | ||
| 399 | cursor = pos; | ||
| 400 | |||
| 401 | //fprintf(stderr, "\nprompt='%s'\n", prompt); | ||
| 402 | } | ||
| 403 | } | ||
| 404 | break; | ||
| 405 | } | ||
| 406 | case '\b': | ||
| 407 | case DEL: | ||
| 408 | /* Backspace */ | ||
| 409 | input_backspace(outputFd, &cursor, &len); | ||
| 410 | break; | ||
| 411 | case '\n': | ||
| 412 | /* Enter */ | ||
| 413 | *(parsenextc + len++ + 1) = c; | ||
| 414 | xwrite(outputFd, &c, 1); | ||
| 415 | break_out = 1; | ||
| 416 | break; | ||
| 417 | case ESC:{ | ||
| 418 | /* escape sequence follows */ | ||
| 419 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 420 | return ret; | ||
| 421 | |||
| 422 | if (c == '[') { /* 91 */ | ||
| 423 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 424 | return ret; | ||
| 425 | |||
| 426 | switch (c) { | ||
| 427 | case 'A': | ||
| 428 | /* Up Arrow -- Get previous command */ | ||
| 429 | if (hp && hp->p) { | ||
| 430 | free(hp->s); | ||
| 431 | hp->s = strdup(parsenextc); | ||
| 432 | hp = hp->p; | ||
| 433 | goto hop; | ||
| 434 | } | ||
| 435 | break; | ||
| 436 | case 'B': | ||
| 437 | /* Down Arrow -- Get next command */ | ||
| 438 | if (hp && hp->n && hp->n->s) { | ||
| 439 | free(hp->s); | ||
| 440 | hp->s = strdup(parsenextc); | ||
| 441 | hp = hp->n; | ||
| 442 | goto hop; | ||
| 443 | } | ||
| 444 | break; | ||
| 445 | |||
| 446 | /* This is where we rewrite the line | ||
| 447 | * using the selected history item */ | ||
| 448 | hop: | ||
| 449 | len = strlen(parsenextc); | ||
| 450 | |||
| 451 | /* return to begining of line */ | ||
| 452 | for (; cursor > 0; cursor--) | ||
| 453 | xwrite(outputFd, "\b", 1); | ||
| 454 | xwrite(outputFd, parsenextc, len); | ||
| 455 | |||
| 456 | /* erase old command */ | ||
| 457 | for (j = 0; j < len; j++) | ||
| 458 | xwrite(outputFd, " ", 1); | ||
| 459 | |||
| 460 | /* return to begining of line */ | ||
| 461 | for (j = len; j > 0; j--) | ||
| 462 | xwrite(outputFd, "\b", 1); | ||
| 463 | |||
| 464 | memset(parsenextc, 0, BUFSIZ); | ||
| 465 | /* write new command */ | ||
| 466 | strcpy(parsenextc, hp->s); | ||
| 467 | len = strlen(hp->s); | ||
| 468 | xwrite(outputFd, parsenextc, len); | ||
| 469 | cursor = len; | ||
| 470 | break; | ||
| 471 | case 'C': | ||
| 472 | /* Right Arrow -- Move forward one character */ | ||
| 473 | if (cursor < len) { | ||
| 474 | xwrite(outputFd, "\033[C", 3); | ||
| 475 | cursor++; | ||
| 476 | } | ||
| 477 | break; | ||
| 478 | case 'D': | ||
| 479 | /* Left Arrow -- Move back one character */ | ||
| 480 | if (cursor > 0) { | ||
| 481 | xwrite(outputFd, "\033[D", 3); | ||
| 482 | cursor--; | ||
| 483 | } | ||
| 484 | break; | ||
| 485 | case '3': | ||
| 486 | /* Delete */ | ||
| 487 | if (cursor != len) { | ||
| 488 | input_delete(outputFd, cursor); | ||
| 489 | len--; | ||
| 490 | } | ||
| 491 | break; | ||
| 492 | case '1': | ||
| 493 | /* Home (Ctrl-A) */ | ||
| 494 | input_home(outputFd, &cursor); | ||
| 495 | break; | ||
| 496 | case '4': | ||
| 497 | /* End (Ctrl-E) */ | ||
| 498 | input_end(outputFd, &cursor, len); | ||
| 499 | break; | ||
| 500 | } | ||
| 501 | if (c == '1' || c == '3' || c == '4') | ||
| 502 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 503 | return ret; /* read 126 (~) */ | ||
| 504 | } | ||
| 505 | if (c == 'O') { | ||
| 506 | /* 79 */ | ||
| 507 | if ((ret = read(inputFd, &c, 1)) < 1) | ||
| 508 | return ret; | ||
| 509 | switch (c) { | ||
| 510 | case 'H': | ||
| 511 | /* Home (xterm) */ | ||
| 512 | input_home(outputFd, &cursor); | ||
| 513 | break; | ||
| 514 | case 'F': | ||
| 515 | /* End (xterm) */ | ||
| 516 | input_end(outputFd, &cursor, len); | ||
| 517 | break; | ||
| 518 | } | ||
| 519 | } | ||
| 520 | c = 0; | ||
| 521 | break; | ||
| 522 | } | ||
| 523 | |||
| 524 | default: /* If it's regular input, do the normal thing */ | ||
| 525 | |||
| 526 | if (!isprint(c)) /* Skip non-printable characters */ | ||
| 527 | break; | ||
| 528 | |||
| 529 | if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ | ||
| 530 | break; | ||
| 531 | |||
| 532 | len++; | ||
| 533 | |||
| 534 | if (cursor == (len - 1)) { /* Append if at the end of the line */ | ||
| 535 | *(parsenextc + cursor) = c; | ||
| 536 | } else { /* Insert otherwise */ | ||
| 537 | memmove(parsenextc + cursor + 1, parsenextc + cursor, | ||
| 538 | len - cursor - 1); | ||
| 539 | |||
| 540 | *(parsenextc + cursor) = c; | ||
| 541 | |||
| 542 | for (j = cursor; j < len; j++) | ||
| 543 | xwrite(outputFd, parsenextc + j, 1); | ||
| 544 | for (; j > cursor; j--) | ||
| 545 | xwrite(outputFd, "\033[D", 3); | ||
| 546 | } | ||
| 504 | 547 | ||
| 505 | default: /* If it's regular input, do the normal thing */ | 548 | cursor++; |
| 549 | xwrite(outputFd, &c, 1); | ||
| 550 | break; | ||
| 551 | } | ||
| 552 | if (c == '\t') | ||
| 553 | lastWasTab = TRUE; | ||
| 554 | else | ||
| 555 | lastWasTab = FALSE; | ||
| 506 | 556 | ||
| 507 | if (!isprint(c)) /* Skip non-printable characters */ | 557 | if (break_out) /* Enter is the command terminator, no more input. */ |
| 508 | break; | 558 | break; |
| 559 | } | ||
| 509 | 560 | ||
| 510 | if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ | 561 | nr = len + 1; |
| 511 | break; | 562 | xioctl(inputFd, TCSETA, (void *) &old_term); |
| 563 | reset_term = 0; | ||
| 512 | 564 | ||
| 513 | len++; | ||
| 514 | 565 | ||
| 515 | if (cursor == (len - 1)) { /* Append if at the end of the line */ | 566 | /* Handle command history log */ |
| 516 | *(parsenextc + cursor) = c; | 567 | if (*(parsenextc)) { |
| 517 | } else { /* Insert otherwise */ | ||
| 518 | memmove(parsenextc + cursor + 1, parsenextc + cursor, | ||
| 519 | len - cursor - 1); | ||
| 520 | 568 | ||
| 521 | *(parsenextc + cursor) = c; | 569 | struct history *h = his_end; |
| 522 | 570 | ||
| 523 | for (j = cursor; j < len; j++) | 571 | if (!h) { |
| 524 | xwrite(outputFd, parsenextc + j, 1); | 572 | /* No previous history */ |
| 525 | for (; j > cursor; j--) | 573 | h = his_front = malloc(sizeof(struct history)); |
| 526 | xwrite(outputFd, "\033[D", 3); | 574 | h->n = malloc(sizeof(struct history)); |
| 527 | } | ||
| 528 | 575 | ||
| 529 | cursor++; | 576 | h->p = NULL; |
| 530 | xwrite(outputFd, &c, 1); | 577 | h->s = strdup(parsenextc); |
| 531 | break; | 578 | h->n->p = h; |
| 532 | } | 579 | h->n->n = NULL; |
| 533 | if (c=='\t') | 580 | h->n->s = NULL; |
| 534 | lastWasTab = TRUE; | 581 | his_end = h->n; |
| 535 | else | 582 | history_counter++; |
| 536 | lastWasTab = FALSE; | 583 | } else { |
| 537 | 584 | /* Add a new history command */ | |
| 538 | if (break_out) /* Enter is the command terminator, no more input. */ | 585 | h->n = malloc(sizeof(struct history)); |
| 539 | break; | 586 | |
| 540 | } | 587 | h->n->p = h; |
| 541 | 588 | h->n->n = NULL; | |
| 542 | nr = len + 1; | 589 | h->n->s = NULL; |
| 543 | xioctl(inputFd, TCSETA, (void *) &old_term); | 590 | h->s = strdup(parsenextc); |
| 544 | reset_term = 0; | 591 | his_end = h->n; |
| 545 | 592 | ||
| 546 | 593 | /* After max history, remove the oldest command */ | |
| 547 | /* Handle command history log */ | 594 | if (history_counter >= MAX_HISTORY) { |
| 548 | if (*(parsenextc)) { | 595 | |
| 549 | 596 | struct history *p = his_front->n; | |
| 550 | struct history *h = his_end; | 597 | |
| 551 | 598 | p->p = NULL; | |
| 552 | if (!h) { | 599 | free(his_front->s); |
| 553 | /* No previous history */ | 600 | free(his_front); |
| 554 | h = his_front = malloc(sizeof(struct history)); | 601 | his_front = p; |
| 555 | h->n = malloc(sizeof(struct history)); | 602 | } else { |
| 556 | h->p = NULL; | 603 | history_counter++; |
| 557 | h->s = strdup(parsenextc); | 604 | } |
| 558 | h->n->p = h; | 605 | } |
| 559 | h->n->n = NULL; | ||
| 560 | h->n->s = NULL; | ||
| 561 | his_end = h->n; | ||
| 562 | history_counter++; | ||
| 563 | } else { | ||
| 564 | /* Add a new history command */ | ||
| 565 | h->n = malloc(sizeof(struct history)); | ||
| 566 | h->n->p = h; | ||
| 567 | h->n->n = NULL; | ||
| 568 | h->n->s = NULL; | ||
| 569 | h->s = strdup(parsenextc); | ||
| 570 | his_end = h->n; | ||
| 571 | |||
| 572 | /* After max history, remove the oldest command */ | ||
| 573 | if (history_counter >= MAX_HISTORY) { | ||
| 574 | |||
| 575 | struct history *p = his_front->n; | ||
| 576 | |||
| 577 | p->p = NULL; | ||
| 578 | free(his_front->s); | ||
| 579 | free(his_front); | ||
| 580 | his_front = p; | ||
| 581 | } else { | ||
| 582 | history_counter++; | ||
| 583 | } | ||
| 584 | } | 606 | } |
| 585 | } | ||
| 586 | 607 | ||
| 587 | return nr; | 608 | return nr; |
| 588 | } | 609 | } |
| 589 | 610 | ||
| 590 | extern void cmdedit_init(void) | 611 | extern void cmdedit_init(void) |
| 591 | { | 612 | { |
| 592 | atexit(cmdedit_reset_term); | 613 | atexit(cmdedit_reset_term); |
| 593 | signal(SIGINT, prepareToDie); | 614 | signal(SIGINT, prepareToDie); |
| 594 | signal(SIGQUIT, prepareToDie); | 615 | signal(SIGQUIT, prepareToDie); |
| 595 | signal(SIGTERM, prepareToDie); | 616 | signal(SIGTERM, prepareToDie); |
| 596 | } | 617 | } |
| 597 | #endif /* BB_FEATURE_SH_COMMAND_EDITING */ | 618 | #endif /* BB_FEATURE_SH_COMMAND_EDITING */ |
diff --git a/shell/cmdedit.h b/shell/cmdedit.h index e776543d6..843a74070 100644 --- a/shell/cmdedit.h +++ b/shell/cmdedit.h | |||
| @@ -12,6 +12,6 @@ | |||
| 12 | * | 12 | * |
| 13 | */ | 13 | */ |
| 14 | 14 | ||
| 15 | extern int cmdedit_read_input(int inputFd, int outputFd, char command[BUFSIZ]); | 15 | extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, char command[BUFSIZ]); |
| 16 | extern void cmdedit_init(void); | 16 | extern void cmdedit_init(void); |
| 17 | 17 | ||
diff --git a/shell/lash.c b/shell/lash.c index 068159697..8c1503ff4 100644 --- a/shell/lash.c +++ b/shell/lash.c | |||
| @@ -39,10 +39,6 @@ | |||
| 39 | #include <unistd.h> | 39 | #include <unistd.h> |
| 40 | 40 | ||
| 41 | 41 | ||
| 42 | #ifdef BB_FEATURE_SH_COMMAND_EDITING | ||
| 43 | #include "cmdedit.h" | ||
| 44 | #endif | ||
| 45 | |||
| 46 | #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" | 42 | #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" |
| 47 | 43 | ||
| 48 | 44 | ||
| @@ -123,8 +119,7 @@ static struct builtInCommand bltins[] = { | |||
| 123 | {"export", "Set environment variable", "export [VAR=value]", shell_export}, | 119 | {"export", "Set environment variable", "export [VAR=value]", shell_export}, |
| 124 | {"unset", "Unset environment variable", "unset VAR", shell_unset}, | 120 | {"unset", "Unset environment variable", "unset VAR", shell_unset}, |
| 125 | 121 | ||
| 126 | {".", "Source-in and run commands in a file", ". filename", | 122 | {".", "Source-in and run commands in a file", ". filename", shell_source}, |
| 127 | shell_source}, | ||
| 128 | {"help", "List shell built-in commands", "help", shell_help}, | 123 | {"help", "List shell built-in commands", "help", shell_help}, |
| 129 | {NULL, NULL, NULL, NULL} | 124 | {NULL, NULL, NULL, NULL} |
| 130 | }; | 125 | }; |
| @@ -396,11 +391,19 @@ static void checkJobs(struct jobSet *jobList) | |||
| 396 | static int getCommand(FILE * source, char *command) | 391 | static int getCommand(FILE * source, char *command) |
| 397 | { | 392 | { |
| 398 | if (source == stdin) { | 393 | if (source == stdin) { |
| 399 | fprintf(stdout, "BBSHELL %s %s", cwd, prompt); | ||
| 400 | fflush(stdout); | ||
| 401 | #ifdef BB_FEATURE_SH_COMMAND_EDITING | 394 | #ifdef BB_FEATURE_SH_COMMAND_EDITING |
| 402 | cmdedit_read_input(fileno(stdin), fileno(stdout), command); | 395 | int len; |
| 396 | char *promptStr; | ||
| 397 | len=fprintf(stdout, "BBSHELL %s %s", cwd, prompt); | ||
| 398 | fflush(stdout); | ||
| 399 | promptStr=(char*)malloc(sizeof(char)*(len+1)); | ||
| 400 | sprintf(promptStr, "BBSHELL %s %s", cwd, prompt); | ||
| 401 | cmdedit_read_input(promptStr, fileno(stdin), fileno(stdout), command); | ||
| 402 | free( promptStr); | ||
| 403 | return 0; | 403 | return 0; |
| 404 | #else | ||
| 405 | fprintf(stdout, "%s %s", cwd, prompt); | ||
| 406 | fflush(stdout); | ||
| 404 | #endif | 407 | #endif |
| 405 | } | 408 | } |
| 406 | 409 | ||
