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...
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.