/* * UNG's Not GNU * * Copyright (c) 2011-2020, Jakob Kaivo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #define _XOPEN_SOURCE 700 #include #include #include #include #include #include #include #include enum { CTRL_B = 0x02, CTRL_D = 0x04, CTRL_F = 0x06, CTRL_G = 0x07, CTRL_L = 0x0c, CTRL_U = 0x15, }; struct { FILE *tty; struct termios original; int lines; int columns; int ret; } global = {0}; struct morefile { FILE *f; FILE *backing; size_t topline; fpos_t *lines; size_t nlines; size_t mark[26]; }; static ssize_t more_getline(struct morefile *mf, size_t lineno, char **line, size_t *n) { if (mf->nlines <= lineno && mf->nlines != 0) { fsetpos(mf->f, &(mf->lines[mf->nlines - 1])); getline(line, n, mf->f); } while (mf->nlines <= lineno) { mf->nlines++; mf->lines = realloc(mf->lines, mf->nlines * sizeof(*mf->lines)); fgetpos(mf->f, &(mf->lines[mf->nlines - 1])); getline(line, n, mf->f); if (mf->backing != mf->f) { fgetpos(mf->backing, &(mf->lines[mf->nlines - 1])); fputs(*line, mf->backing); } } fsetpos(mf->backing, &(mf->lines[lineno])); return getline(line, n, mf->backing); } void resetterm(void) { tcsetattr(fileno(global.tty), TCSANOW, &global.original); } void openrawtty(void) { struct termios term; global.tty = stderr; /* FIXME: only open /dev/tty if stderr is not readable */ // if (!(fcntl(fileno(global.tty), F_GETFL) & (O_WRONLY | O_RDWR))) { global.tty = fopen("/dev/tty", "rb+"); // } if (global.tty == NULL) { perror("Couldn't open tty for reading"); exit(1); } setbuf(global.tty, NULL); tcgetattr(fileno(global.tty), &global.original); term = global.original; term.c_lflag &= ~(ECHO | ICANON); term.c_cc[VMIN] = 1; term.c_cc[VTIME] = 0; tcsetattr(fileno(global.tty), TCSANOW, &term); atexit(resetterm); } void refresh(struct morefile *mf) { char *line = NULL; size_t n = 0; for (size_t i = mf->topline; i < mf->topline + global.lines; i++) { if (more_getline(mf, i, &line, &n) == -1) { break; } printf("%s", line); } free(line); } void scroll(struct morefile *mf, int count, int multiple) { char *line = NULL; size_t n = 0; int by = count ? count * multiple : multiple; if ((by < 0) && ((-by) > mf->topline)) { mf->topline = 0; refresh(mf); } else while (by-- > 0) { mf->topline++; more_getline(mf, mf->topline + global.lines + 1, &line, &n); printf("%s", line); } free(line); } void mark(struct morefile *mf) { int c = fgetc(global.tty); if (islower(c)) { mf->mark[c - 'a'] = mf->topline; } } void jump(struct morefile *mf) { int c = fgetc(global.tty); if (islower(c)) { mf->topline = mf->mark[c - 'a']; refresh(mf); } } int more(const char *file) { struct morefile mf = { .f = stdin, }; int count = 0; char *line = NULL; size_t nline = 0; if (strcmp(file, "-")) { mf.f = fopen(file, "r"); if (!mf.f) { fprintf(stderr, "more: %s: %s\n", file, strerror(errno)); global.ret = 1; return 1; } } fpos_t pos; if (fgetpos(mf.f, &pos) != 0) { mf.backing = tmpfile(); } else { mf.backing = mf.f; } refresh(&mf); while (mf.f) { int c = fgetc(global.tty); switch (c) { case EOF: exit(2); break; case 'h': //show_help(); printf("Help!"); break; case 'f': case CTRL_F: if (count == 0) { count = global.lines; } scroll(&mf, count, 1); break; case 'b': case CTRL_B: if (count == 0) { count = global.lines; } scroll(&mf, count, -1); break; case ' ': count = count ? count : global.lines; /* FALLTHRU */ case 'j': case '\n': scroll(&mf, count, 1); break; case 'k': scroll(&mf, count, -1); break; case 'd': case CTRL_D: scroll(&mf, count, global.lines / 2); break; case 's': count = count ? count : 1; scroll(&mf, global.lines + count, 1); break; case 'u': case CTRL_U: scroll(&mf, count, -global.lines / 2); break; case 'g': //scroll_beginning(count); break; case 'G': //scroll_end(count); break; case 'r': case CTRL_L: refresh(&mf); break; case 'R': // discard(); 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 ':': { fputc(c, global.tty); int c2 = fgetc(global.tty); fprintf(global.tty, "\b \b"); switch (c2) { case 'e': // examine(); break; case 'n': return count ? count : 1; case 'p': return count ? -count : -1; case 't': // tagstring(); break; case 'q': exit(0); default: break; } } case 'v': // invoke_editor(); break; case '=': case CTRL_G: // display_position(); break; case 'Z': if (fgetc(global.tty) != '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; } } return 0; } static int query_term(const char *cap, const char *variable, int def) { int n = 0; char cmd[64]; snprintf(cmd, sizeof(cmd), "tput %s", cap); FILE *f = popen(cmd, "r"); if (f) { if (fscanf(f, "%d", &n) != 1) { n = 0; } pclose(f); if (n != 0) { return n; } } char *value = getenv(variable); if (value) { n = atoi(value); } return n ? n : def; } 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; } int main(int argc, char *argv[]) { int c; global.lines = query_term("lines", "LINES", 24); global.columns = query_term("cols", "COLUMNS", 80); int clear = 0; int fastexit = 0; int ignorecase = 0; int compressempty = 0; int backspace = 1; adjust_args(&argc, &argv); while ((c = getopt(argc, argv, "ceisun:p:t:")) != -1) { switch (c) { case 'c': 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': global.lines = atoi(optarg); break; case 'p': //global.perfile = optarg; break; case 't': //global.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; } openrawtty(); global.lines -= 2; if (optind >= argc) { more("-"); } int min = optind; int max = argc - 1; while (optind < argc) { int next = more(argv[optind]); optind += next; if (optind < min) { optind = min; } if (optind > max) { optind = max; } } return global.ret; }