#define _XOPEN_SOURCE 700 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "more.h" static int more_clear = 0; static int retval = 0; static WINDOW *more_win = NULL; static WINDOW *more_status = NULL; enum { CTRL_B = 0x02, CTRL_D = 0x04, CTRL_F = 0x06, CTRL_G = 0x07, CTRL_L = 0x0c, CTRL_U = 0x15, }; static int more(const char *); ssize_t more_getline(struct more_file *mf, size_t lineno) { if (mf->nlines <= lineno && mf->nlines != 0) { fsetpos(mf->f, &(mf->tlines[mf->nlines - 1])); getline(&(mf->buf), &(mf->nbuf), mf->f); } while (mf->nlines <= lineno) { mf->nlines++; mf->tlines = realloc(mf->tlines, mf->nlines * sizeof(*mf->tlines)); mf->bytepos = realloc(mf->bytepos, mf->nlines * sizeof(*mf->bytepos)); fgetpos(mf->f, &(mf->tlines[mf->nlines - 1])); if (getline(&(mf->buf), &(mf->nbuf), mf->f) == -1) { return -1; } if (mf->nlines > 1) { mf->bytepos[mf->nlines - 1] = mf->bytepos[mf->nlines - 2] + strlen(mf->buf); } else { mf->bytepos[0] = 0; } if (mf->backing != mf->f) { fgetpos(mf->backing, &(mf->tlines[mf->nlines - 1])); fputs(mf->buf, mf->backing); } } fsetpos(mf->backing, &(mf->tlines[lineno])); return getline(&(mf->buf), &(mf->nbuf), mf->backing); } struct more_file more_open(const char *path) { struct more_file mf = { .f = stdin, }; if (strcmp(path, "-")) { mf.f = fopen(path, "r"); if (!mf.f) { fprintf(stderr, "more: %s: %s\r\n", path, strerror(errno)); return mf; } } fpos_t pos; if (fgetpos(mf.f, &pos) != 0) { mf.backing = tmpfile(); } else { mf.backing = mf.f; struct stat st; fstat(fileno(mf.f), &st); mf.nbytes = st.st_size; } mf.path = strdup(path); return mf; } void more_close(struct more_file *mf) { if (mf->backing != mf->f) { fclose(mf->backing); } if (mf->f != stdin) { fclose(mf->f); } free(mf->tlines); free(mf->buf); free(mf->path); } static void more_refresh(struct more_file *mf) { for (size_t i = mf->topline; i < mf->topline + LINES; i++) { /* FIXME: account for long lines */ if (more_getline(mf, i) == -1) { break; } wprintw(more_win, "%s", mf->buf); } } static void more_scroll(struct more_file *mf, int count, int multiple) { int by = count ? count * multiple : multiple; if (abs(by) >= LINES && more_clear) { clear(); } if (by < 0) { wscrl(more_win, by); if ((size_t)(-by) > mf->topline) { mf->topline = 0; } else { mf->topline += by; } more_refresh(mf); return; } while (by-- >= 0) { /* FIXME: account for long lines here, too */ mf->topline++; if (more_getline(mf, mf->topline + LINES) < 0) { break; } wprintw(more_win, "%s", mf->buf); } } static void mark(struct more_file *mf) { int c = wgetch(more_status); if (islower(c)) { mf->mark[c - 'a'] = mf->topline; } } static void jump(struct more_file *mf) { int c = wgetch(more_status); if (islower(c)) { mf->topline = mf->mark[c - 'a']; more_refresh(mf); } } static void more_help(void) { char path[L_tmpnam + 1]; FILE *f = fopen(tmpnam(path), "w"); if (f == NULL) { wprintw(more_status, "error creating help: %s", strerror(errno)); return; } fprintf(f, "^X means hold control and press X\n"); fprintf(f, "\n"); fprintf(f, "Non-Scrolling Commands\n"); fprintf(f, "h display this help\n"); fprintf(f, ":e filename open filename\n"); fprintf(f, "#:n go to the count-th next file\n"); fprintf(f, "#:p go to the count-th previous file\n"); fprintf(f, "v edit file with $EDITOR (default vi)\n"); fprintf(f, "= ^G display current file information\n"); fprintf(f, "q :q ZZ quit\n"); fprintf(f, "r ^L redraw the current screen\n"); fprintf(f, "R discard buffered input and redraw screen\n"); fprintf(f, "\n"); fprintf(f, "Scrolling Commands: Prefix with a count for number of lines, otherwise as shown\n"); fprintf(f, "f ^F forward one screen\n"); fprintf(f, "b ^B backward one screen\n"); fprintf(f, "j forward one line\n"); fprintf(f, "k backward one line\n"); fprintf(f, "d ^D forward one half screen\n"); fprintf(f, "u ^U backward one half screen\n"); fprintf(f, "\n"); fprintf(f, "Jumping Commands\n"); fprintf(f, "#s skip count lines after the end of the current screen\n"); fprintf(f, "#g go to line count, or 1\n"); fprintf(f, "#G go to line count, or end of file\n"); fprintf(f, ":t tag jump to tag\n"); fprintf(f, "mletter mark the current position as letter\n"); fprintf(f, "'letter return to the position marked letter\n"); fprintf(f, "'' return to last position before large movement\n"); fprintf(f, "\n"); fprintf(f, "Searching Commands\n"); fprintf(f, "#/[!]pattern search for the count-th occurence of pattern\n"); fprintf(f, "#?[!]pattern search backward\n"); fprintf(f, "#n repeat search\n"); fprintf(f, "#N repeat search backwards\n"); fclose(f); more(path); remove(path); } static struct more_file more_invoke_editor(struct more_file *mf) { extern char **environ; char *editor = getenv("EDITOR"); if (editor == NULL) { editor = "vi"; } char line[64]; snprintf(line, sizeof(line), "%zd", mf->topline + 1); char *argv[] = { editor, "-c", line, mf->path, NULL }; char *base = editor; char *slash = strrchr(editor, '/'); if (slash) { base = slash + 1; } if (strcmp(base, "ex") && strcmp(base, "vi")) { argv[1] = mf->path; argv[2] = NULL; } struct more_file ret = *mf; def_prog_mode(); reset_shell_mode(); pid_t pid; if (posix_spawnp(&pid, editor, NULL, NULL, argv, environ) == 0) { char *path = strdup(mf->path); size_t line = mf->topline; more_close(mf); waitpid(pid, NULL, 0); ret = more_open(path); ret.topline = line; free(path); } else { wprintw(more_status, "%s %s: %s\n", editor, mf->path, strerror(errno)); } reset_prog_mode(); return ret; } static struct more_file more_examine(struct more_file *mf) { wprintw(more_status, "e"); echo(); char filename[1024]; wgetnstr(more_status, filename, sizeof(filename)); noecho(); wclear(more_status); wordexp_t we = { 0 }; if (wordexp(filename, &we, WRDE_NOCMD) != 0) { wprintw(more_status, "couldn't expand %s", filename); return *mf; } if (we.we_wordc == 0) { return *mf; } strcpy(filename, we.we_wordv[0]); wordfree(&we); if (we.we_wordc > 1) { wprintw(more_status, "not opening multiple files"); return *mf; } if (!strcmp(filename, "#")) { strcpy(filename, mf->path); } struct more_file ret = more_open(filename); if (ret.f == NULL) { wprintw(more_status, "%s: %s", filename, strerror(errno)); return *mf; } more_close(mf); more_refresh(&ret); return ret; } static int more(const char *path) { struct more_file mf = more_open(path); if (mf.f == NULL) { retval = 1; return 1; } more_refresh(&mf); wrefresh(more_win); int count = 0; while (mf.f) { int at_eof = 0; //(mf.topline + LINES >= mf.nlines); wmove(more_status, 0, 0); if (at_eof) { wprintw(more_status, "%s ", mf.path); } int c = wgetch(more_status); wclear(more_status); switch (c) { case EOF: exit(2); break; case 'h': more_help(); break; case 'f': case CTRL_F: if (at_eof) { return 1; } if (count == 0) { count = LINES - 1; } more_scroll(&mf, count, 1); break; case 'b': case CTRL_B: if (count == 0) { count = LINES - 1; } more_scroll(&mf, count, -1); break; case ' ': count = count ? count : LINES - 1; /* FALLTHRU */ case 'j': case '\n': if (at_eof) { return 1; } more_scroll(&mf, count, 1); break; case 'k': more_scroll(&mf, count, -1); break; case 'd': case CTRL_D: if (at_eof) { return 1; } more_scroll(&mf, count, (LINES - 1) / 2); break; case 's': if (at_eof) { return 1; } count = count ? count : 1; more_scroll(&mf, LINES - 1 + count, 1); break; case 'u': case CTRL_U: more_scroll(&mf, count, -(LINES - 1) / 2); break; case 'G': if (count == 0) { count = mf.nbytes; } /* FALLTHRU */ case 'g': more_scroll(&mf, count - mf.topline, 1); break; case 'R': // discard(); /* FALLTHRU */ case 'r': case CTRL_L: more_refresh(&mf); break; case 'm': mark(&mf); break; case '\'': jump(&mf); break; case '/': //search(count); break; case '?': //search(-count); break; case 'n': //repeatsearch(count); break; case 'N': //repeatsearch(-count); break; case ':': waddch(more_status, c); echo(); int c2 = wgetch(more_status); noecho(); wprintw(more_status, "\b \b"); switch (c2) { case 'e': mf = more_examine(&mf); break; case 'n': more_close(&mf); return count ? count : 1; case 'p': more_close(&mf); return count ? -count : -1; case 't': // tagstring(); break; case 'q': exit(0); default: break; } break; case 'v': mf = more_invoke_editor(&mf); break; case '=': case CTRL_G: wprintw(more_status, "%s; File %d/%d; Line %zd; Byte %zd/%zd; %zd%%", mf.path, 0, 0, mf.topline, mf.bytepos[mf.topline], mf.nbytes, 100 * mf.bytepos[mf.topline] / mf.nbytes); break; case 'Z': if (wgetch(more_status) != 'Z') { break; } /* FALLTHRU */ case 'q': exit(0); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': count = count * 10 + (c - '0'); break; default: break; } if (!isdigit(c)) { count = 0; } wrefresh(more_win); } more_close(&mf); return 0; } static void adjust_args(int *argc, char ***argv) { char *env = getenv("MORE"); if (env) { char **newargv = malloc((*argc + 2) * sizeof(*newargv)); newargv[0] = (*argv)[0]; /* TODO: account for spaces in env */ newargv[1] = env; for (int i = 1; i < *argc; i++) { newargv[i + 1] = (*argv)[i]; } newargv[*argc + 1] = NULL; *argv = newargv; (*argc)++; } for (int i = 1; i < *argc; i++) { if (!strcmp((*argv)[i], "--")) { return; } if ((*argv)[i][0] == '+') { fprintf(stderr, "more: adjusting command line option +%s to -%s\n", (*argv)[i] + 1, (*argv)[i] + 1); (*argv)[i][0] = '-'; } } } static void cat_loop(FILE *f) { int c = 0; while ((c = fgetc(f)) != EOF) { putchar(c); } } static void compress_loop(FILE *f) { static int nl = 0; int c = 0; while ((c = fgetc(f)) != EOF) { if (c == '\n') { if (nl == 2) { continue; } else if (nl == 1) { nl = 2; } else { nl = 1; } } else { nl = 0; } putchar(c); } } static int more_cat(const char *path, void (*loop)(FILE *)) { FILE *f = stdin; if (path != NULL && strcmp(path, "-") != 0) { f = fopen(path, "r"); if (!f) { fprintf(stderr, "more: %s: %s\n", path, strerror(errno)); return 1; } } loop(f); if (f != stdin) { fclose(f); } return 0; } static void more_exit(void) { endwin(); } int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); int fastexit = 0; int ignorecase = 0; int compressempty = 0; int backspace = 1; adjust_args(&argc, &argv); int c; while ((c = getopt(argc, argv, "ceisun:p:t:")) != -1) { switch (c) { case 'c': more_clear = 1; break; case 'e': fastexit = 1; break; case 'i': ignorecase = 1; break; case 's': compressempty = 1; break; case 'u': backspace = 0; break; case 'n': setenv("LINES", optarg, 1); break; case 'p': //perfile = optarg; break; case 't': //tag = optarg; break; default: return 1; } } if (!isatty(STDOUT_FILENO)) { int ret = 0; void (*loop)(FILE*) = compressempty ? compress_loop : cat_loop; do { ret |= more_cat(argv[optind++], loop); } while (optind < argc); return ret; } if (isatty(STDIN_FILENO)) { initscr(); } else { FILE *tty = fopen("/dev/tty", "r"); if (tty == NULL) { perror("more: couldn't open terminal"); return 1; } newterm(NULL, stdout, tty); } cbreak(); noecho(); more_win = newwin(LINES - 1, 0, 0, 0); scrollok(more_win, TRUE); more_status = newwin(1, 0, LINES - 1, 0); touchwin(stdscr); atexit(more_exit); if (more_clear) { clear(); } if (optind >= argc) { more("-"); } int min = optind; while (optind < argc) { int next = more(argv[optind]); optind += next; if (optind < min) { optind = min; } } (void)fastexit; (void)ignorecase; (void)backspace; return retval; }