-
Notifications
You must be signed in to change notification settings - Fork 0
/
page.c
180 lines (157 loc) · 4.83 KB
/
page.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#define _POSIX_C_SOURCE 1
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
static FILE* TTY = NULL;
static struct termios* TERM = NULL;
static int PROGRESS = 0, SIZE = 0;
typedef enum {DEFAULT, ESCAPE, NF, CSI, FINAL} EscState; // for ansi esc codes
static int movecursor(int ch, int column) {
if (ch == '\r')
return 0;
if (ch == '\t')
return column + (8 - column % 8);
if (ch == '\b')
return column > 0 ? column - 1 : 0;
if ((ch < 0x80 && !isprint(ch)) || (ch >= 0x80 && ch <= 0xBF))
return column; // non-printing ascii or non-initial utf8 bytes
return column + 1;
}
// handle ANSI escape codes
// http://www.inwap.com/pdp10/ansicode.txt
// https://en.wikipedia.org/wiki/ANSI_escape_code
static EscState transition(EscState state, int ch) {
if (ch < 0x20 || ch > 0x7F)
return ch == 0x1B ? ESCAPE : DEFAULT;
switch (state) {
case ESCAPE:
if (ch >= 0x20 && ch <= 0x2F)
return NF; // nF 3-byte code
return ch == '[' ? CSI : FINAL; // Fp, Fe, Fs 2-byte codes
case NF:
return FINAL;
case CSI:
return ch >= 0x20 && ch <= 0x3F ? CSI : FINAL;
default:
return DEFAULT;
}
}
static int visible(int ch) {
return movecursor(ch, 0) > 0;
}
static int end(int ch) {
return ch == '\n' || ch == EOF;
}
static int printline(int columns, FILE* input) {
int column = 0, ch = fgetc(input);
EscState state = transition(DEFAULT, ch);
// avoid splitting unicode characters and ansi escape codes
while ((column < columns || !visible(ch) || state != DEFAULT) && !end(ch)) {
fputc(ch, stdout);
PROGRESS += 1;
if (state == DEFAULT)
column = movecursor(ch, column);
ch = fgetc(input);
state = transition(state, ch);
}
if (ch == EOF)
return ch;
if (ch == '\n')
PROGRESS += 1;
else
ungetc(ch, input);
fputc('\n', stdout);
return ch;
}
static void erase(void) {
if (SIZE > 0)
fputs("\r \r", stdout);
}
static int printlines(int rows, int columns, FILE* input) {
int ch = 0;
erase();
for (int i = 0; i < rows && ch != EOF; i++)
ch = printline(columns, input);
if (SIZE > 0)
fprintf(stdout, "--(%d%%)--", (100*PROGRESS)/SIZE);
fflush(stdout);
return ch;
}
static void quit(int signal) {
erase();
if (TTY != NULL && TERM != NULL)
tcsetattr(fileno(TTY), TCSANOW, TERM);
exit(signal == 0 ? 0 : 1);
}
int main(int argc, char* argv[]) {
int rows = 24, columns = 80;
int istty = isatty(fileno(stdout));
FILE* input = stdin;
if ((argc < 2 && isatty(fileno(stdin))) || argc > 2)
return fprintf(stderr, "usage: %s [file]\n", argv[0]), 2;
if (argc == 2) {
input = fopen(argv[1], "r");
if (input == NULL)
return perror("cannot open file"), 1;
struct stat stats;
if (istty && fstat(fileno(input), &stats) == 0)
SIZE = stats.st_size;
}
TTY = fopen(ctermid(NULL), "r");
if (TTY == NULL)
return perror("cannot open tty"), 1;
struct winsize ws;
if (ioctl(fileno(TTY), TIOCGWINSZ, &ws) != -1) {
rows = ws.ws_row;
columns = ws.ws_col;
}
if (!istty) {
while (printlines(rows - 1, columns, input) != EOF);
quit(0);
}
int ch = printlines(rows - 1, columns, input);
if (ch == EOF)
quit(0);
// ensure that terminal settings are restored before exiting
struct sigaction action = {.sa_handler = &quit};
sigaction(SIGINT, &action, NULL); // Ctrl-C
sigaction(SIGQUIT, &action, NULL); // Ctrl-'\'
sigaction(SIGTSTP, &action, NULL); // Ctrl-Z
sigaction(SIGTERM, &action, NULL); // kill
sigaction(SIGHUP, &action, NULL); // hangup
// disable keypress buffering and echo
struct termios term;
if (tcgetattr(fileno(TTY), &term) != 0)
return perror("tcgetattr"), 1;
tcflag_t oldflags = term.c_lflag;
term.c_lflag &= ~ICANON & ~ECHO;
if (tcsetattr(fileno(TTY), TCSANOW, &term) != 0)
return perror("tcsetattr"), 1;
term.c_lflag = oldflags;
TERM = &term;
while (ch != EOF) {
switch (fgetc(TTY)) {
case '\n':
ch = printlines(1, columns, input);
break;
case 'd':
ch = printlines(rows / 2, columns, input);
break;
case 't':
if (fseek(input, 0, SEEK_SET) != 0)
break;
PROGRESS = 0; // fallthrough
case ' ':
ch = printlines(rows - 1, columns, input);
break;
case 'q':
quit(0);
}
}
quit(0);
}