summaryrefslogblamecommitdiffstats
path: root/mbbsd/pmore.c
blob: 59075b1ade794b73e5cad5a4a62a02ee83da990e (plain) (tree)
1
2
3
4
5
6
7
8
9
          


                

                                                 


                                                       
                                          
                          




                                                                  
                                                             





                                            

                                
                     
                      





                                                            
                                                             

                                                   

                                                                 




                                                                         
                                                                      
                                 

                                                                   








                      
                                                                



                                   

      
                                                                             


                                                                              
 
                                                                           

                                                                             
               
              
 






                                                                        
 
                        
                                
                                        
                                       





                                                                   
                                           


                                                


                  

                                           

                                                             

                                            
                                                                   








                                                                    
                                                    



                                                                    

              
                   

                           
                       
 
                                                    




                                                 
                                                      
                                                                                    













                                                                        
                                             



                                                                  
                                                 
 
                                                


                         

                      
                                





                                                


                            
                                     
 
                          




                                                          


                               


                                                               
 
                                                      

                    



                                                        
                                                                            
                  
 
                                        
 




                      

                                                 
                                                           


                                                 



                                                                         
                    

                       
                               
 


                         

                            













                                                                              



                                     

                                             
                                   










                                   
 
                                     
 

                                   



                                        


              

           



                                 
 
     
                     




                                 

                

                     












                                                        

 


































                                                                         

                      







































                                                              

                     











                                                               



                                                    





                            

          





                        

             
 
                           
                     
 


                    

                   





                              

               



                                  

              
 
                                



          

                        













































                                                                       

                       


                   





































                                                                        
                                        



                      
                                               











                                        


                 


















                                                                        
 
                   

                                                                     

                                               


                                        
       








                                                         
     

































                                                              
         

                                         
         



                                           
                                               
                                                               
         





                                             
                                                                           









                                               



         




                         




                                                  






                                      
  
                                                
   

         
 
                                                       
                                               





                                                              
       







                                                              
 
                           







                                                       











                                                           





                                    
 



                                    



                                                    


            

                                                 

                           
                                                                     

        
      
                            

                        
                                

                       
                                           


                                     
 
                                          
                             


                                                 
         

                                                         
                                          




                                              
                                                        

                                               
                                 
                                               
             
                                       

                           
                             
 






                                                               
                                                                        













                                                   
                                 
          
                                                       

                                                

                                                        
                                                
 


                                             
 

                                              
             
                                                                     

             

                                      
 
                                              
             




                                                 

             
                             
                                 
          





                                          
                             

                                
                              
             



                                                                  
                                         




                                                        
                                         

                                     

             
                                                                      




                                                         
                                        

                                        
                                            





                                                                                
                                                 



                                             
                           











                                                                               



                                                                        

                                        

                                             








                                       
                     
                                          




                                                            












                                                                          
                                            













                                                      



                                           
                                                 


                                          

                     

                                

                            
                                 









                                                                
                                                         




                                                     

                                     
                 


                             

         
                                                   
                        
                                    
 
                       
         


                                      
                                 
                           

                                     
                                 
                      
         

                 








                                                    
                             






                                                                    
                                          
                                     
                                     
                                          

                                           
                     
                                                                       
                                            
                                       
                                        
                                                
                                                     



                                                                                              
                                 
                                       


                                                
                                                              

        

















                                                          
 


                                           

                                 







                                


                  

                  



                                                 

                                             
      
                         
                                                                    
 
                                     
            
                 















                                                                 
            
      

                             
 
                                      

                              
                                                 


                                                                         

                              
                                               
                                     
                                               

                                               
 

                             
 



                                                           
                                                        








                                                     
 
                    
 











                                                               
 
                                          
 










                                                                     
 
                                                                          



















                                                                               
             

                             
         
 
                                         


                                                                    

                               










                                                     
                               

                                                   
                               


















                                                      

                                                                       
                               

                               
                               


                              

                           
                                          































                                                                       
                                              




                                                          
                                              














                                                   



                                                     
                                              
                      
                                                                    
                     

















                                                                               
                                   








                                                                    


                                                             
                 
                                      
                               
                                                

                                                
 

                                                                     
                                            





                                                                     





                                 
                                                                             




                                                                  
                                   


                      
                               


                                      
                               


                      
                                                               





                                                                          











                                                              



                                                   





                                                         
                      
                                               

                               


                               
                               

                      














                                   
/* $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.
 * <piaip@csie.ntu.edu.tw>
 *
 * 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. So it's in preference now.
 *    Make it default 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 <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <string.h>

// 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

// --------------------------------------------------------------- <FEATURES>
#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_PROMPTEND // when prompt=NA, show only page 1
// -------------------------------------------------------------- </FEATURES>

//#define DEBUG
int debug = 0;

// --------------------------------------------- <Defines and constants>

// --------------------------- <Display>
// 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")
// --------------------------- </Display>

// --------------------------- <Main Navigation>
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
// --------------------------- </Main Navigation>

// --------------------------- <Aux. Structures>
/* browsing preference */
typedef struct
{
    /* mode flags */
    unsigned short int
    wrapmode,   // wrap?
        indicator,  // show wrap indicators

    oldwrapmode,    // traditional wrap
    oldseperator,   // traditional seperator
        oldstatusbar,   // traditional statusbar
    rawmode;    // show file as-is.
} MF_BrowsingPrefrence;

