BeRTOS
|
00001 00070 #include "readline.h" 00071 00072 #include <cfg/compiler.h> 00073 #include <cfg/debug.h> 00074 00075 #include <stdio.h> 00076 00078 #define DEBUG_UNIT_TEST 0 00079 00081 #define DEBUG_DUMP_HISTORY 0 00082 00083 00085 enum RL_KEYS { 00086 SPECIAL_KEYS = 0x1000, 00087 00088 /* 00089 * Three byte keys: 00090 * ################# 00091 * UpArrow: 0x1B 0x5B 0X41 00092 * DownArrow: 0x1B 0x5B 0X42 00093 * RightArrow: 0x1B 0x5B 0x43 00094 * LeftArrow: 0x1b 0x5B 0x44 00095 * Beak(Pause): 0x1b 0x5B 0x50 00096 */ 00097 KEY_UP_ARROW, 00098 KEY_DOWN_ARROW, 00099 KEY_LEFT_ARROW, 00100 KEY_RIGHT_ARROW, 00101 KEY_PAUSE, 00102 00103 /* 00104 * Four byte keys: 00105 * ################ 00106 * F1: 0x1b 0x5B 0x5B 0x41 00107 * F2: 0x1b 0x5B 0x5B 0x42 00108 * F3: 0x1b 0x5B 0x5B 0x43 00109 * F4: 0x1b 0x5B 0x5B 0x44 00110 * F5: 0x1b 0x5B 0x5B 0x45 00111 * Ins: 0x1b 0x5B 0x32 0x7E 00112 * Home: 0x1b 0x5B 0x31 0x7E 00113 * PgUp: 0x1b 0x5B 0x35 0x7E 00114 * Del: 0x1b 0x5B 0x33 0x7E 00115 * End: 0x1b 0x5B 0x34 0x7E 00116 * PgDn: 0x1b 0x5B 0x36 0x7E 00117 */ 00118 KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, 00119 KEY_INS, KEY_HOME, KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN, 00120 00121 /* 00122 * Five byte keys: 00123 * ################ 00124 * F6: 0x1b 0x5B 0x31 0x37 0x7E 00125 * F7: 0x1b 0x5B 0x31 0x38 0x7E 00126 * F8: 0x1b 0x5B 0x31 0x39 0x7E 00127 * F9: 0x1b 0x5B 0x32 0x30 0x7E 00128 * F10: 0x1b 0x5B 0x32 0x31 0x7E 00129 * F11: 0x1b 0x5B 0x32 0x33 0x7E 00130 * F12: 0x1b 0x5B 0x32 0x34 0x7E 00131 */ 00132 KEY_F6, KEY_F7, KEY_F8, KEY_F9, 00133 KEY_F10, KEY_F11, KEY_F12, 00134 }; 00135 00139 #define IS_WORD_SEPARATOR(c) ((c) == ' ' || (c) == '\0') 00140 00142 INLINE void rl_puts(const struct RLContext* ctx, const char* txt) 00143 { 00144 if (!ctx->put) 00145 return; 00146 00147 while (*txt) 00148 ctx->put(*txt++, ctx->put_param); 00149 } 00150 00152 INLINE void rl_putc(const struct RLContext* ctx, char ch) 00153 { 00154 if (ctx->put) 00155 ctx->put(ch, ctx->put_param); 00156 } 00157 00162 static bool rl_getc(const struct RLContext* ctx, int* ch) 00163 { 00164 int c = ctx->get(ctx->get_param); 00165 00166 if (c == EOF) 00167 { 00168 if (ctx->clear) 00169 ctx->clear(ctx->clear_param); 00170 00171 return false; 00172 } 00173 00174 if (c == 0x1B) 00175 { 00176 // Unknown ESC sequence. Ignore it and read 00177 // return next character. 00178 if (ctx->get(ctx->get_param) != 0x5B) 00179 return rl_getc(ctx, ch); 00180 00181 /* To be added: 00182 * Home: 0x1b 0x5B 0x31 0x7E 00183 * F6: 0x1b 0x5B 0x31 0x37 0x7E 00184 * F7: 0x1b 0x5B 0x31 0x38 0x7E 00185 * F8: 0x1b 0x5B 0x31 0x39 0x7E 00186 * Ins: 0x1b 0x5B 0x32 0x7E 00187 * F9: 0x1b 0x5B 0x32 0x30 0x7E 00188 * F10: 0x1b 0x5B 0x32 0x31 0x7E 00189 * F11: 0x1b 0x5B 0x32 0x33 0x7E 00190 * F12: 0x1b 0x5B 0x32 0x34 0x7E 00191 * Del: 0x1b 0x5B 0x33 0x7E 00192 * End: 0x1b 0x5B 0x34 0x7E 00193 * PgUp: 0x1b 0x5B 0x35 0x7E 00194 * PgDn: 0x1b 0x5B 0x36 0x7E 00195 */ 00196 00197 c = ctx->get(ctx->get_param); 00198 switch (c) 00199 { 00200 case 0x41: c = KEY_UP_ARROW; break; 00201 case 0x42: c = KEY_DOWN_ARROW; break; 00202 case 0x43: c = KEY_RIGHT_ARROW; break; 00203 case 0x44: c = KEY_LEFT_ARROW; break; 00204 case 0x50: c = KEY_PAUSE; break; 00205 case 0x5B: 00206 c = ctx->get(ctx->get_param); 00207 switch (c) 00208 { 00209 case 0x41: c = KEY_F1; break; 00210 case 0x42: c = KEY_F2; break; 00211 case 0x43: c = KEY_F3; break; 00212 case 0x44: c = KEY_F4; break; 00213 case 0x45: c = KEY_F5; break; 00214 default: return rl_getc(ctx, ch); 00215 } 00216 break; 00217 default: return rl_getc(ctx, ch); 00218 } 00219 } 00220 00221 *ch = c; 00222 return true; 00223 } 00224 00225 INLINE void beep(struct RLContext* ctx) 00226 { 00227 rl_putc(ctx, '\a'); 00228 } 00229 00230 static bool pop_history(struct RLContext* ctx, int total_len) 00231 { 00232 // Compute the length of the first command (including terminator). 00233 int len = strlen(ctx->real_history+1)+1; 00234 00235 // (the first byte of the history should always be 0) 00236 ASSERT(ctx->real_history[0] == '\0'); 00237 00238 // If it is the only one in the history, do nothing 00239 if (len == total_len) 00240 return false; 00241 00242 // Overwrite the first command with the second one 00243 memmove(ctx->real_history, ctx->real_history+len, HISTORY_SIZE-len); 00244 00245 // Move back the ctx->buffer pointer so that all the indices are still valid 00246 ctx->history -= len; 00247 00248 return true; 00249 } 00250 00252 INLINE bool is_history_begin(struct RLContext* ctx, int i) 00253 { return ctx->history + i == ctx->real_history; } 00254 00256 INLINE bool is_history_end(struct RLContext* ctx, int i) 00257 { return ctx->history + i == ctx->real_history + HISTORY_SIZE; } 00258 00260 INLINE bool is_history_past_end(struct RLContext* ctx, int i) 00261 { return ctx->history + i >= ctx->real_history + HISTORY_SIZE; } 00262 00270 static bool insert_chars(struct RLContext* ctx, size_t *curpos, const char* ch, int num_chars) 00271 { 00272 ASSERT(!is_history_past_end(ctx, *curpos)); 00273 00274 while (is_history_past_end(ctx, *curpos+num_chars+1)) 00275 { 00276 if (!pop_history(ctx, *curpos)) 00277 return false; 00278 } 00279 00280 while (num_chars--) 00281 ctx->history[++(*curpos)] = *ch++; 00282 00283 ASSERT(!is_history_past_end(ctx, *curpos + 1)); 00284 ctx->history[*curpos+1] = '\0'; 00285 return true; 00286 } 00287 00289 static bool insert_char(struct RLContext* ctx, size_t *curpos, char ch) 00290 { 00291 return insert_chars(ctx, curpos, &ch, 1); 00292 } 00293 00294 #if DEBUG_DUMP_HISTORY 00295 00296 static void dump_history(struct RLContext* ctx) 00297 { 00298 int k; 00299 char buf[8]; 00300 ASSERT(ctx->real_history[0] == '\0'); 00301 rl_puts(ctx, "History dump:"); 00302 rl_puts(ctx, "\r\n"); 00303 for (k = 1; 00304 ctx->real_history + k != ctx->history + ctx->history_pos + 1; 00305 k += strlen(&ctx->real_history[k]) + 1) 00306 { 00307 rl_puts(ctx, &ctx->real_history[k]); 00308 rl_puts(ctx, "\r\n"); 00309 } 00310 00311 sprintf(buf, "%d\r\n", ctx->history_pos + (ctx->history - ctx->real_history)); 00312 rl_puts(ctx, buf); 00313 } 00314 #endif /* DEBUG_DUMP_HISTORY */ 00315 00317 static bool complete_word(struct RLContext *ctx, size_t *curpos) 00318 { 00319 const char* completed_word; 00320 size_t wstart; 00321 00322 // If the current character is a separator, 00323 // there is nothing to complete 00324 wstart = *curpos; 00325 if (IS_WORD_SEPARATOR(ctx->history[wstart])) 00326 { 00327 beep(ctx); 00328 return false; 00329 } 00330 00331 // Find the separator before the current word 00332 do 00333 --wstart; 00334 while (!IS_WORD_SEPARATOR(ctx->history[wstart])); 00335 00336 // Complete the word through the hook 00337 completed_word = ctx->match(ctx->match_param, ctx->history + wstart + 1, *curpos - wstart); 00338 if (!completed_word) 00339 return false; 00340 00341 // Move back the terminal cursor to the separator 00342 while (*curpos != wstart) 00343 { 00344 rl_putc(ctx, '\b'); 00345 --*curpos; 00346 } 00347 00348 // Insert the completed command 00349 insert_chars(ctx, curpos, completed_word, strlen(completed_word)); 00350 rl_puts(ctx, completed_word); 00351 insert_char(ctx, curpos, ' '); 00352 rl_putc(ctx, ' '); 00353 00354 return true; 00355 } 00356 00357 void rl_refresh(struct RLContext* ctx) 00358 { 00359 rl_puts(ctx, "\r\n"); 00360 if (ctx->prompt) 00361 rl_puts(ctx, ctx->prompt); 00362 rl_puts(ctx, ctx->history + ctx->history_pos + 1); 00363 } 00364 00365 const char* rl_readline(struct RLContext* ctx) 00366 { 00367 while (1) 00368 { 00369 char ch; 00370 int c; 00371 00372 ASSERT(ctx->history - ctx->real_history + ctx->line_pos < HISTORY_SIZE); 00373 00374 if (!rl_getc(ctx, &c)) 00375 return NULL; 00376 00377 // Just ignore special keys for now 00378 if (c > SPECIAL_KEYS) 00379 continue; 00380 00381 if (c == '\t') 00382 { 00383 // Ask the match hook if available 00384 if (!ctx->match) 00385 return NULL; 00386 00387 complete_word(ctx, &ctx->line_pos); 00388 continue; 00389 } 00390 00391 // Backspace cancels a character, or it is ignored if at 00392 // the start of the line 00393 if (c == '\b') 00394 { 00395 if (ctx->history[ctx->line_pos] != '\0') 00396 { 00397 --ctx->line_pos; 00398 rl_puts(ctx, "\b \b"); 00399 } 00400 continue; 00401 } 00402 00403 if (c == '\r' || c == '\n') 00404 { 00405 rl_puts(ctx, "\r\n"); 00406 break; 00407 } 00408 00409 00410 // Add a character to the buffer, if possible 00411 ch = (char)c; 00412 ASSERT2(ch == c, "a special key was not properly handled"); 00413 if (insert_chars(ctx, &ctx->line_pos, &ch, 1)) 00414 rl_putc(ctx, ch); 00415 else 00416 beep(ctx); 00417 } 00418 00419 ctx->history_pos = ctx->line_pos + 1; 00420 while (ctx->history[ctx->line_pos] != '\0') 00421 --ctx->line_pos; 00422 00423 // Do not store empty lines in the history 00424 if (ctx->line_pos == ctx->history_pos - 1) 00425 ctx->history_pos -= 1; 00426 00427 #if DEBUG_DUMP_HISTORY 00428 dump_history(ctx); 00429 #endif 00430 00431 const char *buf = &ctx->history[ctx->line_pos + 1]; 00432 00433 ctx->line_pos = ctx->history_pos; 00434 00435 if (ctx->prompt) 00436 rl_puts(ctx, ctx->prompt); 00437 00438 insert_chars(ctx, &ctx->line_pos, NULL, 0); 00439 00440 // Since the current pointer now points to the separator, we need 00441 // to return the first character 00442 return buf; 00443 } 00444 00445 00446 #if DEBUG_UNIT_TEST 00447 00449 void rl_test(void); 00450 00451 #if HISTORY_SIZE != 32 00452 #error This test needs HISTORY_SIZE to be set at 32 00453 #endif 00454 00455 static struct RLContext test_ctx; 00456 00457 static char* test_getc_ptr; 00458 static int test_getc(void* data) 00459 { 00460 return *test_getc_ptr++; 00461 } 00462 00467 static bool do_test(char* input_buffer, char* expected_history) 00468 { 00469 rl_init_ctx(&test_ctx); 00470 rl_sethook_get(&test_ctx, test_getc, NULL); 00471 00472 test_getc_ptr = input_buffer; 00473 while (*test_getc_ptr) 00474 rl_readline(&test_ctx); 00475 00476 if (memcmp(test_ctx.real_history, expected_history, HISTORY_SIZE) != 0) 00477 { 00478 ASSERT2(0, "history compare failed"); 00479 return false; 00480 } 00481 00482 return true; 00483 } 00484 00485 void rl_test(void) 00486 { 00487 char* test1_in = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n"; 00488 char test1_hist[HISTORY_SIZE] = "\0l\0m\0n\0o\0p\0q\0r\0s\0t\0u\0v\0w\0x\0y\0z"; 00489 00490 if (!do_test(test1_in, test1_hist)) 00491 return; 00492 00493 kprintf("rl_test successful\n"); 00494 } 00495 00496 #endif /* DEBUG_UNIT_TEST */ 00497