readline.c
Go to the documentation of this file.
00001 00039 #include "readline.h" 00040 00041 #include <cfg/compiler.h> 00042 #include <cfg/debug.h> 00043 00044 #include <stdio.h> 00045 00047 #define DEBUG_DUMP_HISTORY 0 00048 00050 enum RL_KEYS { 00051 SPECIAL_KEYS = 0x1000, 00052 00053 /* 00054 * Three byte keys: 00055 * ################# 00056 * UpArrow: 0x1B 0x5B 0X41 00057 * DownArrow: 0x1B 0x5B 0X42 00058 * RightArrow: 0x1B 0x5B 0x43 00059 * LeftArrow: 0x1b 0x5B 0x44 00060 * Beak(Pause): 0x1b 0x5B 0x50 00061 */ 00062 KEY_UP_ARROW, 00063 KEY_DOWN_ARROW, 00064 KEY_LEFT_ARROW, 00065 KEY_RIGHT_ARROW, 00066 KEY_PAUSE, 00067 00068 /* 00069 * Four byte keys: 00070 * ################ 00071 * F1: 0x1b 0x5B 0x5B 0x41 00072 * F2: 0x1b 0x5B 0x5B 0x42 00073 * F3: 0x1b 0x5B 0x5B 0x43 00074 * F4: 0x1b 0x5B 0x5B 0x44 00075 * F5: 0x1b 0x5B 0x5B 0x45 00076 * Ins: 0x1b 0x5B 0x32 0x7E 00077 * Home: 0x1b 0x5B 0x31 0x7E 00078 * PgUp: 0x1b 0x5B 0x35 0x7E 00079 * Del: 0x1b 0x5B 0x33 0x7E 00080 * End: 0x1b 0x5B 0x34 0x7E 00081 * PgDn: 0x1b 0x5B 0x36 0x7E 00082 */ 00083 KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, 00084 KEY_INS, KEY_HOME, KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN, 00085 00086 /* 00087 * Five byte keys: 00088 * ################ 00089 * F6: 0x1b 0x5B 0x31 0x37 0x7E 00090 * F7: 0x1b 0x5B 0x31 0x38 0x7E 00091 * F8: 0x1b 0x5B 0x31 0x39 0x7E 00092 * F9: 0x1b 0x5B 0x32 0x30 0x7E 00093 * F10: 0x1b 0x5B 0x32 0x31 0x7E 00094 * F11: 0x1b 0x5B 0x32 0x33 0x7E 00095 * F12: 0x1b 0x5B 0x32 0x34 0x7E 00096 */ 00097 KEY_F6, KEY_F7, KEY_F8, KEY_F9, 00098 KEY_F10, KEY_F11, KEY_F12, 00099 }; 00100 00104 #define IS_WORD_SEPARATOR(c) ((c) == ' ' || (c) == '\0') 00105 00107 INLINE void rl_puts(const struct RLContext* ctx, const char* txt) 00108 { 00109 if (!ctx->put) 00110 return; 00111 00112 while (*txt) 00113 ctx->put(*txt++, ctx->put_param); 00114 } 00115 00117 INLINE void rl_putc(const struct RLContext* ctx, char ch) 00118 { 00119 if (ctx->put) 00120 ctx->put(ch, ctx->put_param); 00121 } 00122 00128 static bool rl_getc(const struct RLContext* ctx, int* ch) 00129 { 00130 int c = ctx->get(ctx->get_param); 00131 00132 if (c == EOF) 00133 { 00134 if (ctx->clear) 00135 ctx->clear(ctx->clear_param); 00136 00137 return false; 00138 } 00139 00140 if (c == 0x1B) 00141 { 00142 // Unknown ESC sequence. Ignore it and read 00143 // return next character. 00144 if (ctx->get(ctx->get_param) != 0x5B) 00145 return rl_getc(ctx, ch); 00146 00147 /* 00148 * To be added: 00149 * Home: 0x1b 0x5B 0x31 0x7E 00150 * F6: 0x1b 0x5B 0x31 0x37 0x7E 00151 * F7: 0x1b 0x5B 0x31 0x38 0x7E 00152 * F8: 0x1b 0x5B 0x31 0x39 0x7E 00153 * Ins: 0x1b 0x5B 0x32 0x7E 00154 * F9: 0x1b 0x5B 0x32 0x30 0x7E 00155 * F10: 0x1b 0x5B 0x32 0x31 0x7E 00156 * F11: 0x1b 0x5B 0x32 0x33 0x7E 00157 * F12: 0x1b 0x5B 0x32 0x34 0x7E 00158 * Del: 0x1b 0x5B 0x33 0x7E 00159 * End: 0x1b 0x5B 0x34 0x7E 00160 * PgUp: 0x1b 0x5B 0x35 0x7E 00161 * PgDn: 0x1b 0x5B 0x36 0x7E 00162 */ 00163 00164 c = ctx->get(ctx->get_param); 00165 switch (c) 00166 { 00167 case 0x41: c = KEY_UP_ARROW; break; 00168 case 0x42: c = KEY_DOWN_ARROW; break; 00169 case 0x43: c = KEY_RIGHT_ARROW; break; 00170 case 0x44: c = KEY_LEFT_ARROW; break; 00171 case 0x50: c = KEY_PAUSE; break; 00172 case 0x5B: 00173 c = ctx->get(ctx->get_param); 00174 switch (c) 00175 { 00176 case 0x41: c = KEY_F1; break; 00177 case 0x42: c = KEY_F2; break; 00178 case 0x43: c = KEY_F3; break; 00179 case 0x44: c = KEY_F4; break; 00180 case 0x45: c = KEY_F5; break; 00181 default: return rl_getc(ctx, ch); 00182 } 00183 break; 00184 default: return rl_getc(ctx, ch); 00185 } 00186 } 00187 00188 *ch = c; 00189 return true; 00190 } 00191 00192 INLINE void beep(struct RLContext* ctx) 00193 { 00194 rl_putc(ctx, '\a'); 00195 } 00196 00197 static bool pop_history(struct RLContext* ctx, int total_len) 00198 { 00199 // Compute the length of the first command (including terminator). 00200 int len = strlen(ctx->real_history+1)+1; 00201 00202 // (the first byte of the history should always be 0) 00203 ASSERT(ctx->real_history[0] == '\0'); 00204 00205 // If it is the only one in the history, do nothing 00206 if (len == total_len) 00207 return false; 00208 00209 // Overwrite the first command with the second one 00210 memmove(ctx->real_history, ctx->real_history+len, HISTORY_SIZE-len); 00211 00212 // Move back the ctx->buffer pointer so that all the indices are still valid 00213 ctx->history -= len; 00214 00215 return true; 00216 } 00217 00219 INLINE bool is_history_begin(struct RLContext* ctx, int i) 00220 { return ctx->history + i == ctx->real_history; } 00221 00223 INLINE bool is_history_end(struct RLContext* ctx, int i) 00224 { return ctx->history + i == ctx->real_history + HISTORY_SIZE; } 00225 00227 INLINE bool is_history_past_end(struct RLContext* ctx, int i) 00228 { return ctx->history + i >= ctx->real_history + HISTORY_SIZE; } 00229 00239 static bool insert_chars(struct RLContext* ctx, size_t *curpos, const char* ch, int num_chars) 00240 { 00241 ASSERT(!is_history_past_end(ctx, *curpos)); 00242 00243 while (is_history_past_end(ctx, *curpos+num_chars+1)) 00244 { 00245 if (!pop_history(ctx, *curpos)) 00246 return false; 00247 } 00248 00249 while (num_chars--) 00250 ctx->history[++(*curpos)] = *ch++; 00251 00252 ASSERT(!is_history_past_end(ctx, *curpos + 1)); 00253 ctx->history[*curpos+1] = '\0'; 00254 return true; 00255 } 00256 00258 static bool insert_char(struct RLContext* ctx, size_t *curpos, char ch) 00259 { 00260 return insert_chars(ctx, curpos, &ch, 1); 00261 } 00262 00263 #if DEBUG_DUMP_HISTORY 00264 00265 static void dump_history(struct RLContext* ctx) 00266 { 00267 int k; 00268 char buf[8]; 00269 ASSERT(ctx->real_history[0] == '\0'); 00270 rl_puts(ctx, "History dump:"); 00271 rl_puts(ctx, "\r\n"); 00272 for (k = 1; 00273 ctx->real_history + k != ctx->history + ctx->history_pos + 1; 00274 k += strlen(&ctx->real_history[k]) + 1) 00275 { 00276 rl_puts(ctx, &ctx->real_history[k]); 00277 rl_puts(ctx, "\r\n"); 00278 } 00279 00280 sprintf(buf, "%d\r\n", ctx->history_pos + (ctx->history - ctx->real_history)); 00281 rl_puts(ctx, buf); 00282 } 00283 #endif /* DEBUG_DUMP_HISTORY */ 00284 00286 static bool complete_word(struct RLContext *ctx, size_t *curpos) 00287 { 00288 const char* completed_word; 00289 size_t wstart; 00290 00291 // If the current character is a separator, 00292 // there is nothing to complete 00293 wstart = *curpos; 00294 if (IS_WORD_SEPARATOR(ctx->history[wstart])) 00295 { 00296 beep(ctx); 00297 return false; 00298 } 00299 00300 // Find the separator before the current word 00301 do 00302 --wstart; 00303 while (!IS_WORD_SEPARATOR(ctx->history[wstart])); 00304 00305 // Complete the word through the hook 00306 completed_word = ctx->match(ctx->match_param, ctx->history + wstart + 1, *curpos - wstart); 00307 if (!completed_word) 00308 return false; 00309 00310 // Move back the terminal cursor to the separator 00311 while (*curpos != wstart) 00312 { 00313 rl_putc(ctx, '\b'); 00314 --*curpos; 00315 } 00316 00317 // Insert the completed command 00318 insert_chars(ctx, curpos, completed_word, strlen(completed_word)); 00319 rl_puts(ctx, completed_word); 00320 insert_char(ctx, curpos, ' '); 00321 rl_putc(ctx, ' '); 00322 00323 return true; 00324 } 00325 00326 void rl_refresh(struct RLContext* ctx) 00327 { 00328 rl_puts(ctx, "\r\n"); 00329 if (ctx->prompt) 00330 rl_puts(ctx, ctx->prompt); 00331 rl_puts(ctx, ctx->history + ctx->history_pos + 1); 00332 } 00333 00334 const char* rl_readline(struct RLContext* ctx) 00335 { 00336 while (1) 00337 { 00338 char ch; 00339 int c; 00340 00341 ASSERT(ctx->history - ctx->real_history + ctx->line_pos < HISTORY_SIZE); 00342 00343 if (!rl_getc(ctx, &c)) 00344 return NULL; 00345 00346 // Just ignore special keys for now 00347 if (c > SPECIAL_KEYS) 00348 continue; 00349 00350 if (c == '\t') 00351 { 00352 // Ask the match hook if available 00353 if (!ctx->match) 00354 return NULL; 00355 00356 complete_word(ctx, &ctx->line_pos); 00357 continue; 00358 } 00359 00360 // Backspace cancels a character, or it is ignored if at 00361 // the start of the line 00362 if (c == '\b') 00363 { 00364 if (ctx->history[ctx->line_pos] != '\0') 00365 { 00366 --ctx->line_pos; 00367 rl_puts(ctx, "\b \b"); 00368 } 00369 continue; 00370 } 00371 00372 if (c == '\r' || c == '\n') 00373 { 00374 rl_puts(ctx, "\r\n"); 00375 break; 00376 } 00377 00378 00379 // Add a character to the buffer, if possible 00380 ch = (char)c; 00381 ASSERT2(ch == c, "a special key was not properly handled"); 00382 if (insert_chars(ctx, &ctx->line_pos, &ch, 1)) 00383 rl_putc(ctx, ch); 00384 else 00385 beep(ctx); 00386 } 00387 00388 ctx->history_pos = ctx->line_pos + 1; 00389 while (ctx->history[ctx->line_pos] != '\0') 00390 --ctx->line_pos; 00391 00392 // Do not store empty lines in the history 00393 if (ctx->line_pos == ctx->history_pos - 1) 00394 ctx->history_pos -= 1; 00395 00396 #if DEBUG_DUMP_HISTORY 00397 dump_history(ctx); 00398 #endif 00399 00400 const char *buf = &ctx->history[ctx->line_pos + 1]; 00401 00402 ctx->line_pos = ctx->history_pos; 00403 00404 insert_chars(ctx, &ctx->line_pos, NULL, 0); 00405 00406 // Since the current pointer now points to the separator, we need 00407 // to return the first character 00408 return buf; 00409 } 00410
![(please configure the [header_logo] section in trac.ini)](/chrome/site/bertos_logo.png)