MF_BrowsingPrefrence bpref =
{ MFDISP_WRAP_WRAP, 1, 0, 0, 0, 0, };

/* 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; }

// --------------------------- </Aux. Structures>

// --------------------------------------------- </Defines and constants>

// 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);

        /* 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.oldseperator && 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
        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
            {
            int canOutput = 0;
            /* if col > maxcol,
             * because we have the space for
             * "indicators" (one byte),
             * so we can tolerate one more byte.
             */
            if(col <= maxcol)   // normal case
                canOutput = 1;
            else if (mf.dispe < mf.end-1 && // indicator space
                *(mf.dispe+1) == '\n')
                canOutput = 1;
            else if (bpref.oldwrapmode && // oldwrapmode
                col < t_columns)
            {
                canOutput = 1;
                newline = MFDISP_NEWLINE_MOVE;
            }

            if(canOutput)
                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.oldwrapmode && bpref.indicator)
        {
            if(wrapping)
            outs(MFDISP_WRAP_INDICATOR);
            else
            outs(MFDISP_TRUNC_INDICATOR);
        } else {
            outs(ANSI_RESET);
        }
        }
        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其他功\能鍵",
    "(/" ANSI_COLOR(1;30) "/" ANSI_RESET "s)                 搜尋字串",
    "(n/N)                 重複正/反向搜尋",
    "(Ctrl-T)              存到暫存檔",
    "(f/b)                 跳至下/上篇",
    "(a/A)                 跳至同一作者下/上篇",
    "(t/[-/]+)             主題式閱\讀:循序/前/後篇",
//    "(\\/w/W)               切換顯示原始內容/自動折行/折行符號", // this IS already aligned!
    "(\\)                   切換顯示原始內容", // this IS already aligned!
    "(w/W)                 切換自動折行/顯示折行符號",
    "(o)                   傳統顯示方式(分隔線後空白行與折行等)",
    "(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;

        char buf[256];  // orz
        int prefixlen = 0;
        int barlen = 0;
        int postfix1len = 0, postfix2len = 0;

        int progress  = 
        (int)((unsigned long)(mf.dispe-mf.start) * 100 / mf.len);

        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);

        if(bpref.oldstatusbar)
        {

        prints("  瀏覽 P.%d(%d%%)  %s  %-30.30s%s",
            (int)(mf.lineno / MFNAV_PAGE)+1,
            progress,
            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 行",
            progress,
            (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) "離開 "
            );
        }
        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 's':
        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 'o':
        bpref.oldwrapmode  = !bpref.oldwrapmode;
        bpref.oldseperator = !bpref.oldseperator;
        bpref.oldstatusbar = !bpref.oldstatusbar;
        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
 */