/* $Id$ */ #include "bbs.h" /* * "pmore" is "piaip's more", NOT "PTT's more"!!! * * piaip's new implementation of pager(more) with mmap, * designed for unlimilited length(lines). * * Author: Hung-Te Lin (piaip), June 2005. * * * MAJOR IMPROVEMENTS: * - Clean source code, and more readble to mortal * - Correct navigation * - Excellent search ability (for correctness and user behavior) * - Less memory consumption (mmap is not considered anyway) * - Better support for large terminals * - Unlimited file length and line numbers * * TODO: * - Speed up with supporting Scroll [done] * - Support PTT_PRINTS [done] * - Wrap long lines [done] * - left-right wide navigation * - Big5 truncation * - BBSMovie Support * * WONTDO: * - The message seperator line is different from old more. * I decided to abandon the old style (which is buggy). * > old style: increase one line to show seperator * > pmore style: use blank line for seperator. * - However I've changed my mind. Now this can be simulated * with wrapping. See Features section to enable * PMORE_TRADITIONAL_SEPERATOR if you REALLY eager for this. * HOWEVER IT MAY BE SLOW BECAUSE OPTIMIZED SCROLL IS DISABLED * IN WRAPPING MODE. * * HINTS: * - Remember mmap pointers are NOT null terminated strings. * You have to use strn* APIs and make sure not exceeding mmap buffer. * DO NOT USE strcmp, strstr, strchr, ... * - Scroll handling is painful. If you displayed anything on screen, * remember to MFDISP_DIRTY(); * - To be portable between most BBS systems, pmore is designed to * workaround most BBS bugs inside itself. */ #include #include #include #include #include #include // Platform Related. NoSync is faster but if we don't have it... #ifdef MAP_NOSYNC #define MF_MMAP_OPTION (MAP_NOSYNC) #else #define MF_MMAP_OPTION (MAP_SHARED) #endif // --------------------------------------------------------------- #define PMORE_USE_PTT_PRINTS // PTT or special printing #define PMORE_USE_OPT_SCROLL // optimized scroll #define PMORE_PRELOAD_SIZE (128*1024L) // on busy system set smaller or undef //#define PMORE_TRADITIONAL_SEPERATOR // display seperator with extra space //#define PMORE_TRADITIONAL_STATUSBAR // if you really love that #define PMORE_TRADITIONAL_PROMPTEND // when prompt=NA, show only page 1 // -------------------------------------------------------------- //#define DEBUG int debug = 0; // --------------------------------------------- // --------------------------- // Escapes. I don't like \033 everywhere. #define ESC_NUM (0x1b) #define ESC_STR "\x1b" #define ESC_CHR '\x1b' // Common ANSI commands. #define ANSI_RESET ESC_STR "[m" #define ANSI_COLOR(x) ESC_STR "[" #x "m" #define STR_ANSICODE "[0123456789;," // Poor BBS terminal system Workarounds // - Most BBS implements clrtoeol() as fake command // and usually messed up when output ANSI quoted string. // - A workaround is suggested by kcwu: // https://opensvn.csie.org/traccgi/pttbbs/trac.cgi/changeset/519 #define FORCE_CLRTOEOL() outs(ESC_STR "[K") // --------------------------- // ---------------------------
typedef struct { unsigned char *start, *end, // file buffer *disps, *dispe, // disply start/end *maxdisps; // a very special pointer, // consider as "disps of last page" off_t len; // file total length long lineno, // lineno of disps oldlineno, // last drawn lineno, < 0 means full update // wraplines, // wrapped lines in last display dispedlines, // how many different lines displayed // usually dispedlines = PAGE-wraplines, // but if last line is incomplete(wrapped), // dispedlines = PAGE-wraplines + 1 lastpagelines,// lines of last page to show // this indicates how many lines can // maxdisps(maxlinenoS) display. maxlinenoS; // lineno of maxdisps, "S"! // What does the magic "S" mean? // Just trying to notify you that it's // NOT REAL MAX LINENO NOR FILELENGTH!!! // You may consider "S" of "Start" (disps). } MmappedFile; MmappedFile mf = { 0, 0, 0, 0, 0, 0L, 0, 0, 0, -1L, -1L, -1L }; // current file /* mf_* navigation commands return value meanings */ enum { MFNAV_OK, // navigation ok MFNAV_EXCEED, // request exceeds buffer } MF_NAV_COMMANDS; /* Navigation units (dynamic, so not in enum const) */ #define MFNAV_PAGE (t_lines-2) // when navigation, how many lines in a page to move /* Display system */ enum { /* newline method (because of poor BBS implementation) */ MFDISP_NEWLINE_CLEAR = 0, // \n and cleartoeol MFDISP_NEWLINE_SKIP, MFDISP_NEWLINE_MOVE, // use move to simulate newline. MFDISP_WRAP_TRUNCATE = 0, MFDISP_WRAP_WRAP, } MF_DISP_CONST; #define MFDISP_PAGE (t_lines-1) // the real number of lines to be shown. #define MFDISP_DIRTY() { mf.oldlineno = -1; } /* Indicators */ #define MFDISP_TRUNC_INDICATOR ANSI_COLOR(0;1;37) ">" ANSI_RESET #define MFDISP_WRAP_INDICATOR ANSI_COLOR(0;1;37) "\\" ANSI_RESET // ---------------------------
// --------------------------- /* browsing preference */ typedef struct { /* mode flags */ unsigned short int rawmode, // show file as-is. wrapmode, // wrap? indicator; // show wrap indicators? } MF_BrowsingPrefrence; MF_BrowsingPrefrence bpref = { 0, MFDISP_WRAP_WRAP, 1, }; /* pretty format header */ #define FH_HEADERS (4) // how many headers do we know? #define FH_HEADER_LEN (4) // strlen of each heads static const char *_fh_disp_heads[FH_HEADERS] = {"作者", "標題", "時間", "轉信"}; typedef struct { int lines; // header lines unsigned char *headers[FH_HEADERS]; unsigned char *floats[2]; // right floating, name and val } MF_PrettyFormattedHeader; MF_PrettyFormattedHeader fh = { 0, {0,0,0,0}, {0, 0}}; /* search records */ typedef struct { int len; int (*cmpfunc) (const char *, const char *, size_t); char search_str[81]; // maybe we can change to dynamic allocation } MF_SearchRecord; MF_SearchRecord sr = { 0, strncmp, "" }; enum { MFSEARCH_FORWARD, MFSEARCH_BACKWARD, } MFSEARCH_DIRECTION; // Reset structures #define RESETMF() { memset(&mf, 0, sizeof(mf)); \ mf.lastpagelines = mf.maxlinenoS = mf.oldlineno = -1; } #define RESETFH() { memset(&fh, 0, sizeof(fh)); \ fh.lines = -1; } // --------------------------- // ---------------------------------------------
// used by mf_attach void mf_parseHeaders(); void mf_freeHeaders(); void mf_determinemaxdisps(int); /* * mmap basic operations */ int mf_attach(unsigned char *fn) { struct stat st; int fd = open(fn, O_RDONLY, 0600); if(fd < 0) return 0; if (fstat(fd, &st) || ((mf.len = st.st_size) <= 0) || S_ISDIR(st.st_mode)) { mf.len = 0; close(fd); return 0; } /* mf.len = lseek(fd, 0L, SEEK_END); lseek(fd, 0, SEEK_SET); */ mf.start = mmap(NULL, mf.len, PROT_READ, MF_MMAP_OPTION, fd, 0); close(fd); if(mf.start == MAP_FAILED) { RESETMF(); return 0; } mf.end = mf.start + mf.len; mf.disps = mf.dispe = mf.start; mf.lineno = 0; mf_determinemaxdisps(MFNAV_PAGE); mf.disps = mf.dispe = mf.start; mf.lineno = 0; /* reset and parse article header */ mf_parseHeaders(); return 1; } void mf_detach() { if(mf.start) { munmap(mf.start, mf.len); RESETMF(); } mf_freeHeaders(); } /* * lineno calculation, and moving */ void mf_sync_lineno() { unsigned char *p; if(mf.disps == mf.maxdisps && mf.maxlinenoS >= 0) { mf.lineno = mf.maxlinenoS; } else { mf.lineno = 0; for (p = mf.start; p < mf.disps; p++) if(*p == '\n') mf.lineno ++; if(mf.disps == mf.maxdisps && mf.maxlinenoS < 0) mf.maxlinenoS = mf.lineno; } } int mf_backward(int); // used by mf_buildmaxdisps void mf_determinemaxdisps(int backlines) { unsigned char *pbak = mf.disps, *mbak = mf.maxdisps; long lbak = mf.lineno; if( mf.lastpagelines >= 0 && mf.lastpagelines <= backlines) return; mf.disps = mf.end - 1; mf_backward(backlines); if(mf.disps != mbak) { mf.maxdisps = mf.disps; mf.lastpagelines = backlines; mf.maxlinenoS = -1; #ifdef PMORE_PRELOAD_SIZE if(mf.len <= PMORE_PRELOAD_SIZE) mf_sync_lineno(); // maxlinenoS will be automatically updated #endif } mf.disps = pbak; mf.lineno = lbak; } /* * mf_backwards is also used for maxno determination, * so we cannot change anything in mf except these: * mf.disps * mf.lineno */ int mf_backward(int lines) { int flFirstLine = 1; // first, because we have to trace back to line beginning, // add one line. lines ++; // now try to rollback for lines if(lines == 1) { /* special case! just rollback to start */ while ( mf.disps > mf.start && *(mf.disps-1) != '\n') mf.disps --; mf.disps --; lines --; } else while(mf.disps > mf.start && lines > 0) { while (mf.disps > mf.start && *--mf.disps != '\n'); if(flFirstLine) { flFirstLine = 0; lines--; continue; } if(mf.disps >= mf.start) mf.lineno--, lines--; } if(mf.disps == mf.start) mf.lineno = 0; else mf.disps ++; if(lines > 0) return MFNAV_OK; else return MFNAV_EXCEED; } int mf_forward(int lines) { while(mf.disps <= mf.maxdisps && lines > 0) { while (mf.disps <= mf.maxdisps && *mf.disps++ != '\n'); if(mf.disps <= mf.maxdisps) mf.lineno++, lines--; } if(mf.disps > mf.maxdisps) mf.disps = mf.maxdisps; /* please make sure you have lineno synced. */ if(mf.disps == mf.maxdisps && mf.maxlinenoS < 0) mf.maxlinenoS = mf.lineno; if(lines > 0) return MFNAV_OK; else return MFNAV_EXCEED; } int mf_goTop() { mf.disps = mf.start; mf.lineno = 0; return MFNAV_OK; } int mf_goBottom() { mf.disps = mf.maxdisps; mf_sync_lineno(); return MFNAV_OK; } int mf_goto(int lineno) { mf.disps = mf.start; mf.lineno = 0; return mf_forward(lineno); } int mf_viewedNone() { return (mf.disps <= mf.start); } int mf_viewedAll() { return (mf.dispe >= mf.end); } /* * search! */ int mf_search(int direction) { unsigned char *s = sr.search_str; int l = sr.len; int flFound = 0; if(!*s) return 0; if(direction == MFSEARCH_FORWARD) { mf_forward(1); while(mf.disps < mf.end - l) { if(sr.cmpfunc(mf.disps, s, l) == 0) { flFound = 1; break; } else mf.disps ++; } mf_backward(0); if(mf.disps > mf.maxdisps) mf.disps = mf.maxdisps; mf_sync_lineno(); } else if(direction == MFSEARCH_BACKWARD) { mf_backward(1); while (!flFound && mf.disps > mf.start) { while(!flFound && mf.disps < mf.end-l && *mf.disps != '\n') { if(sr.cmpfunc(mf.disps, s, l) == 0) { flFound = 1; } else mf.disps ++; } if(!flFound) mf_backward(1); } mf_backward(0); if(mf.disps < mf.start) mf.disps = mf.start; mf_sync_lineno(); } if(flFound) MFDISP_DIRTY(); return flFound; } /* String Processing * * maybe you already have your string processors (or not). * whether yes or no, here we provides some. */ void pmore_str_strip_ansi(unsigned char *p) // warning: p is NULL terminated { unsigned char *pb = p; while (*p != 0) { if (*p == ESC_CHR) { // ansi code sequence, ignore them. pb = p++; while (*p && strchr(STR_ANSICODE, *p++)); memmove(pb, p, strlen(p)+1); p = pb; } else if (*p < ' ') { // control codes, ignore them. memmove(p, p+1, strlen(p+1)+1); } else p++; } } /* this chomp is a little different: * it kills starting and trailing spaces. */ void pmore_str_chomp(unsigned char *p) { unsigned char *pb = p + strlen(p)-1; while (pb >= p) if(isascii(*pb) && isspace(*pb)) *pb-- = 0; else break; pb = p; while (*pb && isascii(*pb) && isspace(*pb)) pb++; if(pb != p) memmove(p, pb, strlen(pb)+1); } int pmore_str_safe_big5len(unsigned char *p) { return 0; } /* * Format Related */ void mf_freeHeaders() { if(fh.lines > 0) { int i; for (i = 0; i < FH_HEADERS; i++) if(fh.headers[i]) free(fh.headers[i]); for (i = 0; i < sizeof(fh.floats) / sizeof(unsigned char*); i++) free(fh.floats[i]); RESETFH(); } } void mf_parseHeaders() { /* file format: * AUTHOR: author BOARD: blah <- headers[0], flaots[0], floats[1] * XXX: xxx <- headers[1] * XXX: xxx <- headers[n] * [blank, fill with seperator] <- lines * * #define STR_AUTHOR1 "作者:" * #define STR_AUTHOR2 "發信人:" */ unsigned char *pmf = mf.start; int i = 0; RESETFH(); if(mf.len < LEN_AUTHOR2) return; if (strncmp(mf.start, STR_AUTHOR1, LEN_AUTHOR1) == 0) { fh.lines = 3; // local } else if (strncmp(mf.start, STR_AUTHOR2, LEN_AUTHOR2) == 0) { fh.lines = 4; } else return; for (i = 0; i < fh.lines; i++) { unsigned char *p = pmf, *pb = pmf; int l; /* first, go to line-end */ while(pmf < mf.end && *pmf != '\n') pmf++; if(pmf >= mf.end) break; p = pmf; pmf ++; // move to next line. // p is pointing at a new line. (\n) l = (int)(p - pb); p = (unsigned char*) malloc (l+1); fh.headers[i] = p; memcpy(p, pb, l); p[l] = 0; // now, postprocess p. pmore_str_strip_ansi(p); // strip to quotes[+1 space] if((pb = strchr(p, ':')) != NULL) { if(*(pb+1) == ' ') pb++; memmove(p, pb, strlen(pb)+1); } // kill staring and trailing spaces pmore_str_chomp(p); // special case, floats are in line[0]. if(i == 0 && (pb = strrchr(p, ':')) != NULL && *(pb+1)) { unsigned char *np = strdup(pb+1); fh.floats[1] = np; pmore_str_chomp(np); // remove quote and traverse back *pb-- = 0; while (pb > p && *pb != ',' && !(isascii(*pb) && isspace(*pb))) pb--; if (pb > p) { fh.floats[0] = strdup(pb+1); pmore_str_chomp(fh.floats[0]); *pb = 0; pmore_str_chomp(fh.headers[0]); } else { fh.floats[0] = strdup(""); } } } } /* * mf_disp utility macros */ inline static void MFDISP_SKIPCURLINE() { while (mf.dispe < mf.end && *mf.dispe != '\n') mf.dispe++; } inline static int MFDISP_DBCS_HEADERWIDTH(int originalw) { return originalw - (originalw %2); // return (originalw >> 1) << 1; } /* * display mf content from disps for MFDISP_PAGE */ void mf_disp() { int lines = 0, col = 0, currline = 0, wrapping = 0; int startline = 0, endline = MFDISP_PAGE-1; /* why t_columns-1 here? * because BBS systems usually have a poor terminal system * and many stupid clients behave differently. * So we try to avoid using the last column, leave it for * BBS to place '\n' and CLRTOEOL. */ const int headerw = MFDISP_DBCS_HEADERWIDTH(t_columns-1); const int dispw = headerw - (t_columns - headerw < 2); const int maxcol = dispw - 1; if(mf.wraplines) MFDISP_DIRTY(); // we can't scroll with wrapped lines. mf.wraplines = 0; mf.dispedlines = 0; #ifdef PMORE_USE_OPT_SCROLL /* process scrolling */ if (mf.oldlineno >= 0 && mf.oldlineno != mf.lineno) { int scrll = mf.lineno - mf.oldlineno, i; int reverse = (scrll > 0 ? 0 : 1); if(reverse) scrll = -scrll; else { /* because bottom status line is also scrolled, * we have to erase it here. */ move(b_lines, 0); clrtoeol(); } if(scrll > MFDISP_PAGE) scrll = MFDISP_PAGE; i = scrll; while(i-- > 0) if (reverse) rscroll(); // v else scroll(); // ^ if(reverse) { startline = 0; // v endline = scrll-1; // clear the line which will be scrolled // to bottom (status line position). move(b_lines, 0); clrtoeol(); } else { startline = MFDISP_PAGE - scrll; // ^ endline = MFDISP_PAGE - 1; } move(startline, 0); // return; // uncomment if you want to observe scrolling } else #endif clear(), move(0, 0); mf.dispe = mf.disps; while (lines < MFDISP_PAGE) { int inAnsi = 0; int newline = MFDISP_NEWLINE_CLEAR; currline = mf.lineno + lines; col = 0; if(!wrapping && mf.dispe < mf.end) mf.dispedlines++; /* Is currentline visible? */ if (lines < startline || lines > endline) { while(mf.dispe < mf.end && *mf.dispe != '\n') mf.dispe++; newline = MFDISP_NEWLINE_SKIP; } /* Now, consider what kind of line * (header, seperator, or normal text) * is current line. */ else if (!bpref.rawmode && currline == fh.lines) { /* case 1, header seperator line */ outs(ANSI_COLOR(36)); for(col = 0; col < headerw; col+=2) { // prints("%02d", col); outs("─"); } outs(ANSI_RESET); #ifdef PMORE_TRADITIONAL_SEPERATOR /* Traditional 'more' adds seperator as a newline. * This is buggy, however we can support this * by using wrapping features. * Anyway I(piaip) don't like this. And using wrap * leads to slow display (we cannt speed it up with * optimized scrolling. */ if(bpref.wrapmode == MFDISP_WRAP_WRAP) { /* we have to do all wrapping stuff * in normal text section. * make sure this is updated. */ wrapping = 1; mf.wraplines ++; endline = MFDISP_PAGE-1; if(mf.dispe > mf.start && mf.dispe < mf.end && *mf.dispe == '\n') mf.dispe --; } else #endif MFDISP_SKIPCURLINE(); } else if (!bpref.rawmode && currline < fh.lines) { /* case 2, we're printing headers */ const char *val = fh.headers[currline]; const char *name = _fh_disp_heads[currline]; int w = headerw - FH_HEADER_LEN - 3; outs(ANSI_COLOR(47;34) " "); outs(name); outs(" " ANSI_COLOR(44;37) " "); /* right floating stuff? */ if (currline == 0 && fh.floats[0]) { w -= strlen(fh.floats[0]) + strlen(fh.floats[1]) + 4; } prints("%-*.*s", w, w, (val ? val : "")); if (currline == 0 && fh.floats[0]) { outs(ANSI_COLOR(47;34) " "); outs(fh.floats[0]); outs(" " ANSI_COLOR(44;37) " "); outs(fh.floats[1]); outs(" "); } outs(ANSI_RESET); MFDISP_SKIPCURLINE(); } else if(mf.dispe < mf.end) { /* case 3, normal text */ long dist = mf.end - mf.dispe; long flResetColor = 0; int srlen = -1; int breaknow = 0; // first check quote if(!bpref.rawmode) { if(dist > 1 && (*mf.dispe == ':' || *mf.dispe == '>') && *(mf.dispe+1) == ' ') { outs(ANSI_COLOR(36)); flResetColor = 1; } else if (dist > 2 && (!strncmp(mf.dispe, "※", 2) || !strncmp(mf.dispe, "==>", 3))) { outs(ANSI_COLOR(32)); flResetColor = 1; } } while(!breaknow && mf.dispe < mf.end && *mf.dispe != '\n') { if(inAnsi) { if (!strchr(STR_ANSICODE, *mf.dispe)) inAnsi = 0; if(col <= maxcol) outc(*mf.dispe); } else { if(*mf.dispe == ESC_CHR) inAnsi = 1; else if(srlen < 0 && sr.search_str[0] && // support search //tolower(sr.search_str[0]) == tolower(*mf.dispe) && mf.end - mf.dispe > sr.len && sr.cmpfunc(mf.dispe, sr.search_str, sr.len) == 0) { outs(ANSI_COLOR(7)); srlen = sr.len-1; flResetColor = 1; } #ifdef PMORE_USE_PTT_PRINTS /* special case to resolve dirty Ptt_Prints */ if(inAnsi && mf.end - mf.dispe > 2 && *(mf.dispe+1) == '*') { int i; char buf[64]; // make sure ptt_prints will not exceed memset(buf, 0, sizeof(buf)); strncpy(buf, mf.dispe, 3); // ^[[*s mf.dispe += 2; if(bpref.rawmode) buf[0] = '*'; else Ptt_prints(buf, NO_RELOAD); // result in buf i = strlen(buf); if (col + i > maxcol) i = maxcol - col; if(i > 0) { buf[i] = 0; col += i; outs(buf); } inAnsi = 0; } else #endif { /* if col > maxcol, * because we have the space for * "indicators" (one byte), * so we can tolerate one more byte. */ if(col <= maxcol || (mf.dispe < mf.end-1 && *(mf.dispe+1) == '\n')) outc(*mf.dispe); else switch (bpref.wrapmode) { case MFDISP_WRAP_WRAP: breaknow = 1; wrapping = 1; mf.wraplines ++; break; case MFDISP_WRAP_TRUNCATE: breaknow = 1; MFDISP_SKIPCURLINE(); wrapping = 0; break; } if(!inAnsi) { col++; if (srlen == 0) outs(ANSI_RESET); if(srlen >= 0) srlen --; } } } if(!breaknow) mf.dispe ++; } if(flResetColor) outs(ANSI_RESET); /* "wrapping" should be only in normal text section. * We don't support wrap within scrolling, * so if we have to wrap, invalidate all lines. */ if(breaknow) { if(wrapping) endline = MFDISP_PAGE-1; if(bpref.indicator) { if(wrapping) outs(MFDISP_WRAP_INDICATOR); else outs(MFDISP_TRUNC_INDICATOR); } } else wrapping = 0; } if(mf.dispe < mf.end && *mf.dispe == '\n') mf.dispe ++; // else, we're in wrap mode. switch(newline) { case MFDISP_NEWLINE_SKIP: break; case MFDISP_NEWLINE_CLEAR: FORCE_CLRTOEOL(); outc('\n'); break; case MFDISP_NEWLINE_MOVE: move(lines+1, 0); break; } lines ++; } /* * we've displayed the file. * but if we got wrapped lines in last page, * mf.maxdisps may be required to be larger. */ if(mf.disps == mf.maxdisps && mf.dispe < mf.end) { mf_determinemaxdisps(0); } mf.oldlineno = mf.lineno; } /* --------------------- MAIN PROCEDURE ------------------------- */ static const char * const pmore_help[] = { "\0閱\讀文章功\能鍵使用說明", "\01游標移動功\能鍵", "(j)(↑) 上捲一行", "(k)(↓)(Enter) 下捲一行", "(^B)(PgUp)(BackSpace) 上捲一頁", "(^F)(PgDn)(Space)(→) 下捲一頁", "(0)(g)(Home) 檔案開頭", "($)(G)(End) 檔案結尾", "(;/:) 跳至某行/某頁", "數字鍵 1-9 跳至輸入的行號", "\01其他功\能鍵", "(/) 搜尋字串", "(n/N) 重複正/反向搜尋", "(Ctrl-T) 存到暫存檔", "(f/b) 跳至下/上篇", "(a/A) 跳至同一作者下/上篇", "(t/[-/]+) 主題式閱\讀:循序/前/後篇", "(\\/w/W) 切換顯示原始內容/自動折行/折行符號", // this IS already aligned! "(q)(←) 結束", "(h)(H)(?) 本說明畫面", #ifdef DEBUG "(d) 切換除錯(debug)模式", #endif "\01本系統使用 piaip 的新式瀏覽程式: pmore, piaip's more", NULL }; /* * pmore utility macros */ inline static void PMORE_UINAV_FORWARDPAGE() { /* Usually, a forward is just mf_forward(MFNAV_PAGE); * but because of wrapped lines... * This function is used when user tries to nagivate * with page request. * If you want to a real page forward, don't use this. * That's why we have this special function. */ int i = mf.dispedlines - 1; if(i < 1) i = 1; mf_forward(i); } /* * piaip's more, a replacement for old more */ int pmore(char *fpath, int promptend) { int flExit = 0, retval = 0; int ch = 0; STATINC(STAT_MORE); if(!mf_attach(fpath)) return -1; clear(); while(!flExit) { mf_disp(); #ifdef PMORE_TRADITIONAL_PROMPTEND if(promptend == NA) // && mf_viewedAll()) break; #else if(promptend == NA && mf_viewedAll()) break; #endif move(b_lines, 0); // clrtoeol(); // this shall be done in mf_disp to speed up. /* PRINT BOTTOM STATUS BAR */ #ifdef DEBUG if(debug) { /* in debug mode don't print ANSI codes * because themselves are buggy. */ prints("L#%ld(w%ld) pmt=%d Dsp:%08X/%08X/%08X, " "F:%08X/%08X(%d) tScr(%dx%d)", mf.lineno, mf.wraplines, promptend, (unsigned int)mf.disps, (unsigned int)mf.maxdisps, (unsigned int)mf.dispe, (unsigned int)mf.start, (unsigned int)mf.end, (int)mf.len, t_columns, t_lines ); } else #endif { char *printcolor; #ifndef PMORE_TRADITIONAL_STATUSBAR char buf[256]; // orz int prefixlen = 0; int barlen = 0; int postfix1len = 0, postfix2len = 0; #endif if(mf_viewedAll()) printcolor = ANSI_COLOR(37;44); else if (mf_viewedNone()) printcolor = ANSI_COLOR(33;45); else printcolor = ANSI_COLOR(34;46); outs(ANSI_RESET); outs(printcolor); #ifdef PMORE_TRADITIONAL_STATUSBAR prints(" 瀏覽 P.%d(%d%%) %s %-30.30s%s", (int)(mf.lineno / MFNAV_PAGE)+1, (int)((unsigned long)(mf.dispe-mf.start) * 100 / mf.len), ANSI_COLOR(31;47), "(h)" ANSI_COLOR(30) "求助 " ANSI_COLOR(31) "→↓[PgUp][", "PgDn][Home][End]" ANSI_COLOR(30) "游標移動 " ANSI_COLOR(31) "←[q]" ANSI_COLOR(30) "結束 "); #else if(mf.maxlinenoS >= 0) sprintf(buf, " 瀏覽 第 %1d/%1d 頁 ", (int)(mf.lineno / MFNAV_PAGE)+1, (int)(mf.maxlinenoS / MFNAV_PAGE)+1 ); else sprintf(buf, " 瀏覽 第 %1d 頁 ", (int)(mf.lineno / MFNAV_PAGE)+1 ); outs(buf); prefixlen += strlen(buf); outs(ANSI_COLOR(1;30;47)); sprintf(buf, " 閱\讀進度%3d%%, 目前顯示: 第 %02d~%02d 行", (int)((unsigned long)(mf.dispe-mf.start) * 100 / mf.len), (int)(mf.lineno + 1), (int)(mf.lineno + mf.dispedlines) ); outs(buf); prefixlen += strlen(buf); postfix1len = 12; // check msg below postfix2len = 10; if (prefixlen + postfix1len + postfix2len + 1 > t_columns) { postfix1len = 0; if (prefixlen + postfix1len + postfix2len + 1 > t_columns) postfix2len = 0; } barlen = t_columns - 1 - postfix1len - postfix2len - prefixlen; while(barlen-- > 0) outc(' '); if(postfix1len > 0) outs( ANSI_COLOR(0;31;47) "(h)" ANSI_COLOR(30) "按鍵說明 " ); if(postfix2len > 0) outs( ANSI_COLOR(0;31;47) "←[q]" ANSI_COLOR(30) "離開 " ); #endif outs(ANSI_RESET); FORCE_CLRTOEOL(); } /* igetch() will do refresh(); */ ch = igetch(); switch (ch) { /* ------------------ EXITING KEYS ------------------ */ case 'r': case 'R': case 'Y': case 'y': flExit = 1, retval = 999; break; case 'X': flExit = 1, retval = 998; break; case 'A': flExit = 1, retval = AUTHOR_PREV; break; case 'a': flExit = 1, retval = AUTHOR_NEXT; break; case 'F': case 'f': flExit = 1, retval = READ_NEXT; break; case 'B': case 'b': flExit = 1, retval = READ_PREV; break; case KEY_LEFT: case 'q': flExit = 1, retval = FULLUPDATE; break; /* from Kaede, thread reading */ case ']': case '+': flExit = 1, retval = RELATE_NEXT; break; case '[': case '-': flExit = 1, retval = RELATE_PREV; break; case '=': flExit = 1, retval = RELATE_FIRST; break; /* ------------------ NAVIGATION KEYS ------------------ */ /* Simple Navigation */ case 'j': case 'J': mf_backward(1); break; case 'k': case 'K': mf_forward(1); break; case Ctrl('F'): case KEY_PGDN: PMORE_UINAV_FORWARDPAGE(); break; case Ctrl('B'): case KEY_PGUP: mf_backward(MFNAV_PAGE); break; case '0': case 'g': case KEY_HOME: mf_goTop(); break; case '$': case 'G': case KEY_END: mf_goBottom(); break; /* Compound Navigation */ case '\r': case '\n': case KEY_DOWN: if (mf_viewedAll() || (promptend == 2 && (ch == '\r' || ch == '\n'))) flExit = 1, retval = READ_NEXT; else mf_forward(1); break; case ' ': if (mf_viewedAll()) flExit = 1, retval = READ_NEXT; else PMORE_UINAV_FORWARDPAGE(); break; case KEY_RIGHT: if(mf_viewedAll()) promptend = 0, flExit = 1, retval = 0; else PMORE_UINAV_FORWARDPAGE(); break; case KEY_UP: if(mf_viewedNone()) flExit = 1, retval = READ_PREV; else mf_backward(1); break; case Ctrl('H'): if(mf_viewedNone()) flExit = 1, retval = READ_PREV; else mf_backward(MFNAV_PAGE); break; case 't': if (mf_viewedAll()) flExit = 1, retval = RELATE_NEXT; else PMORE_UINAV_FORWARDPAGE(); break; /* ------------------ SEARCH KEYS ------------------ */ case '/': { char ans[4] = "n"; sr.search_str[0] = 0; getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:", sr.search_str, 40, DOECHO); if (sr.search_str[0]) { if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ", ans, sizeof(ans), LCECHO) && *ans == 'y') sr.cmpfunc = strncmp; else sr.cmpfunc = strncasecmp; if (*ans == 'q') sr.search_str[0] = 0; } sr.len = strlen(sr.search_str); mf_search(MFSEARCH_FORWARD); MFDISP_DIRTY(); } break; case 'n': mf_search(MFSEARCH_FORWARD); break; case 'N': mf_search(MFSEARCH_BACKWARD); break; /* ------------------ SPECIAL KEYS ------------------ */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ';': case ':': { char buf[10] = ""; int i = 0; int pageMode = (ch == ':'); if (ch >= '1' && ch <= '9') buf[0] = ch, buf[1] = 0; getdata_buf(b_lines-1, 0, (pageMode ? "跳至此頁: " : "跳至此行: "), buf, 7, DOECHO); if(buf[0]) { i = atoi(buf); if(i-- > 0) mf_goto(i * (pageMode ? MFNAV_PAGE : 1)); } MFDISP_DIRTY(); } break; case Ctrl('T'): { char buf[10]; getdata(b_lines - 1, 0, "把這篇文章收入到暫存檔?[y/N] ", buf, 4, LCECHO); if (buf[0] == 'y') { setuserfile(buf, ask_tmpbuf(b_lines - 1)); Copy(fpath, buf); } MFDISP_DIRTY(); } break; case 'h': case 'H': case '?': // help show_help(pmore_help); MFDISP_DIRTY(); break; case 'E': // admin edit any files other than ve help file if (HAS_PERM(PERM_SYSOP) && strcmp(fpath, "etc/ve.hlp")) { mf_detach(); vedit(fpath, NA, NULL); return 0; } break; case 'w': switch(bpref.wrapmode) { case MFDISP_WRAP_WRAP: bpref.wrapmode = MFDISP_WRAP_TRUNCATE; break; case MFDISP_WRAP_TRUNCATE: bpref.wrapmode = MFDISP_WRAP_WRAP; break; } MFDISP_DIRTY(); break; case 'W': bpref.indicator = !bpref.indicator; MFDISP_DIRTY(); break; case '\\': bpref.rawmode = !bpref.rawmode; MFDISP_DIRTY(); break; #ifdef DEBUG case 'd': debug = !debug; MFDISP_DIRTY(); break; #endif } } mf_detach(); if (retval == 0 && promptend) { pressanykey(); clear(); } else outs(reset_color); return retval; } /* vim:sw=4 */