/* Reed 5.4 - An autoscrolling text pager
Copyright (C)2000-2002 Joe Wreschnig <piman@sacredchao.net>
next_bracket() Copyright (C)2000 Ben Zeigler, 2002 Joe Wreschnig
getuser(), getgroup() Copyright (C)1985, 1990, 1993, 1998-2000
Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Dar Williams rocks my world. */
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <ctype.h>
#include <curses.h>
#include <dirent.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <regex.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "filemode.h"
/*#define strcoll(s1, s2) strcmp(s1, s2)
/* ^- Uncomment that if you are on a braindead system. */
#define free_regexp(n) if (n) { regfree(n); free(n); n = NULL; }
#define alert() if (cue) beep();
#define VERSION "5.4"
/* Return values for activate_bufer */
#define QUIT 0 /* Exit Reed */
#define NEXT_FILE 1 /* Go to the next buffer */
#define PREV_FILE 2 /* " " previous buffer */
#define DELETE_FILE 3 /* Delete the current buffer */
#define LOAD_FILE 4 /* Load a new buffer */
#define RELOAD_FILE 5 /* Reload the current buffer and respace it */
#define BOOKMARKS 6 /* Load the bookmarks file */
#define HELP 7 /* Load the help file */
#define LOAD_INLINE 8 /* Try and open a filename on the current line */
#define LOAD_LIST 9 /* Display a list of open buffers */
/* Some preset buffer names that get compared a lot */
#define B_STDIN "Input Stream"
#define B_BOOKMARKS "Your Bookmarks"
#define B_BUFFERS "Buffer List"
typedef struct _Buffer Buffer;
static WINDOW *text = NULL, *status = NULL;
static char *bookmark_file = NULL, *inline_file = NULL,
*inline_mark_name = NULL, cue = 1, initial_pause = 1;
static long int initial_delay = 500000;
static short int initial_jump = 1;
static struct userid *user_alist;
static struct userid *group_alist;
struct _Buffer {
int line, lines, *markers;
signed short jump;
unsigned long delay; /* Stored as microseconds/10 */
unsigned char *filename, *message, is_file, paused;
FILE *file;
regex_t *match; /* The current search */
Buffer *n, *p;
};
struct userid {
union {
uid_t u;
gid_t g;
} id;
char *name;
struct userid *next;
};
void *xmalloc(size_t size) {
void *value = malloc(size);
if (value == NULL) {
perror("reed");
exit(1);
}
return value;
}
/* Prototypes. Lots of them. */
Buffer *new_buffer(void);
void delete_buffer(Buffer *buf);
Buffer *next_buffer(Buffer *buf);
Buffer *prev_buffer(Buffer *buf);
void replace_buffer(Buffer *from, Buffer *to);
Buffer *load_file(const char *filename);
void find_line_markers(Buffer *buf);
void pause_file(Buffer *buf);
void unpause_file(Buffer *buf);
void toggle_pause(Buffer *buf);
int seek_to_line_and_display(Buffer *buf, int line);
void update_status_bar(Buffer *buf);
char *input_string(const char *message);
float input_decimal(const char *message);
int input_integer(const char *message);
int get_bookmark(const char *name, Buffer *buf);
void delete_bookmark(const char *name, Buffer *buf);
void set_bookmark(const char *name, Buffer *buf);
void clear_bookmarks(Buffer *buf);
void set_char_bookmark(char c, Buffer *buf);
int get_char_bookmark(char c, Buffer *buf);
regex_t *make_regexp(const char *s);
int search_for(regex_t *match, Buffer *buf, int up_down);
void new_search(Buffer *buf, int up_down);
void next_bracket(const char *left, const char *right, Buffer *buf,
int direction);
int activate_buffer(Buffer *buf);
Buffer *load_new_buffer(Buffer *old_buf, const char *filename);
void parse_rc_file(void);
void initialize_windows(void);
void spawn_editor(Buffer *buf);
char *getuser (uid_t uid);
char *getgroup (gid_t gid);
void print_help();
void print_version();
void parse_stdin(Buffer *n);
Buffer *load_buffer_list(Buffer *buf);
Buffer *load_help_file(Buffer *active);
Buffer *load_bookmarks(Buffer *active);
void find_inline_filename(Buffer *n);
char *extract_filename(const char *line);
/* Allocate the memory for a new buffer, and give it some nice defaults */
Buffer *new_buffer() {
Buffer *buf = xmalloc(sizeof(Buffer));
buf->lines = buf->line = 0;
buf->paused = initial_pause;
buf->is_file = 1;
buf->delay = initial_delay;
buf->jump = initial_jump;
buf->n = buf->p = NULL;
buf->file = NULL;
buf->filename = buf->message = NULL;
buf->markers = NULL;
buf->match = NULL;
return buf;
}
/* Delete a buffer, free all memory associated with it, and fix the list */
void delete_buffer(Buffer *buf) {
if (buf->n) buf->n->p = buf->p;
if (buf->p) buf->p->n = buf->n;
if (buf->match) regfree(buf->match);
if (buf->file) fclose(buf->file);
free(buf->match);
free(buf->filename);
free(buf->markers);
free(buf);
}
Buffer *next_buffer(Buffer *buf) {
if (buf->n != NULL) return buf->n;
buf->message = "You are at the last buffer.";
return buf;
}
Buffer *prev_buffer(Buffer *buf) {
if (buf->p != NULL && strcoll(buf->p->filename, B_BUFFERS)) return buf->p;
return load_buffer_list(buf);
}
int one(struct dirent *unused) { return 1; }
/* From the GNU fileutils package */
char *getuser (uid_t uid) {
struct userid *tail;
struct passwd *pwent;
for (tail = user_alist; tail; tail = tail->next)
if (tail->id.u == uid)
return tail->name;
pwent = getpwuid(uid);
tail = (struct userid *)xmalloc(sizeof(struct userid));
tail->id.u = uid;
tail->name = pwent ? strdup (pwent->pw_name) : NULL;
/* Add to the head of the list, so most recently used is first. */
tail->next = user_alist;
user_alist = tail;
return tail->name;
}
/* From the GNU fileutils package */
char *getgroup (gid_t gid) {
struct userid *tail;
struct group *grent;
for (tail = group_alist; tail; tail = tail->next)
if (tail->id.g == gid)
return tail->name;
grent = getgrgid (gid);
tail = (struct userid *)xmalloc(sizeof(struct userid));
tail->id.g = gid;
tail->name = grent ? strdup (grent->gr_name) : NULL;
/* Add to the head of the list, so most recently used is first. */
tail->next = group_alist;
group_alist = tail;
return tail->name;
}
/* Create a nice-looking file list for viewing directories */
FILE *display_directory(const char *filename) {
DIR *dir = opendir(filename);
FILE *file = tmpfile();
struct dirent **entry;
struct stat st;
char mode[11], stime[13];
int n, cnt;
mode[10] = '\0';
chdir(filename);
fprintf(file,"Mode User Group Size Modified Name\n");
fprintf(file,
"-----------------------------------------------------------\n");
n = scandir(filename, &entry, (void*)one, alphasort);
for (cnt = 0; cnt < n; ++cnt) { /* GNU libc info files */
stat(entry[cnt]->d_name, &st);
mode_string(st.st_mode, mode);
strftime(stime, 15, "%b %e %H:%M", localtime(&st.st_mtime));
fprintf(file, "%s %8s %8s %10d %s %s\n", mode, getuser(st.st_uid),
getgroup(st.st_gid), (int)st.st_size, stime, entry[cnt]->d_name);
free(entry[cnt]);
}
free(entry);
closedir(dir);
return file;
}
/* Copy stdin to a new internal buffer, and reopen it to be the tty */
void parse_stdin(Buffer *n) {
char line[1024], section[2], man[64];
n->is_file = 0;
n->file = tmpfile();
fgets(line, 1024, stdin);
if (sscanf(line, "%63[^( ](%1[0-9]) %*s", man, section) == 2) {
int i;
n->filename = xmalloc(sizeof(char) * 128);
for (i = 0; i < strlen(man); i++) man[i] = tolower(man[i]);
snprintf(n->filename, 128, "%s(%s)", man, section);
} else n->filename = strdup(B_STDIN);
fputs(line, n->file);
while (fgets(line, 1024, stdin)) fputs(line, n->file);
freopen("/dev/tty", "r", stdin);
}
/* Make a buffer that's a list of the rest of the buffers */
Buffer *load_buffer_list(Buffer *buf) {
Buffer *new_buf = new_buffer();
while (buf->p != NULL) buf = buf->p;
new_buf->filename = strdup(B_BUFFERS);
new_buf->file = tmpfile();
new_buf->is_file = 0;
if (!strcoll(buf->filename, B_BUFFERS)) {
buf = buf->n;
delete_buffer(buf->p);
}
buf->p = new_buf;
new_buf->n = buf;
for (; buf != NULL; buf = buf->n) {
int i;
fprintf(new_buf->file, "%s\t", buf->filename);
for (i = ((strlen(buf->filename) / 8) * 8) + 8; i != 60; i++)
fputc(' ', new_buf->file);
fprintf(new_buf->file, "%d/%d\n", buf->line, buf->lines);
}
rewind(new_buf->file);
find_line_markers(new_buf);
return new_buf;
}
Buffer *load_help_file(Buffer *active) {
Buffer *new_buf =
load_new_buffer(active, "/usr/local/share/reed/help");
if (new_buf == active)
new_buf = load_new_buffer(active, "/usr/share/reed/help");
if (new_buf != active) {
active->message = NULL;
active = new_buf;
} else {
active->message = "The help file was not found.";
}
return active;
}
Buffer *load_bookmarks(Buffer *active) {
Buffer *buf = active;
while (buf->p != NULL) buf = buf->p;
for (; buf != NULL; buf = buf->n) {
if (!strcoll(buf->filename, B_BOOKMARKS)) {
active = load_new_buffer(buf, bookmark_file);
free(active->filename);
active->is_file = 0;
active->filename = strdup(B_BOOKMARKS);
delete_buffer(buf);
return active;
}
}
active = load_new_buffer(active, bookmark_file);
free(active->filename);
active->is_file = 0;
active->filename = strdup(B_BOOKMARKS);
return active;
}
/* Create a new buffer from a file on disk */
Buffer *load_file(const char *filename) {
Buffer *buf = new_buffer();
struct stat st;
char *newfilename = NULL;
buf->filename = xmalloc(sizeof(char) * (PATH_MAX + 1));
if (strcoll(filename, "-")) { /* Not stdin... */
if (strstr(filename, "~/") == filename) {
newfilename = xmalloc(sizeof(char) * (strlen(filename) +
strlen(getenv("HOME"))));
sprintf(newfilename, "%s/%s", getenv("HOME"), filename + 2);
} else newfilename = strdup(filename);
if (realpath(newfilename, buf->filename) == NULL) {
delete_buffer(buf);
free(newfilename);
return NULL;
}
buf->filename = realloc(buf->filename,
sizeof(char) * (strlen(buf->filename) + 1));
stat(buf->filename, &st);
if (S_ISDIR(st.st_mode)) buf->file = display_directory(buf->filename);
else buf->file = fopen(buf->filename, "r");
if (buf->file == NULL) {
free(buf->filename);
free(buf);
free(newfilename);
return NULL;
}
} else parse_stdin(buf);
rewind(buf->file);
free(newfilename);
return buf;
}
/* Find the line breaks on \n or COLS characters, and fill out the
markers array in the node. */
void find_line_markers(Buffer *buf) {
unsigned int length = 100, i;
buf->lines = buf->line = 0;
if (buf->markers != NULL) free(buf->markers);
buf->markers = xmalloc(sizeof(int) * length);
buf->markers[0] = 0;
rewind(buf->file);
while (!feof(buf->file)) {
for (i = 0; i < COLS; i++) {
switch (fgetc(buf->file)) {
case '\b': i -= 2; break;
case '\n': i = COLS - 1; break;
case '\t': i = (((i / 8) + 1) * 8) - 1; break;
case '\0': case '\a': case 255: i--; break;
}
}
buf->lines++;
if (buf->lines == length) {
length *= 2;
buf->markers = realloc(buf->markers, sizeof(int) * length);
}
buf->markers[buf->lines] = ftell(buf->file);
}
buf->markers = realloc(buf->markers, sizeof(int) * (buf->lines + 1));
}
void pause_file(Buffer *buf) {
buf->paused = 1;
update_status_bar(buf);
nodelay(text, FALSE);
}
void unpause_file(Buffer *buf) {
buf->paused = 0;
update_status_bar(buf);
nodelay(text, TRUE);
}
void toggle_pause(Buffer *buf) {
buf->paused == 1 ? unpause_file(buf) : pause_file(buf);
}
/* Deal with all the nasty bits of actually displaying a line of the
file, with formatting, highlighted search, and so on. */
int seek_to_line_and_display(Buffer *buf, int line) {
int i;
/* Scrolled too far down. */
if (line > (buf->lines - 1)) {
line = (buf->lines - 1);
if (buf->jump > 0) pause_file(buf);
alert();
}
/* Too far up. */
if (line < 0) {
line = 0;
if (buf->lines > (LINES - 3)) alert();
wclear(text);
}
fseek(buf->file, buf->markers[line], SEEK_SET);
for (i = 0; i < LINES - 4 && line + i + 1 < buf->lines; i++) {
unsigned char l[buf->markers[line + i + 1] -
buf->markers[line + i] + 1];
char *dos_style_newline = NULL;
regmatch_t matched[5];
int j;
fgets(l, (int)(buf->markers[line + i + 1] -
buf->markers[line + i]) + 1, buf->file);
dos_style_newline = strstr(l, "\r\n");
if (dos_style_newline != NULL) dos_style_newline[0] = ' ';
matched[0].rm_eo = matched[0].rm_so = -1;
if (buf->match) regexec(buf->match, l, 5, matched, 0);
for (j = 0; j < strlen(l); j++) {
if (j >= matched[0].rm_so && j < matched[0].rm_eo)
wattron(text, A_STANDOUT);
if (l[j] == '\b') {
waddch(text, '\b');
if (l[j - 1] == l[j + 1]) wattron(text, A_BOLD);
else if (l[j - 1] == '_') wattron(text, A_UNDERLINE);
waddch(text, l[++j]);
} else waddch(text, l[j]);
wattroff(text, A_BOLD | A_UNDERLINE | A_STANDOUT);
}
}
for (; i < LINES - 4; i++) waddstr(text, "\n");
wrefresh(text);
return line;
}
void update_status_bar(Buffer *buf) {
char message[256], *paused = "";
mvwhline(status, 0, 0, ' ', COLS * 2);
if (buf->message == NULL) {
mvwaddstr(status, 0, 0, "File: ");
if (strlen(buf->filename) > COLS - 6) {
waddstr(status, "[...]"); /* Reverse truncate the filename
(the last characters are more likely to
be unique than the first) */
waddstr(status, buf->filename + (strlen(buf->filename) - COLS + 11));
} else waddstr(status, buf->filename);
} else mvwaddstr(status, 0, 0, buf->message);
if (buf->paused) paused = "Paused. ";
snprintf(message, 256, "%sDelay: %.1f, Jump: %d. %d/%d, %.1f%%",
paused, ((float) buf->delay) / 100000, buf->jump, buf->line,
buf->lines, (float)(buf->line) / (float)(buf->lines) * 100.0);
mvwaddstr(status, 1, COLS - strlen(message), message);
wrefresh(status);
}
/* Input a string from the status bar, with a prompt */
char *input_string(const char *message) {
char *value = xmalloc(sizeof(char) * 128);
mvwhline(status, 0, 0, ' ', COLS);
echo();
curs_set(1);
waddstr(status, message);
wgetnstr(status, value, 128);
noecho();
curs_set(0);
return value;
}
/* Same for a character */
char input_char(const char *message) {
char c = ERR;
mvwhline(status, 0, 0, ' ', COLS);
waddstr(status, message);
c = wgetch(status);
/* If this isn't done, weird things happen, e.g. pressing right arrow
generates ^[[C, which then executes the C command and (to the user's
surprise) clears all bookmarks. */
nodelay(status, TRUE);
while (wgetch(status) != ERR) {}
nodelay(status, FALSE);
return c;
}
/* And a floating point number... */
float input_decimal(const char *message) {
char *tmp = input_string(message);
float value = atof(tmp);
free(tmp);
return value;
}
/* And an integer. */
int input_integer(const char *message) {
return (int)(input_decimal(message));
}
/* Return the line number of a bookmark for the current file, or -1 if
the bookmark isn't found. */
int get_bookmark(const char *name, Buffer *buf) {
char line[1024], file[PATH_MAX + 1], markname[128];
int lineno = 0;
FILE *bf = fopen(bookmark_file, "r");
if (bf == NULL) return -1;
while (fgets(line, 1024, bf)) {
if (sscanf(line, "%[^\t]\t%d\t%127s\n", file, &lineno, markname) == 3) {
if (strcoll(file, buf->filename) == 0 && strcoll(markname, name) == 0) {
fclose(bf);
return lineno;
}
}
}
fclose(bf);
return -1;
}
/* Delete a specific bookmark in a file. */
void delete_bookmark(const char *name, Buffer *buf) {
char line[1024], tmp[strlen(getenv("HOME")) + 14];
FILE *bf = fopen(bookmark_file, "r"), *tmpf = NULL;
sprintf(tmp, "%s/.reed_XXXXXX", getenv("HOME"));
tmpf = fdopen(mkstemp(tmp), "a");
if (tmpf == NULL || bf == NULL) {
if (tmpf != NULL) fclose(tmpf);
if (bf != NULL) fclose(bf);
return;
}
while (fgets(line, 1024, bf)) {
char file[PATH_MAX + 1], markname[128];
if (sscanf(line, "%[^\t]\t%*[0-9]\t%127s\n", file, markname) == 2 &&
(strcoll(markname, name) || strcoll(file, buf->filename)))
fputs(line, tmpf);
}
fclose(bf); fclose(tmpf);
unlink(bookmark_file);
rename(tmp, bookmark_file);
}
/* Set a bookmark in a file, deleting any other bookmark of the same name
first. */
void set_bookmark(const char *name, Buffer *buf) {
FILE *bf;
int i;
if (get_bookmark(name, buf) != -1) delete_bookmark(name, buf);
bf = fopen(bookmark_file, "a");
if (bf == NULL) return;
fprintf(bf, "%s", buf->filename);
for (i = 48 - strlen(buf->filename); i > 0; i -= 8) fputc('\t', bf);
fputc('\t', bf);
fprintf(bf, "%d\t%s\n", buf->line, name);
fclose(bf);
}
/* Delete all bookmarks in the current file. */
void clear_bookmarks(Buffer *buf) {
char line[PATH_MAX + 140], tmp[strlen(getenv("HOME")) + 14];
FILE *bf = fopen(bookmark_file, "r"), *tmpf;
sprintf(tmp, "%s/.reed_XXXXXX", getenv("HOME"));
tmpf = fdopen(mkstemp(tmp), "a");
if (bf) {
while (fgets(line, PATH_MAX + 140, bf)) {
char *original = strdup(line);
char *file = strtok(line, "\t");
if (strcoll(file, buf->filename)) fprintf(tmpf, original);
free(original);
}
fclose(bf);
}
unlink(bookmark_file);
rename(tmp, bookmark_file);
fclose(tmpf);
}
/* Set/get single-character bookmarks */
void set_char_bookmark(char c, Buffer *buf) {
char s[2] = " "; s[0] = c;
set_bookmark(s, buf);
}
int get_char_bookmark(char c, Buffer *buf) {
char s[2] = " "; s[0] = c;
return get_bookmark(s, buf);
}
regex_t *make_regexp(const char *s) {
regex_t *match = xmalloc(sizeof(regex_t));
if (regcomp(match, s, 0))
return NULL;
return match;
}
int search_for(regex_t *match, Buffer *buf, int up_down) {
int j, to, found = -1;
if (match == NULL)
return buf->line;
if (up_down == 1) {
fseek(buf->file, buf->markers[buf->line + 1], SEEK_SET);
j = buf->line + 1;
to = buf->lines;
} else {
fseek(buf->file, buf->markers[0], SEEK_SET);
j = 0;
to = buf->line;
}
for (; j < to; j++) {
int num = buf->markers[j+1] - buf->markers[j];
char line[num + 1];
if (fgets(line, num+1, buf->file)) {
if (index(line, '\b')) { /* Remove formatting codes */
char *c = strdup(line);
int i, j = 0;
for (i = 0; i <= strlen(c); i++)
if (c[i] == '\b') j--;
else line[j++] = c[i];
free(c);
}
if (!(regexec(match, line, 0, NULL, 0))) {
found = j;
if (up_down == 1) break;
}
} else break;
}
return found;
}
/* Start a new search, freeing any memory related to an old one */
void new_search(Buffer *buf, int up_down) {
char *s = input_string("Search for: ");
int new_line;
if (!strcoll(s, "")) {
free(s);
buf->message = "Search cancelled.";
free_regexp(buf->match);
return;
} else if (buf->match != NULL) {
free_regexp(buf->match);
}
if ((buf->match = make_regexp(s)) == NULL) {
alert();
buf->message = "Error: Bad regular expression.";
free(s);
return;
}
buf->line -= up_down;
new_line = search_for(buf->match, buf, up_down);
if (new_line >= 0) {
buf->line = seek_to_line_and_display(buf, new_line);
} else if (new_line == -1) {
buf->line += up_down;
buf->message = "The search pattern was not found.";
buf->line = seek_to_line_and_display(buf, buf->line);
free_regexp(buf->match);
}
free(s);
pause_file(buf);
}
void spawn_editor(Buffer *buf) {
char *editor, *cline;
if (getenv("VISUAL")) { editor = getenv("VISUAL"); }
else if (getenv("EDITOR")) { editor = getenv("EDITOR"); }
else { editor = "/bin/ed"; }
cline = xmalloc(sizeof(char) *
(strlen(editor) + strlen(buf->filename) + 16));
sprintf(cline, "%s +%d \"%s\"", editor, buf->line, buf->filename);
system(cline);
free(cline);
}
/* Check the current buffer against a list of known "openable" buffer list
types, and if that fails, try to find something anyway. */
void find_inline_filename(Buffer *buf) {
char l[buf->markers[buf->line + 1] - buf->markers[buf->line] + 1];
fseek(buf->file, buf->markers[buf->line], SEEK_SET);
fgets(l, (int)(buf->markers[buf->line + 1] -
buf->markers[buf->line]) + 1, buf->file);
if (!strcoll(buf->filename, B_BOOKMARKS)) { /* Bookmarks file */
inline_file = strdup(strtok(l, "\t"));
strtok(NULL, " \t");
inline_mark_name = strdup(strtok(NULL, "\n"));
} else if (!strcoll(buf->filename, B_BUFFERS)) /* Buffer list */
inline_file = strdup(strtok(l, "\t"));
else { /* Some file that might have a filename */
struct stat st;
stat(buf->filename, &st);
if (S_ISDIR(st.st_mode)) {
int i = 0;
strtok(l, " \t");
for (i = 0; i < 6; i++) strtok(NULL, " \t");
inline_file = strtok(NULL, "\n");
if (inline_file != NULL) {
char *resolved_name = (char *)xmalloc(strlen(inline_file) +
strlen(buf->filename) + 3);
sprintf(resolved_name, "%s/%s", buf->filename, inline_file);
inline_file = resolved_name;
}
} else {
inline_file = extract_filename(l);
if (inline_file == NULL)
buf->message = "No filename was found on the current line.";
}
}
}
/* Try to extract a filename by checking each word in the line.
This is probably slow, especially over NFS. */
char *extract_filename(const char *line) {
char *working = strdup(line), *retval = NULL;
char *ptr = strchr(working, '/');
if (ptr) {
int i = 0;
while (ptr[i] != ' ' && ptr[i] != '\n' && ptr[i] != '\0') i++;
ptr[i] = '\0';
retval = strdup(ptr);
} else {
struct stat st;
char *s;
int v = 0;
for (s = strtok(working, " \t\n"); s != NULL && retval == NULL;
s = strtok(NULL, " \t\n")) {
v = stat(s, &st);
if (v != -1) retval = strdup(s);
}
}
free(working);
return retval;
}
/* FIXME: This could probably be done a lot better. */
/* I'm not going to bother documenting this, since if you don't understand
it but want to make it work better, just write a new one. :P */
void next_bracket(const char *left, const char *right, Buffer *buf,
int direction) {
regex_t *left_match = NULL, *right_match = NULL;
int left_pos, right_pos, found = -1, old_pos = buf->line;
left_match = make_regexp(left);
right_match = make_regexp(right);
if (direction == 1) {
buf->line--;
left_pos = search_for(left_match, buf, 1);
if (left_pos == -1) {
buf->message = "Error: The start character was not found.";
free_regexp(left_match);
free_regexp(right_match);
return;
}
right_pos = left_pos;
} else {
buf->line++;
right_pos = search_for(right_match, buf, -1);
if (right_pos == -1) {
buf->message = "Error: The start parenthesis was not found.";
buf->line = old_pos;
}
left_pos = right_pos;
}
while (found == -1) {
buf->line = left_pos;
left_pos = search_for(left_match, buf, direction);
buf->line = right_pos;
right_pos = search_for(right_match, buf, direction);
if ((right_pos == -1 && direction == 1) ||
(left_pos == -1 && direction == -1)) {
buf->message = "No matching parenthesis was found.";
found = old_pos;
}
else if (((right_pos < left_pos || left_pos == -1) && direction == 1) ||
((left_pos > right_pos || right_pos == -1) && direction == -1)) {
if (direction == 1) found = right_pos;
if (direction == -1) found = left_pos;
}
}
free_regexp(right_match);
free_regexp(left_match);
buf->line = seek_to_line_and_display(buf, found);
}
/* Set the currently displayed file buffer, and do the main input loop,
and the scrolling loop. */
int activate_buffer(Buffer *buf) {
unsigned long count = 0;
int c;
buf->line = seek_to_line_and_display(buf, buf->line);
buf->paused ? pause_file(buf) : unpause_file(buf);
update_status_bar(buf);
while ((c = wgetch(text)) != 'q') {
buf->message = NULL;
switch (c) {
case ERR: break;
case KEY_UP:
case 'k':
buf->line = seek_to_line_and_display(buf, buf->line - 1);
break;
case KEY_DOWN:
case 'e':
case 'j':
buf->line = seek_to_line_and_display(buf, buf->line + 1);
break;
case KEY_NPAGE:
case ' ':
case 'z':
buf->line =
seek_to_line_and_display(buf, buf->line + (LINES - 4));
break;
case KEY_PPAGE:
case 'w':
case 'b':
buf->line =
seek_to_line_and_display(buf, buf->line - (LINES - 4));
break;
case 'u':
buf->line =
seek_to_line_and_display(buf, buf->line - (LINES - 4) / 2);
break;
case 'd':
buf->line =
seek_to_line_and_display(buf, buf->line + (LINES - 4) / 2);
break;
case 'p':
toggle_pause(buf);
break;
case '\r':
case '\n':
if (buf->paused)
buf->line = seek_to_line_and_display(buf, buf->line + 1);
else count = buf->delay;
break;
case '-':
case '_':
if (buf->delay > 100000)
buf->delay -= 20000;
else
buf->delay /= 2;
break;
case '+':
case '=':
buf->delay += 20000;
break;
case '>':
case '.':
case 'G':
case KEY_END:
buf->line = seek_to_line_and_display(buf, buf->lines - (LINES - 3));
break;
case '<':
case ',':
case 'g':
case KEY_HOME:
buf->line = seek_to_line_and_display(buf, 0);
break;
case 's':
buf->delay =
input_decimal("Enter the time to wait between lines: ") * 100000;
break;
case 'J':
buf->jump =
input_integer("Enter the number of lines to skip at a time: ");
break;
case 'm':{
char *s = input_string("Go to bookmark: ");
int new_line = 0;
if (s != NULL) new_line = get_bookmark(s, buf);
if (new_line == -1) {
alert();
buf->message = "The bookmark was not found.";
} else buf->line = seek_to_line_and_display(buf, new_line);
pause_file(buf);
free(s);
break;
}
case 'M':{
char *s = input_string("Set bookmark: ");
if (strlen(s)) set_bookmark(s, buf);
free(s);
break;
}
case 'D':{
char *s = input_string("Delete bookmark: ");
delete_bookmark(s, buf);
free(s);
break;
}
case 'C': clear_bookmarks(buf); break;
case 'B': return BOOKMARKS;
case '\'':{
int new_line = get_char_bookmark(input_char("Go to "), buf);
if (new_line == -1) buf->message = "The bookmark was not found.";
else buf->line = seek_to_line_and_display(buf, new_line);
pause_file(buf);
break;
}
case '"':
set_char_bookmark(input_char("Set "), buf);
break;
case 'l':{
int new_line = get_bookmark("default", buf);
if (new_line == -1) {
alert();
buf->message = "The bookmark was not found.";
} else buf->line = seek_to_line_and_display(buf, new_line);
pause_file(buf);
break;
}
case 'L':
set_bookmark("default", buf);
break;
case '/': new_search(buf, 1); break;
case 'n':{
int new_line = search_for(buf->match, buf, 1);
if (new_line != -1)
buf->line = seek_to_line_and_display(buf, new_line);
break;
}
case '?': new_search(buf, -1); break;
case 'N':{
int new_line = search_for(buf->match, buf, -1);
if (new_line != -1)
buf->line = seek_to_line_and_display(buf, new_line);
break;
}
case '(': next_bracket("(",")", buf, 1); break;
case '{': next_bracket("{","}", buf, 1); break;
case '[': next_bracket("\\[","\\]", buf, 1); break;
case ')': next_bracket("(",")", buf, -1); break;
case '}': next_bracket("{","}", buf, -1); break;
case ']': next_bracket("\\[","\\]", buf, -1); break;
case 'F':
free_regexp(buf->match);
buf->line = seek_to_line_and_display(buf, buf->line);
break;
case '!':{
char *s = input_string("Run command: ");
endwin();
system(s);
free(s);
initialize_windows();
buf->line = seek_to_line_and_display(buf, buf->line);
break;
}
case '%':
buf->line = seek_to_line_and_display(buf,
buf->lines * (input_decimal("Go to percentage: ") / 100.0));
break;
case 'v':
endwin();
if (buf->is_file) spawn_editor(buf);
else buf->message = "This buffer is not an editable file.";
initialize_windows();
seek_to_line_and_display(buf, buf->line);
break;
case 'a':
cue ^= 1;
break;
case 't':
buf->line = seek_to_line_and_display(buf,
input_integer("Go to line: "));
break;
case 'r':
initialize_windows();
seek_to_line_and_display(buf, buf->line);
break;
case 'R':
free(buf->filename);
buf->filename = input_string("Enter a new file name: ");
buf->is_file = 0;
break;
case 'H':
return HELP;
case ':': /* Buffer manipulation submenu */
switch (input_char("n)ext, p)revious, d)elete, e)xamine, r)eload, o)pen, l)ist, or q)uit? ")) {
case 'n': return NEXT_FILE;
case 'p': return PREV_FILE;
case 'd': return DELETE_FILE;
case 'e': return LOAD_FILE;
case 'r': return RELOAD_FILE;
case 'o':
find_inline_filename(buf);
if (inline_file != NULL) return LOAD_INLINE;
break;
case 'l': return LOAD_LIST;
case 'q': return QUIT;
}
break;
default:
buf->message = "Unknown command. Try 'H' for help.";
}
update_status_bar(buf);
if (!(buf->paused) && buf->jump != 0) {
int num_jumps = abs(buf->jump);
count += 20000;
usleep(10000);
if (count > (buf->delay * num_jumps)) {
int direction = num_jumps / buf->jump, i = 0;
count = 0;
for (i = 0; i < num_jumps &&
((buf->line < buf->lines - (LINES - 3) && direction > 0) ||
(buf->line > 0 && direction < 0));
i++) {
buf->line = seek_to_line_and_display(buf, buf->line + direction);
update_status_bar(buf);
usleep(1); /* Smooth it, so it's easier to read */
}
if ((buf->jump < 0 && buf->line == 0) ||
(buf->jump > 0 && buf->line == buf->lines - (LINES - 3)))
pause_file(buf);
}
}
}
return QUIT;
}
/* Replace an existing buffer with one already open. */
void replace_buffer(Buffer *from, Buffer *to) {
find_line_markers(to);
to->n = from->n;
to->p = from->p;
to->line = from->line;
to->delay = from->delay;
to->jump = from->jump;
to->paused = from->paused;
delete_buffer(from);
if (to->n) to->n->p = to;
if (to->p) to->p->n = to;
}
/* Wrap load_file with some list-handling capabilities. */
Buffer *load_new_buffer(Buffer *old_buf, const char *filename) {
Buffer *new_buf = load_file(filename), *working = NULL, *first = old_buf;
while (first->p != NULL) first = first->p;
/* Check to see if the file is open already and remove it if it is... */
for (working = first; working != NULL; working = working->n)
if (new_buf && !strcoll(new_buf->filename, working->filename)) {
replace_buffer(working, new_buf);
return new_buf;
} else if (new_buf == NULL && !working->is_file &&
!strcoll(filename, working->filename)) {
working->message = "Buffer is already open, going to it.";
return working;
}
if (new_buf == NULL) {
alert();
old_buf->message = "There was an error loading the file.";
return old_buf;
} else {
if (old_buf->n) old_buf->n->p = new_buf;
new_buf->n = old_buf->n;
new_buf->p = old_buf;
old_buf->n = new_buf;
return new_buf;
}
}
void parse_rc_file() {
char rcfile[strlen(getenv("HOME")) + 9];
FILE *rc;
sprintf(rcfile, "%s/.reedrc", getenv("HOME"));
if ((rc = fopen(rcfile, "r")) != NULL) {
char line[512];
while (fgets(line, 512, rc)) {
char key[10], value[10];
if (sscanf(line, "%10s %10s\n", key, value) == 2) {
if (!strcasecmp(key, "delay")) initial_delay = (atof(value) * 100000);
else if (!strcasecmp(key, "jump")) initial_jump = atoi(value);
else if (!strcasecmp(key, "beep") && !strcasecmp(value, "off"))
cue = 0;
else if (!strcasecmp(key, "paused") && !strcasecmp(value, "off"))
initial_pause = 0;
}
}
fclose(rc);
}
}
void print_help() {
fprintf(stderr, "Reed %s - An autoscrolling text pager\n", VERSION);
fprintf(stderr, "Proper usage: reed [options] [filenames]\n");
fprintf(stderr, "Options (overriding ~/.reedrc):\n");
fprintf(stderr, " -d [num]\tScrolling delay in approximately tenths of a second.\n");
fprintf(stderr, " -j [num]\tNumber of lines to jump when scrolling.\n");
fprintf(stderr, " -u/-p\t\tStarted unpaused or paused, respectively.\n");
fprintf(stderr, " -b/-q\t\tTurn audio cues on or off, respectively.\n");
fprintf(stderr, " -v\t\tDisplay version and copyright information.\n");
fprintf(stderr, " -h\t\tDisplay this help text.\n");
exit(0);
}
void print_version() {
fprintf(stderr, "Reed %s - An autoscrolling text pager\n", VERSION);
fprintf(stderr, "Copyright (c)2000-2002 Joe Wreschnig\n\n");
fprintf(stderr, "This program is free software; you can redistribute it and/or modify\n");
fprintf(stderr, "it under the terms of the GNU General Public License as published by\n");
fprintf(stderr, "the Free Software Foundation; either version 2 of the License, or\n");
fprintf(stderr, "(at your option) any later version.\n\n");
fprintf(stderr, "This program is distributed in the hope that it will be useful,\n");
fprintf(stderr, "but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
fprintf(stderr, "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
fprintf(stderr, "GNU General Public License for more details.\n");
exit(0);
}
void initialize_windows(void) {
if (status != NULL) delwin(status);
if (text != NULL) delwin(text);
status = newwin(2, COLS, 0, 0);
text = newwin(LINES - 3, COLS, 3, 0);
mvhline(2, 0, '-', COLS); refresh();
scrollok(text, TRUE); noecho(); cbreak(); keypad(text, TRUE); curs_set(0);
wclear(text); wclear(status);
curs_set(0);
}
int main(int argc, char **argv) {
char *f = NULL, opt;
int action = 0, i = 0;
struct stat st;
Buffer *active = NULL;
if (argc > 1 && !strcoll(argv[1], "--help")) print_help();
bookmark_file = xmalloc(sizeof(char) * (strlen(getenv("HOME")) + 17));
sprintf(bookmark_file, "%s/.reed_bookmarks", getenv("HOME"));
parse_rc_file();
while ((opt = getopt(argc, argv, "bqhvd:j:puf:")) != -1) {
switch (opt) {
case 'h': print_help();
case 'v': print_version();
case 'd': initial_delay = atof(optarg) * 100000; break;
case 'j': initial_jump = atoi(optarg); break;
case 'p': initial_pause = 1; break;
case 'u': initial_pause = 0; break;
case 'b': cue = 1; break;
case 'q': cue = 0; break;
case 'f': f = optarg;
}
}
initscr();
for (i = optind; i < argc; i++) {
int valid = stat(argv[i], &st);
if (valid == -1 && strcoll(argv[i], "-")) {
fprintf(stderr, "reed: warning: %s: Invalid filename. Ignoring it...\n",
argv[i]);
} else {
if (active == NULL) active = load_file(argv[i]);
else {
Buffer *new_buf = load_file(argv[i]);
if (new_buf != NULL) {
active->n = new_buf;
new_buf->p = active;
active = new_buf;
}
}
if (f != NULL) {
free(active->filename);
active->filename = strdup(f);
active->is_file = 0;
}
}
}
if (active == NULL) {
if (isatty(fileno(stdin)) == 0) {
active = load_file("-");
} else {
endwin();
fprintf(stderr, "reed: error: No valid filenames were found.\n");
exit(1);
}
}
initialize_windows();
while (active->p != NULL) {
active = active->p;
}
waddstr(text, "Please wait... loading file.\n");
wrefresh(text);
find_line_markers(active);
while ((action = activate_buffer(active)) != QUIT) {
wclear(status);
wclear(text);
switch (action) {
case NEXT_FILE:
active = next_buffer(active);
break;
case PREV_FILE:
active = prev_buffer(active);
break;
case DELETE_FILE:
if (active->p) {
active = active->p;
delete_buffer(active->n);
} else if (active->n) {
active = active->n;
delete_buffer(active->p);
} else {
delete_buffer(active);
active = NULL;
}
break;
case RELOAD_FILE:
initialize_windows();
if (active->is_file) {
active = load_new_buffer(active, active->filename);
delete_buffer(active->p);
}
find_line_markers(active);
break;
case LOAD_FILE:
f = input_string("Enter a filename to load: ");
active = load_new_buffer(active, f);
free(f);
break;
case LOAD_INLINE:
active = load_new_buffer(active, inline_file);
if (inline_mark_name) {
int i = get_bookmark(inline_mark_name, active);
if (i >= 0) active->line = i;
free(inline_mark_name);
inline_mark_name = NULL;
}
free(inline_file);
inline_file = NULL;
break;
case LOAD_LIST:
active = load_buffer_list(active);
break;
case BOOKMARKS:
active = load_bookmarks(active);
break;
case HELP:
active = load_help_file(active);
break;
}
if (active == NULL) break;
else if (active->markers == NULL) {
waddstr(text, "Please wait... loading file.\n");
wrefresh(text);
find_line_markers(active);
}
}
endwin();
exit(0);
}
syntax highlighted by Code2HTML, v. 0.9.1