Search code examples
cpaginationncurses

Need help using ncurses for command output pagination in my custom CLI


I need a simple pager for my CLI commands output. I want to be able to scroll up/down in the output, either one whole page at a time or line by line.

I took a look at the "less" source code, but it was too complicated to help me. So I started to write a simple one by myself. What I've done so far is that I write the command output to a file. then read the file line by line and write to ncurses window. When i reach bottom of the window, I wait for the user to press a key, then clear the screen and write a new page and so on. The result is something like the "more" command.

Here is the simple code I use:

int print()
{
    FILE *fp;
    ssize_t read;
    int row, col, x, y;
    char *line = NULL;
    char c;
    size_t len;

    initscr();
    getmaxyx(stdscr, row, col);

    fp = fopen("path_to_output_file", "r");

    if (!fp)
    {
        printf("Failed to open CLI output file.\n");
        return -1;
    }

    while ((read = getline(&line, &len, fp)) != -1)
    {
        getyx(stdscr, y, x);

        if (y == (row - 1))
        {
            printw("Press Any Key to continue...");
            c = getch();

            if (c == 'q')
            {
                break;
            }

            clear();
            move(0, 0);
        }

        printw(line);
        refresh();
    }

    fclose(fp);
    getch();
    endwin();

    return 0
}

Now I need help to find the idea of implementing backward scroll to move up one page/line in the output. How should I traverse the file and print the lines to ncurses window to get my desired result.

Other than that, any idea for improving my simple pager is appreciated...


Solution

  • Though you're already working on it, here's an implementation of backward moves which utilizes fseek() to file offsets of the beginnings of (physical/window) lines, collected while the lines are read:

        …
        keypad(stdscr, TRUE);   // enable returning function key tokens
        long offset, *pos = NULL, lineno = 0;
        char buffer[col+1];
        TABSIZE = 1;
        do
        {   y = 0;
            long topline = lineno;
            while (offset = ftell(fp), line = fgets(buffer, sizeof buffer, fp))
            {
                pos = realloc(pos, (lineno+1) * sizeof *pos);   if (!pos) exit(1);
                pos[lineno++] = offset; // save offset of current line
                addstr(line);
                getyx(stdscr, y, x);
                if (y == row-1)
                    break;
            }
            printw("Press [upward arrow] or [Page Up] or any key to continue...");
            int c = getch();
            if (c == KEY_UP)    // Up arrow
                fseek(fp, pos[lineno = topline>1 ? topline-1 : 0], SEEK_SET);
            else
            if (c == KEY_PPAGE) // Previous page
                fseek(fp, pos[lineno = topline>=row ? topline-row+1 : 0], SEEK_SET);
            else
            if (c == 'q' || !line)
                break;
    
            clear();
            move(0, 0);
        } while (1);
    
        fclose(fp);
        endwin();
        …
    

    As a simple approach to handle the problem with TABs, I set TABSIZE to 1.