source: mainline/uspace/app/bdsh/input.c@ cedd33b

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since cedd33b was cedd33b, checked in by Jiri Svoboda <jiri@…>, 16 years ago

Cut (Ctrl-X) and select all (Ctrl-A).

  • Property mode set to 100644
File size: 16.3 KB
Line 
1/* Copyright (c) 2008, Tim Post <tinkertim@gmail.com>
2 * All rights reserved.
3 * Copyright (c) 2008, Jiri Svoboda - 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 are met:
7 *
8 * Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * Neither the name of the original program's authors nor the names of its
16 * contributors may be used to endorse or promote products derived from this
17 * software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <io/console.h>
36#include <io/keycode.h>
37#include <io/style.h>
38#include <io/color.h>
39#include <vfs/vfs.h>
40#include <clipboard.h>
41#include <macros.h>
42#include <errno.h>
43#include <assert.h>
44#include <bool.h>
45
46#include "config.h"
47#include "util.h"
48#include "scli.h"
49#include "input.h"
50#include "errors.h"
51#include "exec.h"
52
53#define HISTORY_LEN 10
54
55/** Text input field. */
56typedef struct {
57 /** Buffer holding text currently being edited */
58 wchar_t buffer[INPUT_MAX + 1];
59 /** Screen coordinates of the top-left corner of the text field */
60 int col0, row0;
61 /** Screen dimensions */
62 int con_cols, con_rows;
63 /** Number of characters in @c buffer */
64 int nc;
65 /** Caret position within buffer */
66 int pos;
67 /** Selection mark position within buffer */
68 int sel_start;
69
70 /** History (dynamically allocated strings) */
71 char *history[1 + HISTORY_LEN];
72 /** Number of entries in @c history, not counting [0] */
73 int hnum;
74 /** Current position in history */
75 int hpos;
76 /** Exit flag */
77 bool done;
78} tinput_t;
79
80/** Seek direction */
81typedef enum {
82 seek_backward = -1,
83 seek_forward = 1
84} seek_dir_t;
85
86static tinput_t tinput;
87
88static char *tinput_read(tinput_t *ti);
89static void tinput_insert_string(tinput_t *ti, const char *str);
90static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb);
91static bool tinput_sel_active(tinput_t *ti);
92static void tinput_sel_all(tinput_t *ti);
93static void tinput_sel_delete(tinput_t *ti);
94static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev);
95static void tinput_key_shift(tinput_t *ti, console_event_t *ev);
96static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev);
97static void tinput_key_unmod(tinput_t *ti, console_event_t *ev);
98static void tinput_pre_seek(tinput_t *ti, bool shift_held);
99static void tinput_post_seek(tinput_t *ti, bool shift_held);
100
101/* Tokenizes input from console, sees if the first word is a built-in, if so
102 * invokes the built-in entry point (a[0]) passing all arguments in a[] to
103 * the handler */
104int tok_input(cliuser_t *usr)
105{
106 char *cmd[WORD_MAX];
107 int n = 0, i = 0;
108 int rc = 0;
109 char *tmp;
110
111 if (NULL == usr->line)
112 return CL_EFAIL;
113
114 tmp = str_dup(usr->line);
115
116 cmd[n] = strtok(tmp, " ");
117 while (cmd[n] && n < WORD_MAX) {
118 cmd[++n] = strtok(NULL, " ");
119 }
120
121 /* We have rubbish */
122 if (NULL == cmd[0]) {
123 rc = CL_ENOENT;
124 goto finit;
125 }
126
127 /* Its a builtin command ? */
128 if ((i = (is_builtin(cmd[0]))) > -1) {
129 rc = run_builtin(i, cmd, usr);
130 goto finit;
131 /* Its a module ? */
132 } else if ((i = (is_module(cmd[0]))) > -1) {
133 rc = run_module(i, cmd);
134 goto finit;
135 }
136
137 /* See what try_exec thinks of it */
138 rc = try_exec(cmd[0], cmd);
139
140finit:
141 if (NULL != usr->line) {
142 free(usr->line);
143 usr->line = (char *) NULL;
144 }
145 if (NULL != tmp)
146 free(tmp);
147
148 return rc;
149}
150
151static void tinput_display_tail(tinput_t *ti, int start, int pad)
152{
153 static wchar_t dbuf[INPUT_MAX + 1];
154 int sa, sb;
155 int i, p;
156
157 tinput_sel_get_bounds(ti, &sa, &sb);
158
159 console_goto(fphone(stdout), (ti->col0 + start) % ti->con_cols,
160 ti->row0 + (ti->col0 + start) / ti->con_cols);
161 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
162
163 p = start;
164 if (p < sa) {
165 memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
166 dbuf[sa - p] = '\0';
167 printf("%ls", dbuf);
168 p = sa;
169 }
170
171 if (p < sb) {
172 fflush(stdout);
173 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_RED, 0);
174 memcpy(dbuf, ti->buffer + p,
175 (sb - p) * sizeof(wchar_t));
176 dbuf[sb - p] = '\0';
177 printf("%ls", dbuf);
178 p = sb;
179 }
180
181 fflush(stdout);
182 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
183
184 if (p < ti->nc) {
185 memcpy(dbuf, ti->buffer + p,
186 (ti->nc - p) * sizeof(wchar_t));
187 dbuf[ti->nc - p] = '\0';
188 printf("%ls", dbuf);
189 }
190
191 for (i = 0; i < pad; ++i)
192 putchar(' ');
193 fflush(stdout);
194}
195
196static char *tinput_get_str(tinput_t *ti)
197{
198 return wstr_to_astr(ti->buffer);
199}
200
201static void tinput_position_caret(tinput_t *ti)
202{
203 console_goto(fphone(stdout), (ti->col0 + ti->pos) % ti->con_cols,
204 ti->row0 + (ti->col0 + ti->pos) / ti->con_cols);
205}
206
207/** Update row0 in case the screen could have scrolled. */
208static void tinput_update_origin(tinput_t *ti)
209{
210 int width, rows;
211
212 width = ti->col0 + ti->nc;
213 rows = (width / ti->con_cols) + 1;
214
215 /* Update row0 if the screen scrolled. */
216 if (ti->row0 + rows > ti->con_rows)
217 ti->row0 = ti->con_rows - rows;
218}
219
220static void tinput_insert_char(tinput_t *ti, wchar_t c)
221{
222 int i;
223 int new_width, new_height;
224
225 if (ti->nc == INPUT_MAX)
226 return;
227
228 new_width = ti->col0 + ti->nc + 1;
229 if (new_width % ti->con_cols == 0) {
230 /* Advancing to new line. */
231 new_height = (new_width / ti->con_cols) + 1;
232 if (new_height >= ti->con_rows)
233 return; /* Disallow text longer than 1 page for now. */
234 }
235
236 for (i = ti->nc; i > ti->pos; --i)
237 ti->buffer[i] = ti->buffer[i - 1];
238
239 ti->buffer[ti->pos] = c;
240 ti->pos += 1;
241 ti->nc += 1;
242 ti->buffer[ti->nc] = '\0';
243 ti->sel_start = ti->pos;
244
245 tinput_display_tail(ti, ti->pos - 1, 0);
246 tinput_update_origin(ti);
247 tinput_position_caret(ti);
248}
249
250static void tinput_insert_string(tinput_t *ti, const char *str)
251{
252 int i;
253 int new_width, new_height;
254 int ilen;
255 wchar_t c;
256 size_t off;
257
258 ilen = min((ssize_t) str_length(str), INPUT_MAX - ti->nc);
259 if (ilen == 0)
260 return;
261
262 new_width = ti->col0 + ti->nc + ilen;
263 new_height = (new_width / ti->con_cols) + 1;
264 if (new_height >= ti->con_rows)
265 return; /* Disallow text longer than 1 page for now. */
266
267 for (i = ti->nc - 1; i >= ti->pos; --i)
268 ti->buffer[i + ilen] = ti->buffer[i];
269
270 off = 0; i = 0;
271 while (i < ilen) {
272 c = str_decode(str, &off, STR_NO_LIMIT);
273 if (c == '\0')
274 break;
275
276 /* Filter out non-printable chars. */
277 if (c < 32)
278 c = 32;
279
280 ti->buffer[i++] = c;
281 }
282
283 ti->pos += ilen;
284 ti->nc += ilen;
285 ti->buffer[ti->nc] = '\0';
286 ti->sel_start = ti->pos;
287
288 tinput_display_tail(ti, ti->pos - ilen, 0);
289 tinput_update_origin(ti);
290 tinput_position_caret(ti);
291}
292
293static void tinput_backspace(tinput_t *ti)
294{
295 int i;
296
297 if (tinput_sel_active(ti)) {
298 tinput_sel_delete(ti);
299 return;
300 }
301
302 if (ti->pos == 0)
303 return;
304
305 for (i = ti->pos; i < ti->nc; ++i)
306 ti->buffer[i - 1] = ti->buffer[i];
307 ti->pos -= 1;
308 ti->nc -= 1;
309 ti->buffer[ti->nc] = '\0';
310 ti->sel_start = ti->pos;
311
312 tinput_display_tail(ti, ti->pos, 1);
313 tinput_position_caret(ti);
314}
315
316static void tinput_delete(tinput_t *ti)
317{
318 if (tinput_sel_active(ti)) {
319 tinput_sel_delete(ti);
320 return;
321 }
322
323 if (ti->pos == ti->nc)
324 return;
325
326 ti->pos += 1;
327 ti->sel_start = ti->pos;
328
329 tinput_backspace(ti);
330}
331
332static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
333{
334 tinput_pre_seek(ti, shift_held);
335
336 if (dir == seek_forward) {
337 if (ti->pos < ti->nc)
338 ti->pos += 1;
339 } else {
340 if (ti->pos > 0)
341 ti->pos -= 1;
342 }
343
344 tinput_post_seek(ti, shift_held);
345}
346
347static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
348{
349 tinput_pre_seek(ti, shift_held);
350
351 if (dir == seek_forward) {
352 if (ti->pos == ti->nc)
353 return;
354
355 while (1) {
356 ti->pos += 1;
357
358 if (ti->pos == ti->nc)
359 break;
360
361 if (ti->buffer[ti->pos - 1] == ' ' &&
362 ti->buffer[ti->pos] != ' ')
363 break;
364 }
365 } else {
366 if (ti->pos == 0)
367 return;
368
369 while (1) {
370 ti->pos -= 1;
371
372 if (ti->pos == 0)
373 break;
374
375 if (ti->buffer[ti->pos - 1] == ' ' &&
376 ti->buffer[ti->pos] != ' ')
377 break;
378 }
379
380 }
381
382 tinput_post_seek(ti, shift_held);
383}
384
385static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
386{
387 tinput_pre_seek(ti, shift_held);
388
389 if (dir == seek_forward) {
390 if (ti->pos + ti->con_cols <= ti->nc)
391 ti->pos = ti->pos + ti->con_cols;
392 } else {
393 if (ti->pos - ti->con_cols >= 0)
394 ti->pos = ti->pos - ti->con_cols;
395 }
396
397 tinput_post_seek(ti, shift_held);
398}
399
400static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
401{
402 tinput_pre_seek(ti, shift_held);
403
404 if (dir == seek_backward)
405 ti->pos = 0;
406 else
407 ti->pos = ti->nc;
408
409 tinput_post_seek(ti, shift_held);
410}
411
412static void tinput_pre_seek(tinput_t *ti, bool shift_held)
413{
414 if (tinput_sel_active(ti) && !shift_held) {
415 /* Unselect and redraw. */
416 ti->sel_start = ti->pos;
417 tinput_display_tail(ti, 0, 0);
418 tinput_position_caret(ti);
419 }
420}
421
422static void tinput_post_seek(tinput_t *ti, bool shift_held)
423{
424 if (shift_held) {
425 /* Selecting text. Need redraw. */
426 tinput_display_tail(ti, 0, 0);
427 } else {
428 /* Shift not held. Keep selection empty. */
429 ti->sel_start = ti->pos;
430 }
431 tinput_position_caret(ti);
432}
433
434static void tinput_history_insert(tinput_t *ti, char *str)
435{
436 int i;
437
438 if (ti->hnum < HISTORY_LEN) {
439 ti->hnum += 1;
440 } else {
441 if (ti->history[HISTORY_LEN] != NULL)
442 free(ti->history[HISTORY_LEN]);
443 }
444
445 for (i = ti->hnum; i > 1; --i)
446 ti->history[i] = ti->history[i - 1];
447
448 ti->history[1] = str_dup(str);
449
450 if (ti->history[0] != NULL) {
451 free(ti->history[0]);
452 ti->history[0] = NULL;
453 }
454}
455
456static void tinput_set_str(tinput_t *ti, char *str)
457{
458 str_to_wstr(ti->buffer, INPUT_MAX, str);
459 ti->nc = wstr_length(ti->buffer);
460 ti->pos = ti->nc;
461 ti->sel_start = ti->pos;
462}
463
464static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb)
465{
466 if (ti->sel_start < ti->pos) {
467 *sa = ti->sel_start;
468 *sb = ti->pos;
469 } else {
470 *sa = ti->pos;
471 *sb = ti->sel_start;
472 }
473}
474
475static bool tinput_sel_active(tinput_t *ti)
476{
477 return ti->sel_start != ti->pos;
478}
479
480static void tinput_sel_all(tinput_t *ti)
481{
482 ti->sel_start = 0;
483 ti->pos = ti->nc;
484 tinput_display_tail(ti, 0, 0);
485 tinput_position_caret(ti);
486}
487
488static void tinput_sel_delete(tinput_t *ti)
489{
490 int sa, sb;
491
492 tinput_sel_get_bounds(ti, &sa, &sb);
493 if (sa == sb)
494 return;
495
496 memmove(ti->buffer + sa, ti->buffer + sb,
497 (ti->nc - sb) * sizeof(wchar_t));
498 ti->pos = ti->sel_start = sa;
499 ti->nc -= (sb - sa);
500 ti->buffer[ti->nc] = '\0';
501
502 tinput_display_tail(ti, sa, sb - sa);
503 tinput_position_caret(ti);
504}
505
506static void tinput_sel_copy_to_cb(tinput_t *ti)
507{
508 int sa, sb;
509 wchar_t tmp_c;
510 char *str;
511
512 tinput_sel_get_bounds(ti, &sa, &sb);
513
514 if (sb < ti->nc) {
515 tmp_c = ti->buffer[sb];
516 ti->buffer[sb] = '\0';
517 }
518
519 str = wstr_to_astr(ti->buffer + sa);
520
521 if (sb < ti->nc)
522 ti->buffer[sb] = tmp_c;
523
524 if (str == NULL)
525 goto error;
526
527 if (clipboard_put_str(str) != EOK)
528 goto error;
529
530 free(str);
531 return;
532error:
533 return;
534 /* TODO: Give the user some warning. */
535}
536
537static void tinput_paste_from_cb(tinput_t *ti)
538{
539 char *str;
540 int rc;
541
542 rc = clipboard_get_str(&str);
543 if (rc != EOK || str == NULL)
544 return; /* TODO: Give the user some warning. */
545
546 tinput_insert_string(ti, str);
547 free(str);
548}
549
550static void tinput_history_seek(tinput_t *ti, int offs)
551{
552 int pad;
553
554 if (ti->hpos + offs < 0 || ti->hpos + offs > ti->hnum)
555 return;
556
557 if (ti->history[ti->hpos] != NULL) {
558 free(ti->history[ti->hpos]);
559 ti->history[ti->hpos] = NULL;
560 }
561
562 ti->history[ti->hpos] = tinput_get_str(ti);
563 ti->hpos += offs;
564
565 pad = ti->nc - str_length(ti->history[ti->hpos]);
566 if (pad < 0) pad = 0;
567
568 tinput_set_str(ti, ti->history[ti->hpos]);
569 tinput_display_tail(ti, 0, pad);
570 tinput_update_origin(ti);
571 tinput_position_caret(ti);
572}
573
574/** Initialize text input field.
575 *
576 * Must be called before using the field. It clears the history.
577 */
578static void tinput_init(tinput_t *ti)
579{
580 ti->hnum = 0;
581 ti->hpos = 0;
582 ti->history[0] = NULL;
583}
584
585/** Read in one line of input. */
586static char *tinput_read(tinput_t *ti)
587{
588 console_event_t ev;
589 char *str;
590
591 fflush(stdout);
592
593 if (console_get_size(fphone(stdin), &ti->con_cols, &ti->con_rows) != EOK)
594 return NULL;
595 if (console_get_pos(fphone(stdin), &ti->col0, &ti->row0) != EOK)
596 return NULL;
597
598 ti->pos = ti->sel_start = 0;
599 ti->nc = 0;
600 ti->buffer[0] = '\0';
601 ti->done = false;
602
603 while (!ti->done) {
604 fflush(stdout);
605 if (!console_get_event(fphone(stdin), &ev))
606 return NULL;
607
608 if (ev.type != KEY_PRESS)
609 continue;
610
611 if ((ev.mods & KM_CTRL) != 0 &&
612 (ev.mods & (KM_ALT | KM_SHIFT)) == 0) {
613 tinput_key_ctrl(ti, &ev);
614 }
615
616 if ((ev.mods & KM_SHIFT) != 0 &&
617 (ev.mods & (KM_CTRL | KM_ALT)) == 0) {
618 tinput_key_shift(ti, &ev);
619 }
620
621 if ((ev.mods & KM_CTRL) != 0 &&
622 (ev.mods & KM_SHIFT) != 0 &&
623 (ev.mods & KM_ALT) == 0) {
624 tinput_key_ctrl_shift(ti, &ev);
625 }
626
627 if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
628 tinput_key_unmod(ti, &ev);
629 }
630
631 if (ev.c >= ' ') {
632 tinput_sel_delete(ti);
633 tinput_insert_char(ti, ev.c);
634 }
635 }
636
637 ti->pos = ti->nc;
638 tinput_position_caret(ti);
639 putchar('\n');
640
641 str = tinput_get_str(ti);
642 if (str_cmp(str, "") != 0)
643 tinput_history_insert(ti, str);
644
645 ti->hpos = 0;
646
647 return str;
648}
649
650static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev)
651{
652 switch (ev->key) {
653 case KC_LEFT:
654 tinput_seek_word(ti, seek_backward, false);
655 break;
656 case KC_RIGHT:
657 tinput_seek_word(ti, seek_forward, false);
658 break;
659 case KC_UP:
660 tinput_seek_vertical(ti, seek_backward, false);
661 break;
662 case KC_DOWN:
663 tinput_seek_vertical(ti, seek_forward, false);
664 break;
665 case KC_X:
666 tinput_sel_copy_to_cb(ti);
667 tinput_sel_delete(ti);
668 break;
669 case KC_C:
670 tinput_sel_copy_to_cb(ti);
671 break;
672 case KC_V:
673 tinput_paste_from_cb(ti);
674 break;
675 case KC_A:
676 tinput_sel_all(ti);
677 break;
678 default:
679 break;
680 }
681}
682
683static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev)
684{
685 switch (ev->key) {
686 case KC_LEFT:
687 tinput_seek_word(ti, seek_backward, true);
688 break;
689 case KC_RIGHT:
690 tinput_seek_word(ti, seek_forward, true);
691 break;
692 case KC_UP:
693 tinput_seek_vertical(ti, seek_backward, true);
694 break;
695 case KC_DOWN:
696 tinput_seek_vertical(ti, seek_forward, true);
697 break;
698 default:
699 break;
700 }
701}
702
703static void tinput_key_shift(tinput_t *ti, console_event_t *ev)
704{
705 switch (ev->key) {
706 case KC_LEFT:
707 tinput_seek_cell(ti, seek_backward, true);
708 break;
709 case KC_RIGHT:
710 tinput_seek_cell(ti, seek_forward, true);
711 break;
712 case KC_UP:
713 tinput_seek_vertical(ti, seek_backward, true);
714 break;
715 case KC_DOWN:
716 tinput_seek_vertical(ti, seek_forward, true);
717 break;
718 case KC_HOME:
719 tinput_seek_max(ti, seek_backward, true);
720 break;
721 case KC_END:
722 tinput_seek_max(ti, seek_forward, true);
723 break;
724 default:
725 break;
726 }
727}
728
729static void tinput_key_unmod(tinput_t *ti, console_event_t *ev)
730{
731 switch (ev->key) {
732 case KC_ENTER:
733 case KC_NENTER:
734 ti->done = true;
735 break;
736 case KC_BACKSPACE:
737 tinput_backspace(ti);
738 break;
739 case KC_DELETE:
740 tinput_delete(ti);
741 break;
742 case KC_LEFT:
743 tinput_seek_cell(ti, seek_backward, false);
744 break;
745 case KC_RIGHT:
746 tinput_seek_cell(ti, seek_forward, false);
747 break;
748 case KC_HOME:
749 tinput_seek_max(ti, seek_backward, false);
750 break;
751 case KC_END:
752 tinput_seek_max(ti, seek_forward, false);
753 break;
754 case KC_UP:
755 tinput_history_seek(ti, +1);
756 break;
757 case KC_DOWN:
758 tinput_history_seek(ti, -1);
759 break;
760 default:
761 break;
762 }
763}
764
765void get_input(cliuser_t *usr)
766{
767 char *str;
768
769 fflush(stdout);
770 console_set_style(fphone(stdout), STYLE_EMPHASIS);
771 printf("%s", usr->prompt);
772 fflush(stdout);
773 console_set_style(fphone(stdout), STYLE_NORMAL);
774
775 str = tinput_read(&tinput);
776
777 /* Check for empty input. */
778 if (str_cmp(str, "") == 0) {
779 free(str);
780 return;
781 }
782
783 usr->line = str;
784 return;
785}
786
787void input_init(void)
788{
789 tinput_init(&tinput);
790}
Note: See TracBrowser for help on using the repository browser.