1/* more.c - View FILE (or stdin) one screenful at a time.
2 *
3 * Copyright 2013 Bilal Qureshi <bilal.jmi@gmail.com>
4 *
5 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/more.html
6
7USE_MORE(NEWTOY(more, 0, TOYFLAG_USR|TOYFLAG_BIN))
8
9config MORE
10  bool "more"
11  default n
12  help
13    usage: more [FILE...]
14
15    View FILE(s) (or stdin) one screenful at a time.
16*/
17
18#define FOR_more
19#include "toys.h"
20
21GLOBALS(
22  struct termios inf;
23  int cin_fd;
24)
25
26static void signal_handler(int sig)
27{
28  // Reset the terminal whether we were signalled or exited normally.
29  tcsetattr(TT.cin_fd, TCSANOW, &TT.inf);
30
31  if (sig == 0) _exit(0);
32
33  // We were actually signalled, so move to a new line and re-raise the signal.
34  xputc('\n');
35  signal(sig, SIG_DFL);
36  raise(sig);
37  _exit(sig | 128);
38}
39
40static void show_file_header(const char *name)
41{
42  printf(":::::::::::::::::::::::\n%s\n:::::::::::::::::::::::\n", name);
43}
44
45static int prompt(FILE *cin, const char* fmt, ...)
46{
47  int input_key;
48  va_list ap;
49
50  printf("\33[7m"); // Reverse video before printing the prompt.
51
52  va_start(ap, fmt);
53  vfprintf(stdout, fmt, ap);
54  va_end(ap);
55
56  while (1) {
57    fflush(NULL);
58    input_key = tolower(getc(cin));
59    printf("\33[0m\33[1K\r"); // Reset all attributes, erase to start of line.
60    if (strchr(" \nrq", input_key)) {
61      fflush(NULL);
62      return input_key;
63    }
64    printf("\33[7m(Enter:Next line Space:Next page Q:Quit R:Show the rest)");
65  }
66}
67
68static void do_cat_operation(int fd, char *name)
69{
70  if (toys.optc > 1) show_file_header(name);
71  xsendfile(fd, 1);
72}
73
74void more_main()
75{
76  int ch, input_key = 0, show_prompt;
77  unsigned rows = 24, cols = 80, row = 0, col = 0;
78  struct stat st;
79  struct termios newf;
80  FILE *fp, *cin;
81
82  if (!isatty(1) || !(cin = fopen("/dev/tty", "r"))) {
83    loopfiles(toys.optargs, do_cat_operation);
84    return;
85  }
86
87  TT.cin_fd = fileno(cin);
88  tcgetattr(TT.cin_fd, &TT.inf);
89
90  //Prepare terminal for input
91  memcpy(&newf, &TT.inf, sizeof(struct termios));
92  newf.c_lflag &= ~(ICANON | ECHO);
93  newf.c_cc[VMIN] = 1;
94  newf.c_cc[VTIME] = 0;
95  tcsetattr(TT.cin_fd, TCSANOW, &newf);
96
97  sigatexit(signal_handler);
98
99  do {
100    fp = stdin;
101    if (*toys.optargs && !(fp = fopen(*toys.optargs, "r"))) {
102        perror_msg("%s", *toys.optargs);
103        goto next_file;
104    }
105    st.st_size = show_prompt = col = row = 0;
106    fstat(fileno(fp), &st);
107    terminal_size(&cols, &rows);
108    rows--;
109
110    if (toys.optc > 1) {
111      show_file_header(*toys.optargs);
112      row += 3;
113    }
114
115    while ((ch = getc(fp)) != EOF) {
116      if (input_key != 'r' && show_prompt) {
117        if (st.st_size)
118          input_key = prompt(cin, "--More--(%d%% of %lld bytes)",
119              (int) (100 * ( (double) ftell(fp) / (double) st.st_size)),
120              (long long)st.st_size);
121        else
122          input_key = prompt(cin, "--More--");
123        if (input_key == 'q') goto stop;
124
125        col = row = show_prompt = 0;
126        terminal_size(&cols, &rows);
127        rows--;
128      }
129
130      putchar(ch);
131      if (ch == '\t') col = (col | 0x7) + 1;
132      else col++;
133      if (col == cols) putchar(ch = '\n');
134      if (ch == '\n') {
135        col = 0;
136        if (++row >= rows || input_key == '\n') show_prompt = 1;
137      }
138    }
139    fclose(fp);
140
141next_file:
142    if (*toys.optargs && *++toys.optargs) {
143      input_key = prompt(cin, "--More--(Next file: %s)", *toys.optargs);
144      if (input_key == 'q') goto stop;
145    }
146  } while (*toys.optargs);
147
148stop:
149  tcsetattr(TT.cin_fd, TCSANOW, &TT.inf);
150  fclose(cin);
151  // Even if optarg not found, exit value still 0
152  toys.exitval = 0;
153}
154