I am trying to implement an ncurses app with text scrolling like less. What is the recommended way to do this?
Here's what I know:
scroll
to move the text buffer up or down by 1 row. However, you'll end up with one blank line at the top if you scroll down, or at the bottom if you scroll up, which you'll have to repaint yourself.I guess I could re-implement wordwrap myself and keep an array of all the post-wrapped lines, but this seems like a common problem, so there might be a better way.
Drawing text to the terminal screen can have a performance impact on certain systems, especially on older hardware terminals. So ncurses lets you "stack up" a bunch of text to display to the screen, then use the refresh () function to make all of those changes visible to the user.
You can create scrolling text in HTML using the <marquee> tag, or you can create CSS scrolling text (the preferred method). You can make your text scroll from right to left.
Moving to a screen location and displaying text is such a common thing that ncurses provides a shortcut to do both at once. The mvaddch (row, col, c) function will display a character at screen location row,col. And the mvaddstr (row, col, s) function will display a string at that location.
Here's a little trick that creates scrolling text that falls like snow. To use this, change the bit that reads Scrolling text... to the text that you want to scroll. You can also change any of the other settings to modify how the text falls, how high the falling text is, etc.
The answer depends on how much text you have, and what other uses you are making of the data on the screen. But usually the pad feature is preferred way to display scrollable text. This is not only an ncurses feature, but is supported by most implementations of curses (i.e., anything after the late 1980s).
A pad is like a window, but its size is not limited to the current screen size. Instead, the data is shown through a view whose position within the pad can be easily changed.
There are two sample programs in ncurses-examples
which are relevant: view.c
displays a file by writing its contents onto a window, while padview.c
uses a pad on which the entire file is drawn, and uses the pad-functions for moving the view around as needed, up/down, and left/right.
In those programs, the show_all
function does the actual drawing, and is about a third as long for padview.c
(35 lines) compared to the equivalent for view.c
(94 lines).
Further reading: discussion of view
as an example for comparing ncurses and slang libraries.
I'm not very experienced in ncurses yet, so I don't know all the library functions, but keeping a doubly-linked list of text lines could work. Here's a primitive clone of less:
#include <ncurses.h>
#include <stdlib.h>
#define MAXLEN 128
typedef struct Line {
char contents[MAXLEN];
struct Line *prev, *next;
} Line;
Line *head, *curr;
int nr_lines;
int curr_line;
int term_rows, term_cols;
void load(const char *filename);
void draw(Line *l);
int main(int argc, char **argv)
{
if (argc == 1)
{
fputs("less: No file to open\n", stderr);
return 1;
}
initscr();
noecho();
keypad(stdscr, TRUE); // for KEY_UP, KEY_DOWN
getmaxyx(stdscr, term_rows, term_cols);
addstr("Reading text...\n");
load(argv[1]);
curr = head;
curr_line = 0;
draw(curr);
int ch;
while ((ch = getch()) != EOF && ch != 'q')
{
if (ch == KEY_UP && curr->prev != NULL)
{
curr_line--;
curr = curr->prev;
}
else if (ch == KEY_DOWN && curr->next != NULL
&& curr_line + term_rows < nr_lines)
{
curr_line++;
curr = curr->next;
}
draw(curr);
}
endwin();
return 0;
}
void load(const char *filename)
{
FILE *fp = fopen(filename, "r");
Line *lp;
head = malloc(sizeof(Line));
head->prev = head->next = NULL;
curr = head;
while (fgets(curr->contents, MAXLEN, fp))
{
lp = malloc(sizeof(Line));
lp->prev = curr;
curr->next = lp;
curr = lp;
nr_lines++;
}
curr->next = NULL;
}
void draw(Line *l)
{
int i;
clear();
for (i = 0; i < term_rows && l != NULL; i++, l = l->next)
addstr(l->contents);
}
I'm not sure how this would handle lines longer than 128 characters, but that's a different problem with a different solution.
As for word wrapping, if you don't need to preserve the original text format you could split long lines into two as they're read from the file.
ncurses only knows about the current screen. When the screen is scrolled ncurses does not keep track of any text that leaves the screen. Your terminal might have a scroll back buffer but that is independent of ncurses and curses has no visibility into the terminal's scroll back buffer.
I expect that you will need to keep track of your current location in the file if you need to scroll.
I didn't think ncurses did word wrap how are you doing the word wrap?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With