/* $Id$ */
#include "bbs.h"
/* 把這兩個 size 調到一頁的範圍是不是能降低不必要的 IO ? */
#define MORE_BUFSIZE 4096
#define MORE_WINSIZE 4096
#define STR_ANSICODE "[0123456789;,"
struct MorePool {
int base, size, head;
unsigned char more_pool[MORE_BUFSIZE];
};
static const char * const more_help[] = {
"\0閱\讀文章功\能鍵使用說明",
"\01游標移動功\能鍵",
"(↑) 上捲一行",
"(↓)(Enter) 下捲一行",
"(^B)(PgUp)(BackSpace) 上捲一頁",
"(→)(PgDn)(Space) 下捲一頁",
"(0)(g)(Home) 檔案開頭",
"($)(G) (End) 檔案結尾",
"\01其他功\能鍵",
"(/) 搜尋字串",
"(n/N) 重複正/反向搜尋",
"(TAB) URL連結",
"(Ctrl-T) 存到暫存檔",
"(:/f/b) 跳至某頁/下/上篇",
"(a/A) 跳至同一作者下/上篇",
"([-/]+) 主題式閱\讀 上/下",
"(t) 主題式循序閱\讀",
"(q)(←) 結束",
"(h)(H)(?) 輔助說明畫面",
NULL
};
#if DEPRECATED__UNUSED
int beep = 0;
#endif
static void
more_goto(int fd, struct MorePool *mp, off_t off)
{
int base = mp->base;
if (off < base || off >= base + mp->size) {
mp->base = base = off & (-MORE_WINSIZE);
lseek(fd, base, SEEK_SET);
mp->size = read(fd, mp->more_pool, MORE_BUFSIZE);
}
mp->head = off - base;
}
static int
more_readln(int fd, struct MorePool *mp, unsigned char *buf)
{
int ch;
unsigned char *data, *tail, *cc;
int len, bytes, in_ansi, in_big5;
int size, head, ansilen;
len = bytes = in_ansi = in_big5 = ansilen = 0;
tail = buf + ANSILINELEN - 1;
size = mp->size;
head = mp->head;
data = &mp->more_pool[head];
do {
if (head >= size) {
mp->base += size;
data = mp->more_pool;
mp->size = size = read(fd, data, MORE_BUFSIZE);
if (size == 0)
break;
head = 0;
}
ch = *data++;
head++;
bytes++;
if (ch == '\n') {
break;
}
if (ch == '\t') {
do {
*buf++ = ' ';
}
while ((++len & 7) && len < t_columns);
} else if (ch == '\033') {
if (atoi((char *)(data + 1)) > 47) {
if ((cc = (unsigned char *)strchr((char *)(data + 1), 'm')) != NULL) {
ch = cc - data + 1;
data += ch;
head += ch;
bytes += ch;
}
} else {
*buf++ = ch;
in_ansi = 1;
}
} else if (in_ansi) {
*buf++ = ch;
if (!strchr(STR_ANSICODE, ch))
in_ansi = 0;
} else if (in_big5) {
++len;
*buf++ = ch;
in_big5 = 0;
} else if (isprint2(ch)) {
len++;
*buf++ = ch;
if (ch >= 0xa1 && ch <= 0xf9) /* first byte of big5 encoding */
in_big5 = 1;
}
} while (len < t_columns && buf < tail);
if (in_big5 && len >= t_columns) {
--buf;
--head;
--data;
--bytes;
}
if (len == t_columns && head < size && *data == '\n') {
/* XXX: not handle head==size, should read data */
/* no extra newline dirty hack for exact 80byte line */
data++; bytes++; head++;
}
*buf = '\0';
mp->head = head;
return bytes;
}
int
more(char *fpath, int promptend)
{
char *head[4] = {"作者", "標題", "時間", "轉信"};
char *ptr, *word = NULL, buf[ANSILINELEN + 1];
struct stat st;
int fd, fsize;
unsigned int pagebreak[MAX_PAGES], pageno, lino = 0;
int line, ch, viewed, pos, numbytes;
int header = 0;
int local = 0;
char search_char0 = 0;
static char search_str[81] = "";
typedef char *(*FPTR) ();
static FPTR fptr;
int searching = 0;
int scrollup = 0;
char *printcolor[3] = {"44", "33;45", "0;34;46"}, color = 0;
struct MorePool mp;
/* Ptt */
memset(pagebreak, 0, sizeof(pagebreak));
if (*search_str)
search_char0 = *search_str;
*search_str = 0;
fd = open(fpath, O_RDONLY, 0600);
if (fd < 0)
return -1;
if (fstat(fd, &st) || ((fsize = st.st_size) <= 0) || S_ISDIR(st.st_mode)) {
close(fd);
//Ptt
return -1;
}
pagebreak[0] = pageno = viewed = line = pos = 0;
clear();
/* rocker */
mp.base = mp.head = mp.size = 0;
while ((numbytes = more_readln(fd, &mp, (unsigned char *)buf)) || (line == t_lines)) {
if (scrollup) {
rscroll();
move(0, 0);
}
if (numbytes) { /* 一般資料處理 */
if (!viewed) { /* begin of file */
if (!strncmp(buf, str_author1, LEN_AUTHOR1)) {
line = 3;
word = buf + LEN_AUTHOR1;
local = 1;
} else if (!strncmp(buf, str_author2, LEN_AUTHOR2)) {
line = 4;
word = buf + LEN_AUTHOR2;
}
while (pos < line) {
if (!pos && ((ptr = strstr(word, str_post1)) ||
(ptr = strstr(word, str_post2)))) {
ptr[-1] = '\0';
prints("\033[47;34m %s \033[44;37m%-53.53s"
"\033[47;34m %.4s \033[44;37m%-13s\033[m\n",
head[0], word, ptr, ptr + 5);
} else if (pos < line)
prints("\033[47;34m %s \033[44;37m%-72.72s"
"\033[m\n", head[pos], word);
viewed += numbytes;
numbytes = more_readln(fd, &mp, (unsigned char *)buf);
/* 第一行太長了 */
if (!pos && viewed > 79) {
/* 第二行不是 [標....] */
if (memcmp(buf, head[1], 2)) {
/* 讀下一行進來處理 */
viewed += numbytes;
numbytes = more_readln(fd, &mp, (unsigned char *)buf);
}
}
pos++;
}
if (pos) {
header = 1;
prints("\033[36m%s\033[m\n", msg_seperator);
++line;
++pos;
}
lino = pos;
word = NULL;
}
/* ※處理引用者 & 引言 */
if ((buf[0] == ':' || buf[0] == '>') && (buf[1] == ' '))
word = "\033[36m";
else if (!strncmp(buf, "※", 2) || !strncmp(buf, "==>", 3))
word = "\033[32m";
if (word)
outs(word);
{
char msg[500], *pos;
if (*search_str && (pos = (*fptr)(buf, search_str))) {
char SearchStr[81];
char buf1[100], *pos1;
int search_str_len = strlen(search_str);
strlcpy(SearchStr, pos, search_str_len + 1);
searching = 0;
snprintf(msg, sizeof(msg),
"%.*s\033[7m%s\033[m", (int)(pos - buf), buf,
SearchStr);
while ((pos = (*fptr)(pos1 = pos + search_str_len,
search_str))) {
snprintf(buf1, sizeof(buf1),
"%.*s\033[7m%s\033[m", (int)(pos - pos1),
pos1, SearchStr);
strlcat(msg, buf1, sizeof(msg));
}
strlcat(msg, pos1, sizeof(msg));
outs(Ptt_prints(msg, NO_RELOAD));
} else
outs(Ptt_prints(buf, NO_RELOAD));
}
if (word) {
outs("\033[m");
word = NULL;
}
outc('\n');
#if DEPRECATED__UNUSED
if (beep) {
bell();
beep = 0;
}
#endif
if (line < b_lines) /* 一般資料讀取 */
line++;
if (line == b_lines && searching == -1) {
if (pageno > 0)
more_goto(fd, &mp, viewed = pagebreak[--pageno]);
else
searching = 0;
lino = pos = line = 0;
clear();
continue;
}
if (scrollup) {
move(line = b_lines, 0);
clrtoeol();
for (pos = 1; pos < b_lines; pos++)
viewed += more_readln(fd, &mp, (unsigned char *)buf);
} else if (pos == b_lines) /* 捲動螢幕 */
scroll();
else
pos++;
if (!scrollup &&
++lino >= (unsigned)b_lines &&
pageno < MAX_PAGES - 1 ) {
pagebreak[++pageno] = viewed;
lino = 1;
}
if (scrollup) {
lino = scrollup;
scrollup = 0;
}
viewed += numbytes; /* 累計讀過資料 */
} else
line = b_lines; /* end of END */
if (promptend &&
((!searching && line == b_lines) || viewed == fsize)) {
/* Kaede 剛好 100% 時不停 */
move(b_lines, 0);
if (viewed == fsize) {
if (searching == 1)
searching = 0;
color = 0;
} else if (pageno == 1 && lino == 1) {
if (searching == -1)
searching = 0;
color = 1;
} else
color = 2;
prints("\033[m\033[%sm 瀏覽 P.%d(%d%%) %s %-30.30s%s",
printcolor[(int)color],
pageno,
(int)((viewed * 100) / fsize),
"\033[31;47m",
"(h)\033[30m求助 \033[31m→↓[PgUp][",
"PgDn][Home][End]\033[30m游標移動 \033[31m←[q]\033[30m結束 \033[m");
while (line == b_lines || (line > 0 && viewed == fsize)) {
switch ((ch = igetch())) {
case ':':
{
char buf[10];
int i = 0;
getdata(b_lines - 1, 0, "Goto Page: ", buf, 5, DOECHO);
sscanf(buf, "%d", &i);
if (0 < i && i < MAX_PAGES && (i == 1 || pagebreak[i - 1]))
pageno = i - 1;
else if (pageno)
pageno--;
lino = line = 0;
break;
}
case '/':
{
char ans[4] = "n";
*search_str = search_char0;
getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:", search_str,
40, DOECHO);
if (*search_str) {
searching = 1;
if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
ans, sizeof(ans), LCECHO) && *ans == 'y')
fptr = &strstr;
else
fptr = &strcasestr;
}
if (*ans == 'q')
searching = 0;
if (pageno)
pageno--;
lino = line = 0;
break;
}
case 'n':
if (*search_str) {
searching = 1;
if (pageno)
pageno--;
lino = line = 0;
}
break;
case 'N':
if (*search_str) {
searching = -1;
if (pageno)
pageno--;
lino = line = 0;
}
break;
case 'r': // Ptt: put all reply/recommend function here
case 'R':
case 'Y':
case 'y':
close(fd);
return 999;
case 'X':
close(fd);
return 998;
case 'A':
close(fd);
return AUTHOR_PREV;
case 'a':
close(fd);
return AUTHOR_NEXT;
case 'F':
case 'f':
close(fd);
return READ_NEXT;
case 'B':
case 'b':
close(fd);
return READ_PREV;
case KEY_LEFT:
case 'q':
close(fd);
return FULLUPDATE;
case ']': /* Kaede 為了主題閱讀方便 */
case '+':
close(fd);
return RELATE_NEXT;
case '[': /* Kaede 為了主題閱讀方便 */
case '-':
close(fd);
return RELATE_PREV;
case '=': /* Kaede 為了主題閱讀方便 */
close(fd);
return RELATE_FIRST;
case Ctrl('F'):
case KEY_PGDN:
line = 1;
break;
case 't':
if (viewed == fsize) {
close(fd);
return RELATE_NEXT;
}
line = 1;
break;
case ' ':
if (viewed == fsize) {
close(fd);
return READ_NEXT;
}
line = 1;
break;
case KEY_RIGHT:
if (viewed == fsize) {
close(fd);
return 0;
}
line = 1;
break;
case '\r':
case '\n':
case KEY_DOWN:
if (viewed == fsize ||
(promptend == 2 && (ch == '\r' || ch == '\n'))) {
close(fd);
return READ_NEXT;
}
line = t_lines - 2;
break;
case '$':
case 'G':
case KEY_END:
line = t_lines;
break;
case '0':
case 'g':
case KEY_HOME:
pageno = line = 0;
break;
case 'h':
case 'H':
case '?':
/* Kaede Buggy ... */
show_help(more_help);
if (pageno)
pageno--;
lino = line = 0;
break;
case 'E':
if (HAS_PERM(PERM_SYSOP) && strcmp(fpath, "etc/ve.hlp")) {
close(fd);
vedit(fpath, NA, NULL);
return 0;
}
break;
case Ctrl('T'):
getdata(b_lines - 2, 0, "把這篇文章收入到暫存檔?[y/N] ",
buf, 4, LCECHO);
if (buf[0] == 'y') {
setuserfile(buf, ask_tmpbuf(b_lines - 1));
Copy(fpath, buf);
}
if (pageno)
pageno--;
lino = line = 0;
break;
case KEY_UP:
line = -1;
break;
case Ctrl('B'):
case KEY_PGUP:
if (pageno > 1) {
if (lino < 2)
pageno -= 2;
else
pageno--;
lino = line = 0;
} else if (pageno && lino > 1)
pageno = line = 0;
break;
case Ctrl('H'):
if (pageno > 1) {
if (lino < 2)
pageno -= 2;
else
pageno--;
lino = line = 0;
} else if (pageno && lino > 1)
pageno = line = 0;
else {
close(fd);
return READ_PREV;
}
}
}
if (line > 0) {
move(b_lines, 0);
clrtoeol();
refresh();
} else if (line < 0) { /* Line scroll up */
if (pageno <= 1) {
if (lino == 1 || !pageno) {
close(fd);
return READ_PREV;
}
if (header && lino <= 5) {
more_goto(fd, &mp, viewed = pagebreak[scrollup = lino =
pageno = 0] = 0);
clear();
}
}
if (pageno && (int)lino > 1 + local) {
line = (lino - 2) - local;
if (pageno > 1 && viewed == fsize)
line += local;
scrollup = lino - 1;
more_goto(fd, &mp, viewed = pagebreak[pageno - 1]);
while (line--)
viewed += more_readln(fd, &mp, (unsigned char *)buf);
} else if (pageno > 1) {
scrollup = b_lines - 1;
line = (b_lines - 2) - local;
more_goto(fd, &mp, viewed = pagebreak[--pageno - 1]);
while (line--)
viewed += more_readln(fd, &mp, (unsigned char *)buf);
}
line = pos = 0;
} else {
pos = 0;
more_goto(fd, &mp, viewed = pagebreak[pageno]);
move(0, 0);
clear();
}
}
}
close(fd);
if (promptend) {
pressanykey();
clear();
} else
outs(reset_color);
return 0;
}