15f096d7f406350b399d8645e146e82747d09277
[libcmdline.git] / src / lib / cmdline_rdline.c
1 /*-
2  * Copyright (c) <2010>, Intel Corporation
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * - Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * - Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in
14  *   the documentation and/or other materials provided with the
15  *   distribution.
16  *
17  * - Neither the name of Intel Corporation nor the names of its
18  *   contributors may be used to endorse or promote products derived
19  *   from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
32  * OF THE POSSIBILITY OF SUCH DAMAGE.
33  */
34
35 /*
36  * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
37  * All rights reserved.
38  * Redistribution and use in source and binary forms, with or without
39  * modification, are permitted provided that the following conditions are met:
40  *
41  *     * Redistributions of source code must retain the above copyright
42  *       notice, this list of conditions and the following disclaimer.
43  *     * Redistributions in binary form must reproduce the above copyright
44  *       notice, this list of conditions and the following disclaimer in the
45  *       documentation and/or other materials provided with the distribution.
46  *     * Neither the name of the University of California, Berkeley nor the
47  *       names of its contributors may be used to endorse or promote products
48  *       derived from this software without specific prior written permission.
49  *
50  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
51  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
52  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
54  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
55  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
56  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
57  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
58  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
59  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
60  */
61
62 #include <stdlib.h>
63 #include <stdio.h>
64 #include <stdint.h>
65 #include <string.h>
66 #include <stdarg.h>
67 #include <ctype.h>
68
69 #include "cmdline_cirbuf.h"
70 #include "cmdline_rdline.h"
71
72 static void rdline_puts(struct rdline *rdl, const char *buf);
73 static void rdline_miniprintf(struct rdline *rdl,
74                               const char *buf, unsigned int val);
75
76 #ifndef NO_RDLINE_HISTORY
77 static void rdline_remove_old_history_item(struct rdline *rdl);
78 static void rdline_remove_first_history_item(struct rdline *rdl);
79 static unsigned int rdline_get_history_size(struct rdline *rdl);
80 #endif /* !NO_RDLINE_HISTORY */
81
82
83 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
84  * own. */
85 static int
86 isblank2(char c)
87 {
88         if (c == ' ' ||
89             c == '\t' )
90                 return 1;
91         return 0;
92 }
93
94 void
95 rdline_init(struct rdline *rdl,
96                  rdline_write_char_t *write_char,
97                  rdline_validate_t *validate,
98                  rdline_complete_t *complete)
99 {
100         memset(rdl, 0, sizeof(*rdl));
101         rdl->validate = validate;
102         rdl->complete = complete;
103         rdl->write_char = write_char;
104         rdl->status = RDLINE_INIT;
105 #ifndef NO_RDLINE_HISTORY
106         cirbuf_init(&rdl->history, rdl->history_buf, 0, RDLINE_HISTORY_BUF_SIZE);
107 #endif /* !NO_RDLINE_HISTORY */
108 }
109
110 void
111 rdline_newline(struct rdline *rdl, const char *prompt)
112 {
113         unsigned int i;
114
115         vt100_init(&rdl->vt100);
116         cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
117         cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
118
119         if (prompt != rdl->prompt)
120                 memcpy(rdl->prompt, prompt, sizeof(rdl->prompt)-1);
121         rdl->prompt_size = strlen(prompt);
122
123         for (i=0 ; i<rdl->prompt_size ; i++)
124                 rdl->write_char(rdl, rdl->prompt[i]);
125         rdl->status = RDLINE_RUNNING;
126
127 #ifndef NO_RDLINE_HISTORY
128         rdl->history_cur_line = -1;
129 #endif /* !NO_RDLINE_HISTORY */
130 }
131
132 void
133 rdline_stop(struct rdline *rdl)
134 {
135         rdl->status = RDLINE_INIT;
136 }
137
138 void
139 rdline_restart(struct rdline *rdl)
140 {
141         rdl->status = RDLINE_RUNNING;
142 }
143
144 const char *
145 rdline_get_buffer(struct rdline *rdl)
146 {
147         unsigned int len_l, len_r;
148         cirbuf_align_left(&rdl->left);
149         cirbuf_align_left(&rdl->right);
150
151         len_l = CIRBUF_GET_LEN(&rdl->left);
152         len_r = CIRBUF_GET_LEN(&rdl->right);
153         memcpy(rdl->left_buf+len_l, rdl->right_buf, len_r);
154
155         rdl->left_buf[len_l + len_r] = '\n';
156         rdl->left_buf[len_l + len_r + 1] = '\0';
157         return rdl->left_buf;
158 }
159
160 static void
161 display_right_buffer(struct rdline *rdl)
162 {
163         unsigned int i;
164         char tmp;
165
166         rdline_puts(rdl, vt100_clear_right);
167         if (!CIRBUF_IS_EMPTY(&rdl->right)) {
168                 CIRBUF_FOREACH(&rdl->right, i, tmp) {
169                         rdl->write_char(rdl, tmp);
170                 }
171                 rdline_miniprintf(rdl, vt100_multi_left,
172                                   CIRBUF_GET_LEN(&rdl->right));
173         }
174 }
175
176 void
177 rdline_redisplay(struct rdline *rdl)
178 {
179         unsigned int i;
180         char tmp;
181
182         rdline_puts(rdl, vt100_home);
183         for (i=0 ; i<rdl->prompt_size ; i++)
184                 rdl->write_char(rdl, rdl->prompt[i]);
185         CIRBUF_FOREACH(&rdl->left, i, tmp) {
186                 rdl->write_char(rdl, tmp);
187         }
188         display_right_buffer(rdl);
189 }
190
191 int
192 rdline_char_in(struct rdline *rdl, char c)
193 {
194         unsigned int i;
195         int cmd;
196         char tmp;
197 #ifndef NO_RDLINE_HISTORY
198         char *buf;
199 #endif
200
201         if (rdl->status != RDLINE_RUNNING)
202                 return RDLINE_RES_NOT_RUNNING;
203
204         cmd = vt100_parser(&rdl->vt100, c);
205         if (cmd == -2)
206                 return RDLINE_RES_SUCCESS;
207
208         if (cmd >= 0) {
209                 switch (cmd) {
210                 case CMDLINE_KEY_CTRL_B:
211                 case CMDLINE_KEY_LEFT_ARR:
212                         if (CIRBUF_IS_EMPTY(&rdl->left))
213                                 break;
214                         tmp = cirbuf_get_tail(&rdl->left);
215                         cirbuf_del_tail(&rdl->left);
216                         cirbuf_add_head(&rdl->right, tmp);
217                         rdline_puts(rdl, vt100_left_arr);
218                         break;
219
220                 case CMDLINE_KEY_CTRL_F:
221                 case CMDLINE_KEY_RIGHT_ARR:
222                         if (CIRBUF_IS_EMPTY(&rdl->right))
223                                 break;
224                         tmp = cirbuf_get_head(&rdl->right);
225                         cirbuf_del_head(&rdl->right);
226                         cirbuf_add_tail(&rdl->left, tmp);
227                         rdline_puts(rdl, vt100_right_arr);
228                         break;
229
230                 case CMDLINE_KEY_WLEFT:
231                         while (! CIRBUF_IS_EMPTY(&rdl->left) &&
232                                (tmp = cirbuf_get_tail(&rdl->left)) &&
233                                isblank2(tmp)) {
234                                 rdline_puts(rdl, vt100_left_arr);
235                                 cirbuf_del_tail(&rdl->left);
236                                 cirbuf_add_head(&rdl->right, tmp);
237                         }
238                         while (! CIRBUF_IS_EMPTY(&rdl->left) &&
239                                (tmp = cirbuf_get_tail(&rdl->left)) &&
240                                !isblank2(tmp)) {
241                                 rdline_puts(rdl, vt100_left_arr);
242                                 cirbuf_del_tail(&rdl->left);
243                                 cirbuf_add_head(&rdl->right, tmp);
244                         }
245                         break;
246
247                 case CMDLINE_KEY_WRIGHT:
248                         while (! CIRBUF_IS_EMPTY(&rdl->right) &&
249                                (tmp = cirbuf_get_head(&rdl->right)) &&
250                                isblank2(tmp)) {
251                                 rdline_puts(rdl, vt100_right_arr);
252                                 cirbuf_del_head(&rdl->right);
253                                 cirbuf_add_tail(&rdl->left, tmp);
254                         }
255                         while (! CIRBUF_IS_EMPTY(&rdl->right) &&
256                                (tmp = cirbuf_get_head(&rdl->right)) &&
257                                !isblank2(tmp)) {
258                                 rdline_puts(rdl, vt100_right_arr);
259                                 cirbuf_del_head(&rdl->right);
260                                 cirbuf_add_tail(&rdl->left, tmp);
261                         }
262                         break;
263
264                 case CMDLINE_KEY_BKSPACE:
265                         if(!cirbuf_del_tail_safe(&rdl->left)) {
266                                 rdline_puts(rdl, vt100_bs);
267                                 display_right_buffer(rdl);
268                         }
269                         break;
270
271                 case CMDLINE_KEY_META_BKSPACE:
272                         while (! CIRBUF_IS_EMPTY(&rdl->left) && isblank2(cirbuf_get_tail(&rdl->left))) {
273                                 rdline_puts(rdl, vt100_bs);
274                                 cirbuf_del_tail(&rdl->left);
275                         }
276                         while (! CIRBUF_IS_EMPTY(&rdl->left) && !isblank2(cirbuf_get_tail(&rdl->left))) {
277                                 rdline_puts(rdl, vt100_bs);
278                                 cirbuf_del_tail(&rdl->left);
279                         }
280                         display_right_buffer(rdl);
281                         break;
282
283                 case CMDLINE_KEY_SUPPR:
284                 case CMDLINE_KEY_CTRL_D:
285                         if (cmd == CMDLINE_KEY_CTRL_D &&
286                             CIRBUF_IS_EMPTY(&rdl->left) &&
287                             CIRBUF_IS_EMPTY(&rdl->right)) {
288                                 return RDLINE_RES_EOF;
289                         }
290                         if (!cirbuf_del_head_safe(&rdl->right)) {
291                                 display_right_buffer(rdl);
292                         }
293                         break;
294
295                 case CMDLINE_KEY_CTRL_A:
296                         if (CIRBUF_IS_EMPTY(&rdl->left))
297                                 break;
298                         rdline_miniprintf(rdl, vt100_multi_left,
299                                             CIRBUF_GET_LEN(&rdl->left));
300                         while (! CIRBUF_IS_EMPTY(&rdl->left)) {
301                                 tmp = cirbuf_get_tail(&rdl->left);
302                                 cirbuf_del_tail(&rdl->left);
303                                 cirbuf_add_head(&rdl->right, tmp);
304                         }
305                         break;
306
307                 case CMDLINE_KEY_CTRL_E:
308                         if (CIRBUF_IS_EMPTY(&rdl->right))
309                                 break;
310                         rdline_miniprintf(rdl, vt100_multi_right,
311                                             CIRBUF_GET_LEN(&rdl->right));
312                         while (! CIRBUF_IS_EMPTY(&rdl->right)) {
313                                 tmp = cirbuf_get_head(&rdl->right);
314                                 cirbuf_del_head(&rdl->right);
315                                 cirbuf_add_tail(&rdl->left, tmp);
316                         }
317                         break;
318
319 #ifndef NO_RDLINE_KILL_BUF
320                 case CMDLINE_KEY_CTRL_K:
321                         cirbuf_get_buf_head(&rdl->right, rdl->kill_buf, RDLINE_BUF_SIZE);
322                         rdl->kill_size = CIRBUF_GET_LEN(&rdl->right);
323                         cirbuf_del_buf_head(&rdl->right, rdl->kill_size);
324                         rdline_puts(rdl, vt100_clear_right);
325                         break;
326
327                 case CMDLINE_KEY_CTRL_Y:
328                         i=0;
329                         while(CIRBUF_GET_LEN(&rdl->right) + CIRBUF_GET_LEN(&rdl->left) <
330                               RDLINE_BUF_SIZE &&
331                               i < rdl->kill_size) {
332                                 cirbuf_add_tail(&rdl->left, rdl->kill_buf[i]);
333                                 rdl->write_char(rdl, rdl->kill_buf[i]);
334                                 i++;
335                         }
336                         display_right_buffer(rdl);
337                         break;
338 #endif /* !NO_RDLINE_KILL_BUF */
339
340                 case CMDLINE_KEY_CTRL_C:
341                         rdline_puts(rdl, "\r\n");
342                         rdline_newline(rdl, rdl->prompt);
343                         break;
344
345                 case CMDLINE_KEY_CTRL_L:
346                         rdline_redisplay(rdl);
347                         break;
348
349                 case CMDLINE_KEY_TAB:
350                 case CMDLINE_KEY_HELP:
351                         cirbuf_align_left(&rdl->left);
352                         rdl->left_buf[CIRBUF_GET_LEN(&rdl->left)] = '\0';
353                         if (rdl->complete) {
354                                 char tmp_buf[BUFSIZ];
355                                 int complete_state;
356                                 int ret;
357                                 unsigned int tmp_size;
358
359                                 if (cmd == CMDLINE_KEY_TAB)
360                                         complete_state = 0;
361                                 else
362                                         complete_state = -1;
363
364                                 /* see in parse.h for help on complete() */
365                                 ret = rdl->complete(rdl, rdl->left_buf,
366                                                     tmp_buf, sizeof(tmp_buf),
367                                                     &complete_state);
368                                 /* no completion or error */
369                                 if (ret <= 0) {
370                                         return RDLINE_RES_COMPLETE;
371                                 }
372
373                                 tmp_size = strlen(tmp_buf);
374                                 /* add chars */
375                                 if (ret == RDLINE_RES_COMPLETE) {
376                                         i=0;
377                                         while(CIRBUF_GET_LEN(&rdl->right) + CIRBUF_GET_LEN(&rdl->left) <
378                                               RDLINE_BUF_SIZE &&
379                                               i < tmp_size) {
380                                                 cirbuf_add_tail(&rdl->left, tmp_buf[i]);
381                                                 rdl->write_char(rdl, tmp_buf[i]);
382                                                 i++;
383                                         }
384                                         display_right_buffer(rdl);
385                                         return RDLINE_RES_COMPLETE; /* ?? */
386                                 }
387
388                                 /* choice */
389                                 rdline_puts(rdl, "\r\n");
390                                 while (ret) {
391                                         rdl->write_char(rdl, ' ');
392                                         for (i=0 ; tmp_buf[i] ; i++)
393                                                 rdl->write_char(rdl, tmp_buf[i]);
394                                         rdline_puts(rdl, "\r\n");
395                                         ret = rdl->complete(rdl, rdl->left_buf,
396                                                             tmp_buf, sizeof(tmp_buf),
397                                                             &complete_state);
398                                 }
399
400                                 rdline_redisplay(rdl);
401                         }
402                         return RDLINE_RES_COMPLETE;
403
404                 case CMDLINE_KEY_RETURN:
405                 case CMDLINE_KEY_RETURN2:
406                         rdline_get_buffer(rdl);
407                         rdl->status = RDLINE_INIT;
408                         rdline_puts(rdl, "\r\n");
409 #ifndef NO_RDLINE_HISTORY
410                         if (rdl->history_cur_line != -1)
411                                 rdline_remove_first_history_item(rdl);
412 #endif
413
414                         if (rdl->validate)
415                                 rdl->validate(rdl, rdl->left_buf, CIRBUF_GET_LEN(&rdl->left)+2);
416                         return RDLINE_RES_VALIDATED;
417
418 #ifndef NO_RDLINE_HISTORY
419                 case CMDLINE_KEY_UP_ARR:
420                         if (rdl->history_cur_line == 0) {
421                                 rdline_remove_first_history_item(rdl);
422                         }
423                         if (rdl->history_cur_line <= 0) {
424                                 rdline_add_history(rdl, rdline_get_buffer(rdl));
425                                 rdl->history_cur_line = 0;
426                         }
427
428                         buf = rdline_get_history_item(rdl, rdl->history_cur_line + 1);
429                         if (!buf)
430                                 break;
431
432                         rdl->history_cur_line ++;
433                         vt100_init(&rdl->vt100);
434                         cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
435                         cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
436                         cirbuf_add_buf_tail(&rdl->left, buf, strlen(buf));
437                         rdline_redisplay(rdl);
438                         break;
439
440                 case CMDLINE_KEY_DOWN_ARR:
441                         if (rdl->history_cur_line - 1 < 0)
442                                 break;
443
444                         rdl->history_cur_line --;
445                         buf = rdline_get_history_item(rdl, rdl->history_cur_line);
446                         if (!buf)
447                                 break;
448                         vt100_init(&rdl->vt100);
449                         cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
450                         cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
451                         cirbuf_add_buf_tail(&rdl->left, buf, strlen(buf));
452                         rdline_redisplay(rdl);
453
454                         break;
455 #endif /* !NO_RDLINE_HISTORY */
456
457
458                 default:
459                         break;
460                 }
461
462                 return RDLINE_RES_SUCCESS;
463         }
464
465         if (!isprint((int)c))
466                 return RDLINE_RES_SUCCESS;
467
468         /* standard chars */
469         if (CIRBUF_GET_LEN(&rdl->left) + CIRBUF_GET_LEN(&rdl->right) >= RDLINE_BUF_SIZE)
470                 return RDLINE_RES_SUCCESS;
471
472         if (cirbuf_add_tail_safe(&rdl->left, c))
473                 return RDLINE_RES_SUCCESS;
474
475         rdl->write_char(rdl, c);
476         display_right_buffer(rdl);
477
478         return RDLINE_RES_SUCCESS;
479 }
480
481
482 /* HISTORY */
483
484 #ifndef NO_RDLINE_HISTORY
485 static void
486 rdline_remove_old_history_item(struct rdline * rdl)
487 {
488         char tmp;
489
490         while (! CIRBUF_IS_EMPTY(&rdl->history) ) {
491                 tmp = cirbuf_get_head(&rdl->history);
492                 cirbuf_del_head(&rdl->history);
493                 if (!tmp)
494                         break;
495         }
496 }
497
498 static void
499 rdline_remove_first_history_item(struct rdline * rdl)
500 {
501         char tmp;
502
503         if ( CIRBUF_IS_EMPTY(&rdl->history) ) {
504                 return;
505         }
506         else {
507                 cirbuf_del_tail(&rdl->history);
508         }
509
510         while (! CIRBUF_IS_EMPTY(&rdl->history) ) {
511                 tmp = cirbuf_get_tail(&rdl->history);
512                 if (!tmp)
513                         break;
514                 cirbuf_del_tail(&rdl->history);
515         }
516 }
517
518 static unsigned int
519 rdline_get_history_size(struct rdline * rdl)
520 {
521         unsigned int i, tmp, ret=0;
522
523         CIRBUF_FOREACH(&rdl->history, i, tmp) {
524                 if (tmp == 0)
525                         ret ++;
526         }
527
528         return ret;
529 }
530
531 char *
532 rdline_get_history_item(struct rdline * rdl, unsigned int idx)
533 {
534         unsigned int len, i, tmp;
535
536         len = rdline_get_history_size(rdl);
537         if ( idx >= len ) {
538                 return NULL;
539         }
540
541         cirbuf_align_left(&rdl->history);
542
543         CIRBUF_FOREACH(&rdl->history, i, tmp) {
544                 if ( idx == len - 1) {
545                         return rdl->history_buf + i;
546                 }
547                 if (tmp == 0)
548                         len --;
549         }
550
551         return NULL;
552 }
553
554 int
555 rdline_add_history(struct rdline * rdl, const char * buf)
556 {
557         unsigned int len, i;
558
559         len = strlen(buf);
560         for (i=0; i<len ; i++) {
561                 if (buf[i] == '\n') {
562                         len = i;
563                         break;
564                 }
565         }
566
567         if ( len >= RDLINE_HISTORY_BUF_SIZE )
568                 return -1;
569
570         while ( len >= CIRBUF_GET_FREELEN(&rdl->history) ) {
571                 rdline_remove_old_history_item(rdl);
572         }
573
574         cirbuf_add_buf_tail(&rdl->history, buf, len);
575         cirbuf_add_tail(&rdl->history, 0);
576
577         return 0;
578 }
579
580 void
581 rdline_clear_history(struct rdline * rdl)
582 {
583         cirbuf_init(&rdl->history, rdl->history_buf, 0, RDLINE_HISTORY_BUF_SIZE);
584 }
585
586 #else /* !NO_RDLINE_HISTORY */
587
588 int rdline_add_history(struct rdline * rdl, const char * buf) {return -1;}
589 void rdline_clear_history(struct rdline * rdl) {}
590 char * rdline_get_history_item(struct rdline * rdl, unsigned int i) {return NULL;}
591
592
593 #endif /* !NO_RDLINE_HISTORY */
594
595
596 /* STATIC USEFUL FUNCS */
597
598 static void
599 rdline_puts(struct rdline * rdl, const char * buf)
600 {
601         char c;
602         while ( (c = *(buf++)) != '\0' ) {
603                 rdl->write_char(rdl, c);
604         }
605 }
606
607 /* a very very basic printf with one arg and one format 'u' */
608 static void
609 rdline_miniprintf(struct rdline *rdl, const char * buf, unsigned int val)
610 {
611         char c, started=0, div=100;
612
613         while ( (c=*(buf++)) ) {
614                 if (c != '%') {
615                         rdl->write_char(rdl, c);
616                         continue;
617                 }
618                 c = *(buf++);
619                 if (c != 'u') {
620                         rdl->write_char(rdl, '%');
621                         rdl->write_char(rdl, c);
622                         continue;
623                 }
624                 /* val is never more than 255 */
625                 while (div) {
626                         c = (char)(val / div);
627                         if (c || started) {
628                                 rdl->write_char(rdl, (char)(c+'0'));
629                                 started = 1;
630                         }
631                         val %= div;
632                         div /= 10;
633                 }
634         }
635 }
636