linux/tools/perf/ui/browser.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#include "../util.h"
   3#include "../string2.h"
   4#include "../config.h"
   5#include "../../perf.h"
   6#include "libslang.h"
   7#include "ui.h"
   8#include "util.h"
   9#include <linux/compiler.h>
  10#include <linux/list.h>
  11#include <linux/rbtree.h>
  12#include <linux/string.h>
  13#include <stdlib.h>
  14#include <sys/ttydefaults.h>
  15#include "browser.h"
  16#include "helpline.h"
  17#include "keysyms.h"
  18#include "../color.h"
  19#include "sane_ctype.h"
  20
  21static int ui_browser__percent_color(struct ui_browser *browser,
  22                                     double percent, bool current)
  23{
  24        if (current && (!browser->use_navkeypressed || browser->navkeypressed))
  25                return HE_COLORSET_SELECTED;
  26        if (percent >= MIN_RED)
  27                return HE_COLORSET_TOP;
  28        if (percent >= MIN_GREEN)
  29                return HE_COLORSET_MEDIUM;
  30        return HE_COLORSET_NORMAL;
  31}
  32
  33int ui_browser__set_color(struct ui_browser *browser, int color)
  34{
  35        int ret = browser->current_color;
  36        browser->current_color = color;
  37        SLsmg_set_color(color);
  38        return ret;
  39}
  40
  41void ui_browser__set_percent_color(struct ui_browser *browser,
  42                                   double percent, bool current)
  43{
  44         int color = ui_browser__percent_color(browser, percent, current);
  45         ui_browser__set_color(browser, color);
  46}
  47
  48void ui_browser__gotorc_title(struct ui_browser *browser, int y, int x)
  49{
  50        SLsmg_gotorc(browser->y + y, browser->x + x);
  51}
  52
  53void ui_browser__gotorc(struct ui_browser *browser, int y, int x)
  54{
  55        SLsmg_gotorc(browser->y + y + browser->extra_title_lines, browser->x + x);
  56}
  57
  58void ui_browser__write_nstring(struct ui_browser *browser __maybe_unused, const char *msg,
  59                               unsigned int width)
  60{
  61        slsmg_write_nstring(msg, width);
  62}
  63
  64void ui_browser__vprintf(struct ui_browser *browser __maybe_unused, const char *fmt, va_list args)
  65{
  66        slsmg_vprintf(fmt, args);
  67}
  68
  69void ui_browser__printf(struct ui_browser *browser __maybe_unused, const char *fmt, ...)
  70{
  71        va_list args;
  72
  73        va_start(args, fmt);
  74        ui_browser__vprintf(browser, fmt, args);
  75        va_end(args);
  76}
  77
  78static struct list_head *
  79ui_browser__list_head_filter_entries(struct ui_browser *browser,
  80                                     struct list_head *pos)
  81{
  82        do {
  83                if (!browser->filter || !browser->filter(browser, pos))
  84                        return pos;
  85                pos = pos->next;
  86        } while (pos != browser->entries);
  87
  88        return NULL;
  89}
  90
  91static struct list_head *
  92ui_browser__list_head_filter_prev_entries(struct ui_browser *browser,
  93                                          struct list_head *pos)
  94{
  95        do {
  96                if (!browser->filter || !browser->filter(browser, pos))
  97                        return pos;
  98                pos = pos->prev;
  99        } while (pos != browser->entries);
 100
 101        return NULL;
 102}
 103
 104void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence)
 105{
 106        struct list_head *head = browser->entries;
 107        struct list_head *pos;
 108
 109        if (browser->nr_entries == 0)
 110                return;
 111
 112        switch (whence) {
 113        case SEEK_SET:
 114                pos = ui_browser__list_head_filter_entries(browser, head->next);
 115                break;
 116        case SEEK_CUR:
 117                pos = browser->top;
 118                break;
 119        case SEEK_END:
 120                pos = ui_browser__list_head_filter_prev_entries(browser, head->prev);
 121                break;
 122        default:
 123                return;
 124        }
 125
 126        assert(pos != NULL);
 127
 128        if (offset > 0) {
 129                while (offset-- != 0)
 130                        pos = ui_browser__list_head_filter_entries(browser, pos->next);
 131        } else {
 132                while (offset++ != 0)
 133                        pos = ui_browser__list_head_filter_prev_entries(browser, pos->prev);
 134        }
 135
 136        browser->top = pos;
 137}
 138
 139void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence)
 140{
 141        struct rb_root *root = browser->entries;
 142        struct rb_node *nd;
 143
 144        switch (whence) {
 145        case SEEK_SET:
 146                nd = rb_first(root);
 147                break;
 148        case SEEK_CUR:
 149                nd = browser->top;
 150                break;
 151        case SEEK_END:
 152                nd = rb_last(root);
 153                break;
 154        default:
 155                return;
 156        }
 157
 158        if (offset > 0) {
 159                while (offset-- != 0)
 160                        nd = rb_next(nd);
 161        } else {
 162                while (offset++ != 0)
 163                        nd = rb_prev(nd);
 164        }
 165
 166        browser->top = nd;
 167}
 168
 169unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser)
 170{
 171        struct rb_node *nd;
 172        int row = 0;
 173
 174        if (browser->top == NULL)
 175                browser->top = rb_first(browser->entries);
 176
 177        nd = browser->top;
 178
 179        while (nd != NULL) {
 180                ui_browser__gotorc(browser, row, 0);
 181                browser->write(browser, nd, row);
 182                if (++row == browser->rows)
 183                        break;
 184                nd = rb_next(nd);
 185        }
 186
 187        return row;
 188}
 189
 190bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row)
 191{
 192        return browser->top_idx + row == browser->index;
 193}
 194
 195void ui_browser__refresh_dimensions(struct ui_browser *browser)
 196{
 197        browser->width = SLtt_Screen_Cols - 1;
 198        browser->height = browser->rows = SLtt_Screen_Rows - 2;
 199        browser->rows -= browser->extra_title_lines;
 200        browser->y = 1;
 201        browser->x = 0;
 202}
 203
 204void ui_browser__handle_resize(struct ui_browser *browser)
 205{
 206        ui__refresh_dimensions(false);
 207        ui_browser__show(browser, browser->title, ui_helpline__current);
 208        ui_browser__refresh(browser);
 209}
 210
 211int ui_browser__warning(struct ui_browser *browser, int timeout,
 212                        const char *format, ...)
 213{
 214        va_list args;
 215        char *text;
 216        int key = 0, err;
 217
 218        va_start(args, format);
 219        err = vasprintf(&text, format, args);
 220        va_end(args);
 221
 222        if (err < 0) {
 223                va_start(args, format);
 224                ui_helpline__vpush(format, args);
 225                va_end(args);
 226        } else {
 227                while ((key = ui__question_window("Warning!", text,
 228                                                   "Press any key...",
 229                                                   timeout)) == K_RESIZE)
 230                        ui_browser__handle_resize(browser);
 231                free(text);
 232        }
 233
 234        return key;
 235}
 236
 237int ui_browser__help_window(struct ui_browser *browser, const char *text)
 238{
 239        int key;
 240
 241        while ((key = ui__help_window(text)) == K_RESIZE)
 242                ui_browser__handle_resize(browser);
 243
 244        return key;
 245}
 246
 247bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text)
 248{
 249        int key;
 250
 251        while ((key = ui__dialog_yesno(text)) == K_RESIZE)
 252                ui_browser__handle_resize(browser);
 253
 254        return key == K_ENTER || toupper(key) == 'Y';
 255}
 256
 257void ui_browser__reset_index(struct ui_browser *browser)
 258{
 259        browser->index = browser->top_idx = 0;
 260        browser->seek(browser, 0, SEEK_SET);
 261}
 262
 263void __ui_browser__show_title(struct ui_browser *browser, const char *title)
 264{
 265        SLsmg_gotorc(0, 0);
 266        ui_browser__set_color(browser, HE_COLORSET_ROOT);
 267        ui_browser__write_nstring(browser, title, browser->width + 1);
 268}
 269
 270void ui_browser__show_title(struct ui_browser *browser, const char *title)
 271{
 272        pthread_mutex_lock(&ui__lock);
 273        __ui_browser__show_title(browser, title);
 274        pthread_mutex_unlock(&ui__lock);
 275}
 276
 277int ui_browser__show(struct ui_browser *browser, const char *title,
 278                     const char *helpline, ...)
 279{
 280        int err;
 281        va_list ap;
 282
 283        if (browser->refresh_dimensions == NULL)
 284                browser->refresh_dimensions = ui_browser__refresh_dimensions;
 285
 286        browser->refresh_dimensions(browser);
 287
 288        pthread_mutex_lock(&ui__lock);
 289        __ui_browser__show_title(browser, title);
 290
 291        browser->title = title;
 292        zfree(&browser->helpline);
 293
 294        va_start(ap, helpline);
 295        err = vasprintf(&browser->helpline, helpline, ap);
 296        va_end(ap);
 297        if (err > 0)
 298                ui_helpline__push(browser->helpline);
 299        pthread_mutex_unlock(&ui__lock);
 300        return err ? 0 : -1;
 301}
 302
 303void ui_browser__hide(struct ui_browser *browser)
 304{
 305        pthread_mutex_lock(&ui__lock);
 306        ui_helpline__pop();
 307        zfree(&browser->helpline);
 308        pthread_mutex_unlock(&ui__lock);
 309}
 310
 311static void ui_browser__scrollbar_set(struct ui_browser *browser)
 312{
 313        int height = browser->height, h = 0, pct = 0,
 314            col = browser->width,
 315            row = 0;
 316
 317        if (browser->nr_entries > 1) {
 318                pct = ((browser->index * (browser->height - 1)) /
 319                       (browser->nr_entries - 1));
 320        }
 321
 322        SLsmg_set_char_set(1);
 323
 324        while (h < height) {
 325                ui_browser__gotorc(browser, row++, col);
 326                SLsmg_write_char(h == pct ? SLSMG_DIAMOND_CHAR : SLSMG_CKBRD_CHAR);
 327                ++h;
 328        }
 329
 330        SLsmg_set_char_set(0);
 331}
 332
 333static int __ui_browser__refresh(struct ui_browser *browser)
 334{
 335        int row;
 336        int width = browser->width;
 337
 338        row = browser->refresh(browser);
 339        ui_browser__set_color(browser, HE_COLORSET_NORMAL);
 340
 341        if (!browser->use_navkeypressed || browser->navkeypressed)
 342                ui_browser__scrollbar_set(browser);
 343        else
 344                width += 1;
 345
 346        SLsmg_fill_region(browser->y + row + browser->extra_title_lines, browser->x,
 347                          browser->rows - row, width, ' ');
 348
 349        return 0;
 350}
 351
 352int ui_browser__refresh(struct ui_browser *browser)
 353{
 354        pthread_mutex_lock(&ui__lock);
 355        __ui_browser__refresh(browser);
 356        pthread_mutex_unlock(&ui__lock);
 357
 358        return 0;
 359}
 360
 361/*
 362 * Here we're updating nr_entries _after_ we started browsing, i.e.  we have to
 363 * forget about any reference to any entry in the underlying data structure,
 364 * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser
 365 * after an output_resort and hist decay.
 366 */
 367void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries)
 368{
 369        off_t offset = nr_entries - browser->nr_entries;
 370
 371        browser->nr_entries = nr_entries;
 372
 373        if (offset < 0) {
 374                if (browser->top_idx < (u64)-offset)
 375                        offset = -browser->top_idx;
 376
 377                browser->index += offset;
 378                browser->top_idx += offset;
 379        }
 380
 381        browser->top = NULL;
 382        browser->seek(browser, browser->top_idx, SEEK_SET);
 383}
 384
 385int ui_browser__run(struct ui_browser *browser, int delay_secs)
 386{
 387        int err, key;
 388
 389        while (1) {
 390                off_t offset;
 391
 392                pthread_mutex_lock(&ui__lock);
 393                err = __ui_browser__refresh(browser);
 394                SLsmg_refresh();
 395                pthread_mutex_unlock(&ui__lock);
 396                if (err < 0)
 397                        break;
 398
 399                key = ui__getch(delay_secs);
 400
 401                if (key == K_RESIZE) {
 402                        ui__refresh_dimensions(false);
 403                        browser->refresh_dimensions(browser);
 404                        __ui_browser__show_title(browser, browser->title);
 405                        ui_helpline__puts(browser->helpline);
 406                        continue;
 407                }
 408
 409                if (browser->use_navkeypressed && !browser->navkeypressed) {
 410                        if (key == K_DOWN || key == K_UP ||
 411                            (browser->columns && (key == K_LEFT || key == K_RIGHT)) ||
 412                            key == K_PGDN || key == K_PGUP ||
 413                            key == K_HOME || key == K_END ||
 414                            key == ' ') {
 415                                browser->navkeypressed = true;
 416                                continue;
 417                        } else
 418                                return key;
 419                }
 420
 421                switch (key) {
 422                case K_DOWN:
 423                        if (browser->index == browser->nr_entries - 1)
 424                                break;
 425                        ++browser->index;
 426                        if (browser->index == browser->top_idx + browser->rows) {
 427                                ++browser->top_idx;
 428                                browser->seek(browser, +1, SEEK_CUR);
 429                        }
 430                        break;
 431                case K_UP:
 432                        if (browser->index == 0)
 433                                break;
 434                        --browser->index;
 435                        if (browser->index < browser->top_idx) {
 436                                --browser->top_idx;
 437                                browser->seek(browser, -1, SEEK_CUR);
 438                        }
 439                        break;
 440                case K_RIGHT:
 441                        if (!browser->columns)
 442                                goto out;
 443                        if (browser->horiz_scroll < browser->columns - 1)
 444                                ++browser->horiz_scroll;
 445                        break;
 446                case K_LEFT:
 447                        if (!browser->columns)
 448                                goto out;
 449                        if (browser->horiz_scroll != 0)
 450                                --browser->horiz_scroll;
 451                        break;
 452                case K_PGDN:
 453                case ' ':
 454                        if (browser->top_idx + browser->rows > browser->nr_entries - 1)
 455                                break;
 456
 457                        offset = browser->rows;
 458                        if (browser->index + offset > browser->nr_entries - 1)
 459                                offset = browser->nr_entries - 1 - browser->index;
 460                        browser->index += offset;
 461                        browser->top_idx += offset;
 462                        browser->seek(browser, +offset, SEEK_CUR);
 463                        break;
 464                case K_PGUP:
 465                        if (browser->top_idx == 0)
 466                                break;
 467
 468                        if (browser->top_idx < browser->rows)
 469                                offset = browser->top_idx;
 470                        else
 471                                offset = browser->rows;
 472
 473                        browser->index -= offset;
 474                        browser->top_idx -= offset;
 475                        browser->seek(browser, -offset, SEEK_CUR);
 476                        break;
 477                case K_HOME:
 478                        ui_browser__reset_index(browser);
 479                        break;
 480                case K_END:
 481                        offset = browser->rows - 1;
 482                        if (offset >= browser->nr_entries)
 483                                offset = browser->nr_entries - 1;
 484
 485                        browser->index = browser->nr_entries - 1;
 486                        browser->top_idx = browser->index - offset;
 487                        browser->seek(browser, -offset, SEEK_END);
 488                        break;
 489                default:
 490                out:
 491                        return key;
 492                }
 493        }
 494        return -1;
 495}
 496
 497unsigned int ui_browser__list_head_refresh(struct ui_browser *browser)
 498{
 499        struct list_head *pos;
 500        struct list_head *head = browser->entries;
 501        int row = 0;
 502
 503        if (browser->top == NULL || browser->top == browser->entries)
 504                browser->top = ui_browser__list_head_filter_entries(browser, head->next);
 505
 506        pos = browser->top;
 507
 508        list_for_each_from(pos, head) {
 509                if (!browser->filter || !browser->filter(browser, pos)) {
 510                        ui_browser__gotorc(browser, row, 0);
 511                        browser->write(browser, pos, row);
 512                        if (++row == browser->rows)
 513                                break;
 514                }
 515        }
 516
 517        return row;
 518}
 519
 520static struct ui_browser_colorset {
 521        const char *name, *fg, *bg;
 522        int colorset;
 523} ui_browser__colorsets[] = {
 524        {
 525                .colorset = HE_COLORSET_TOP,
 526                .name     = "top",
 527                .fg       = "red",
 528                .bg       = "default",
 529        },
 530        {
 531                .colorset = HE_COLORSET_MEDIUM,
 532                .name     = "medium",
 533                .fg       = "green",
 534                .bg       = "default",
 535        },
 536        {
 537                .colorset = HE_COLORSET_NORMAL,
 538                .name     = "normal",
 539                .fg       = "default",
 540                .bg       = "default",
 541        },
 542        {
 543                .colorset = HE_COLORSET_SELECTED,
 544                .name     = "selected",
 545                .fg       = "black",
 546                .bg       = "yellow",
 547        },
 548        {
 549                .colorset = HE_COLORSET_JUMP_ARROWS,
 550                .name     = "jump_arrows",
 551                .fg       = "blue",
 552                .bg       = "default",
 553        },
 554        {
 555                .colorset = HE_COLORSET_ADDR,
 556                .name     = "addr",
 557                .fg       = "magenta",
 558                .bg       = "default",
 559        },
 560        {
 561                .colorset = HE_COLORSET_ROOT,
 562                .name     = "root",
 563                .fg       = "white",
 564                .bg       = "blue",
 565        },
 566        {
 567                .name = NULL,
 568        }
 569};
 570
 571
 572static int ui_browser__color_config(const char *var, const char *value,
 573                                    void *data __maybe_unused)
 574{
 575        char *fg = NULL, *bg;
 576        int i;
 577
 578        /* same dir for all commands */
 579        if (!strstarts(var, "colors.") != 0)
 580                return 0;
 581
 582        for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) {
 583                const char *name = var + 7;
 584
 585                if (strcmp(ui_browser__colorsets[i].name, name) != 0)
 586                        continue;
 587
 588                fg = strdup(value);
 589                if (fg == NULL)
 590                        break;
 591
 592                bg = strchr(fg, ',');
 593                if (bg == NULL)
 594                        break;
 595
 596                *bg = '\0';
 597                bg = ltrim(++bg);
 598                ui_browser__colorsets[i].bg = bg;
 599                ui_browser__colorsets[i].fg = fg;
 600                return 0;
 601        }
 602
 603        free(fg);
 604        return -1;
 605}
 606
 607void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence)
 608{
 609        switch (whence) {
 610        case SEEK_SET:
 611                browser->top = browser->entries;
 612                break;
 613        case SEEK_CUR:
 614                browser->top = browser->top + browser->top_idx + offset;
 615                break;
 616        case SEEK_END:
 617                browser->top = browser->top + browser->nr_entries - 1 + offset;
 618                break;
 619        default:
 620                return;
 621        }
 622}
 623
 624unsigned int ui_browser__argv_refresh(struct ui_browser *browser)
 625{
 626        unsigned int row = 0, idx = browser->top_idx;
 627        char **pos;
 628
 629        if (browser->top == NULL)
 630                browser->top = browser->entries;
 631
 632        pos = (char **)browser->top;
 633        while (idx < browser->nr_entries) {
 634                if (!browser->filter || !browser->filter(browser, *pos)) {
 635                        ui_browser__gotorc(browser, row, 0);
 636                        browser->write(browser, pos, row);
 637                        if (++row == browser->rows)
 638                                break;
 639                }
 640
 641                ++idx;
 642                ++pos;
 643        }
 644
 645        return row;
 646}
 647
 648void __ui_browser__vline(struct ui_browser *browser, unsigned int column,
 649                         u16 start, u16 end)
 650{
 651        SLsmg_set_char_set(1);
 652        ui_browser__gotorc(browser, start, column);
 653        SLsmg_draw_vline(end - start + 1);
 654        SLsmg_set_char_set(0);
 655}
 656
 657void ui_browser__write_graph(struct ui_browser *browser __maybe_unused,
 658                             int graph)
 659{
 660        SLsmg_set_char_set(1);
 661        SLsmg_write_char(graph);
 662        SLsmg_set_char_set(0);
 663}
 664
 665static void __ui_browser__line_arrow_up(struct ui_browser *browser,
 666                                        unsigned int column,
 667                                        u64 start, u64 end)
 668{
 669        unsigned int row, end_row;
 670
 671        SLsmg_set_char_set(1);
 672
 673        if (start < browser->top_idx + browser->rows) {
 674                row = start - browser->top_idx;
 675                ui_browser__gotorc(browser, row, column);
 676                SLsmg_write_char(SLSMG_LLCORN_CHAR);
 677                ui_browser__gotorc(browser, row, column + 1);
 678                SLsmg_draw_hline(2);
 679
 680                if (row-- == 0)
 681                        goto out;
 682        } else
 683                row = browser->rows - 1;
 684
 685        if (end > browser->top_idx)
 686                end_row = end - browser->top_idx;
 687        else
 688                end_row = 0;
 689
 690        ui_browser__gotorc(browser, end_row, column);
 691        SLsmg_draw_vline(row - end_row + 1);
 692
 693        ui_browser__gotorc(browser, end_row, column);
 694        if (end >= browser->top_idx) {
 695                SLsmg_write_char(SLSMG_ULCORN_CHAR);
 696                ui_browser__gotorc(browser, end_row, column + 1);
 697                SLsmg_write_char(SLSMG_HLINE_CHAR);
 698                ui_browser__gotorc(browser, end_row, column + 2);
 699                SLsmg_write_char(SLSMG_RARROW_CHAR);
 700        }
 701out:
 702        SLsmg_set_char_set(0);
 703}
 704
 705static void __ui_browser__line_arrow_down(struct ui_browser *browser,
 706                                          unsigned int column,
 707                                          u64 start, u64 end)
 708{
 709        unsigned int row, end_row;
 710
 711        SLsmg_set_char_set(1);
 712
 713        if (start >= browser->top_idx) {
 714                row = start - browser->top_idx;
 715                ui_browser__gotorc(browser, row, column);
 716                SLsmg_write_char(SLSMG_ULCORN_CHAR);
 717                ui_browser__gotorc(browser, row, column + 1);
 718                SLsmg_draw_hline(2);
 719
 720                if (++row == 0)
 721                        goto out;
 722        } else
 723                row = 0;
 724
 725        if (end >= browser->top_idx + browser->rows)
 726                end_row = browser->rows - 1;
 727        else
 728                end_row = end - browser->top_idx;
 729
 730        ui_browser__gotorc(browser, row, column);
 731        SLsmg_draw_vline(end_row - row + 1);
 732
 733        ui_browser__gotorc(browser, end_row, column);
 734        if (end < browser->top_idx + browser->rows) {
 735                SLsmg_write_char(SLSMG_LLCORN_CHAR);
 736                ui_browser__gotorc(browser, end_row, column + 1);
 737                SLsmg_write_char(SLSMG_HLINE_CHAR);
 738                ui_browser__gotorc(browser, end_row, column + 2);
 739                SLsmg_write_char(SLSMG_RARROW_CHAR);
 740        }
 741out:
 742        SLsmg_set_char_set(0);
 743}
 744
 745void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
 746                              u64 start, u64 end)
 747{
 748        if (start > end)
 749                __ui_browser__line_arrow_up(browser, column, start, end);
 750        else
 751                __ui_browser__line_arrow_down(browser, column, start, end);
 752}
 753
 754void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column,
 755                            unsigned int row, bool arrow_down)
 756{
 757        unsigned int end_row;
 758
 759        if (row >= browser->top_idx)
 760                end_row = row - browser->top_idx;
 761        else
 762                return;
 763
 764        SLsmg_set_char_set(1);
 765
 766        if (arrow_down) {
 767                ui_browser__gotorc(browser, end_row, column - 1);
 768                SLsmg_write_char(SLSMG_ULCORN_CHAR);
 769                ui_browser__gotorc(browser, end_row, column);
 770                SLsmg_draw_hline(2);
 771                ui_browser__gotorc(browser, end_row + 1, column - 1);
 772                SLsmg_write_char(SLSMG_LTEE_CHAR);
 773        } else {
 774                ui_browser__gotorc(browser, end_row, column - 1);
 775                SLsmg_write_char(SLSMG_LTEE_CHAR);
 776                ui_browser__gotorc(browser, end_row, column);
 777                SLsmg_draw_hline(2);
 778        }
 779
 780        SLsmg_set_char_set(0);
 781}
 782
 783void ui_browser__init(void)
 784{
 785        int i = 0;
 786
 787        perf_config(ui_browser__color_config, NULL);
 788
 789        while (ui_browser__colorsets[i].name) {
 790                struct ui_browser_colorset *c = &ui_browser__colorsets[i++];
 791                sltt_set_color(c->colorset, c->name, c->fg, c->bg);
 792        }
 793}
 794