summaryrefslogblamecommitdiffstats
path: root/mbbsd/edit.c
blob: 2dd23eee0314ddbd61cef2073cfe9fbf2c94a0af (plain) (tree)
1
2
3
4
5
6
7
          

                                             


                                                                         
                      










                                                                         
  

                                                                             












                                                                      
                                                                     



                                                                           
   
                
 
                                    
                                            
 



                               


                                                      





                 




                                                                          


                            



                            
                            
                           
 
                          




                          












                                                                             
                   











                                                

                                                                 






















                                                                              
       



                                                                                       

                                                        


                                  
                                  








                                                                               
                            
 
                                                                  
                                                                    
                                                               
                                                                 





                                                                              
                                                                          

                         

                                   


                                   
                                   



                                   
                         
                          
 

                                                


                                   
                             
 

                                          





                                             
 
                                      














                                      
                                          














         
                               
                           
                           
                           
                           
                           
                           
                           
                          

  
                                    




         
      

  
                























                                                                         
                                                                           








                                              
                          

                                               
 
                                       
                      
                         
                               
                         



                                                                

 

                       


                                                                      
                                                   
                       



                                      








                                      

                            




                                                 
                                       
                                          


                                          

                                         

 

                      

                                    

                             



                       




                                                                       
          




                                    
                            


                              
                                                    
                      
                   


                      
                       






                      



                                                     

                                   
 
                              


                              

                           


                              
                                                    
                      
                   


                      
                       







                                      










                                         
                                                                                        

                                                              
                                                               


                                                                          
                                                            

          
                                                              


                                                                      
                                                                                  
                                                                   
                                                           





                                                                 
           
              
 
                     
                              
 
                                                                
                                          



                                


                                                             

                                                    


                                              
                                         

                                 

 



















                                            

                                              



































                                                                                   
                                                                 






































                                                               






                                        

 



                                                                     
                   
                                                   

                       
                                  

                                        
                       

                                   
         

                  



               
                 
                           
 



                             

 



                                                                    
                   
                                                      

                       

                                  
                                        
                       

                                   
         

                  



               


















































                                                                            
                        
 





















                                                                                                 


                  
                               
 











                                                      




                                                 
          
                          

                            
                                           
 
                                              





                    


























                                                               

                     



             


                                            
                   
                        
 
                  
 

                                                                   


                            

                 
              



                 
   














                                             

                                                       
           
                                            
 
                  

                                   

                    
                               



                   

                                                 
                                                                  
   
           
                                         
 

                                        

                   


                                      
                                         
          

                    
                               
          

                    
                                
 
                       


                                            
                                              
                                      




                                            
     

 





                                                                            
          
                  



                         
                               
                 
 
                                                  
                                                     
                            





                          



                                                  

                                                                             






                                                              


                                       
                                    
                                               
                     
 
                                                      
 
                                                       
                    


                                                 




                                                                 





                                                                         




                                              
                   


                                 
                                                        
                            
                                              
 
                           
 

                                        
 

                                   




                                           
                      
 
                                                                     

                                               


                                                                        

                                         
                


                                         

                                                   
                                 


                                      
         
                                          
     
                

 





                                                                            
           

                   
                                                



                                   

                                   
                   
                                                          
                                          
                                                              
                               
                                                                        
            
                                                                                
                                          

                       
                       

                          
                                     
            
                                     
            
                       


                              
                                    

                

                                    





                                  



                     


                                

                            
 
 
           









                                      
                                                          




                                              
                              
 
            
 
                   
                           
                                          
                            


                            
                                                         


     






                                                                      
 
                  
 

                                
 

                                           
 






                                           
 
                                                                                 








                                                   
                                                                    


                                      
 
                              


  




                             
  












                                                                             
   
          

                       
                           


                          
                   
                                       
                   
 
 
                                           
                   

                                    
                          

                   
                                                
 
                                        
                                         
                
                                         
                
                         

                                    
                                                         


                          
                                                 
                                 
                            
                               


                         


                                                           



                  
           
                 


                        
                                          

                                        
                                                                                                                      
                                  


     
           
                                  
 
                             
                                             
                      
 
               
                              
                                       











                                           
                                         


            
                      

                 


                                               
 

                        

                                                                

                                                 


                  
           




                                         
                         

                                 





                                                       
                           





                              
 
                                 
                                                      
                                                                                 
                                                      
                          
                   


     
           
                  



                                          
                           

                                          
                           

                                                                               
                                          

                          

                   








                                                       
                                                                
                                                       
                                      





                                             
           
                  



                                  
                                          
                                  

                                                      
                    


                          

                 
                       
   
    
                 
 



                             

                              
                                         

                                  

                                  
                                     
                                         
                                                                                     

                                             
                             


                       


     


                 
    
                    


                                         
                                 
                         
                                 

                                                                          
                            







                                            
 
          
                             




                                        
                  
                          

                     
                                       
              


                                                                 




                          
           































                                                   
              

                       
                             



                                                                     





                                             
                                                                 
 
                                         
                                          
                              
                                                
                              




                                           

                                                     
                                                                                

                                           
                                



                                  
 
                                      


                                          
 
                                                 
                                                                       



                                                                          

                          
                                                      

                                     
                                                                  
                                       
                 
                               
                                                      
                                               
                                                                     

                                       
                  
                                                                              

                                                                                      
                                             
                              
                                             

                                         
                                                                      
                                           


                     
                                                 




                        


                       
          
                 
 
                                                 



                                  
                                  

                                         
                  
                                                                  

                            
                                               
                      
                     



                            

                                           

                                                                 
                                                                                    
         

                                   

                                                            
                              







                                        

                                     
           
                                          
 




                                    

                                           


                                             

                   
                        

                     
                          
               

 
    
                                             
 
                    
                                                       
                                                              
                              
          
            
                                  
                


                                              
                                                              


                                                         
                                             
                                                                      

                                     
                     

                                                                            





                                                        
                                     
                                                               
                                                                       
                                                                      

                                    
                         
                                                                     
                                                                            

                                                                              
                                                              
                                            

                                                                           

                                             
                    
                                                                            
                                                                                  
                      

                                                                    

                                                 



                 
                                                                 
                                        
                     
                                                           

                           
                                                                            
                     




                                                                            
                                                                        
                                         

                                                                      
                                                                        
                                   

                                                                      
                                                    
                                                                    
                               
                                                                  
                                                    

     
                                                               

 









































                                                                      
    


                                       
                      

                                        
 
                       
 
                                           
                                                              
                                               

               
                      







                                      
                          
                         
 
                                                                  














                                                                     
 


                            



                                                 
                                     
 
                            
                              












                                                                                




                     
                   
                                                              
                                                     


        

                                 
                                              


                                                              


      














                                      



                              


                                                  
          
                                                                                                                         
 
                              
                      

                                                                        
                                
 
                                
                       

              



                              

                   
                          
                        
                           
                                    
        
                                    
 




                             



                                                 

                                                         
 
                      

                      
 
                     
             
                                                              
                               
              

                            

                      

                          

                            


                            






                            

                                
                   

                                           
                                                                    
                                          

                            
                                         
                              





                                                                        






                              
                   
 
                                                                   
                                
 
                       
                                               
                       

                         
                       
                                      
     

                                                       
                          


















                                                                       
                         



                                                                  

                     
             







                                                   
                                     
         
     
                              
 



                                                        
                        

                         
                                                                   
                                          
                                 
     
 
                        

                       
 











                                                 
                      
                                              
                                            
      
         
                       
                                                        

                                  
                                      
         
     


               

 








                                             
 


                            
           
                  
 

                                
                                   


                                          
 





                                                           
            

                                    
     
 
 






                                           
           
                                               
 


                            
 

                               
 
                                        
 

                                                                      
 
                      
 


                                              
                   
     

 

                        
   
           
                  
 
                            
                  
 

                               
 

                                        
                                                                
 






                                                                           

                                               

                                                             
            

                                 
                                           
 




                                         
     


                                       
 




                                                                        
     









                                                  
     
 
                    






                                    
 
                          

                   









                                                









                                                
                   






                              

                                 



                         

                                                                                                                         
 
                                           

                          
                           


                    
                                



                     
                            









                                                                                                            
                                          




                                                                                 
                   




                  
                                         


                                             












                                                                                       



                                                     



                                                   
 
                                           


                                                   
        

  
                                               



                                                
                                            



                                                      
                                       






                                                                           
                                        



                                                 
                                         



                                                                       
                                      
                                                                  


                                                                             

                                                                   


        
                                      

                                                


        
                                      



                                                                         
                                        
                                                         


        
                                       
                                                            

        
                                                
                                                                   



                                                      

              
                                   






































                                                                           






                                                  



                      
     




































































                                                                        

 





                                                                                        
                    


































                                                    
                                

                               
                                  
                                          















                                                    
                     


                                

                                  
                                                          
                                                  
                 
                               
                     


                                                

                                  
                                   
                     

                                   


























                                               




                                                                                
 

                                                   

                      

                                      


                                   



                          
                          

                   
                             

                                          
                                          
      
 
                        

                                    













                                                              



                                 


                                                      
     











                                   
 
                





                                                               
                                      
                          
      
 
                       
         


                             
             




                                       
                                
                 

             
          


                              
                


                           

                                           





                                    
                




                                     

                                           











                                                 
 



                                                          











                                                             



                                         

                                                
                                         
                                 





                                          

                                                
                                         
                                 


                                                                  
                                
                                              

                                                                  
                                

                                           











                                                     













                                                                   
                         





                                                               
                         
                                   
                         
                                     
                                             

                                        
                         


                     
                     







                                                          
                                

                                                 
                                                        





                                         


         
                                            
                         


                         
           
                                          
 
                                           


           
                                                  









                                                           





                    





                                                    













                                                                        
         
                                    


                                                                           
         




                
                  
                                               
 
              



                                                               





                  
                            



                             

                                    
     


                                                            







                                                                       
                                                                   

                                
 
                                         
 
                


                                              
         

                                
 


                                                      
 

                                                              
 





                                                           
             

                                          
 
          
      

                                                        
 
             
                         
 

                               






                           
 
                                                                             
                                        



























                                                    
                                    




                                      
                      











































                                                                               
                           

                        




                                                                                                                  
                          

                 
                




                                                                                           
                          

                 
         
                    
                             


                                              
 
                                        

         
              
                                          

 


                                                         
           
                 
 
                                      


                          














                                                                
               
 


















                                             
         









                                                          
                 























                                                                                        
         
     
                
                                                                            
                                                                   
 
                  



                                                              

                                          
                                        



         
           
                         
 
                 
 

                                     
 





                                                                                                      

 
           
                          
 
                 
 






                                                  

 
           
                         
 




                                                                         
 







                                                                                             
 










                                                                       
                  
     

 


                                                      
           



                                               





                              
           
                 


                                    

                                        
              
               
                  
                                    
                     



                        
                   

 




                      
 


                                                              
                                  


                               

                                                                                                                








                                                                  
                                       





















                                                          
                                      


                               
 



















                                                                                                
                     
                                                                            
     
                                               




                               
                                                                     

             

 













































































                                                                            
 










                       
                           
              
                                                              






                                                                               
                          
























                                                                            
                                











                                                         


                                                   








                                                               





                                                            
   
                                                                                      
 
                                                            
                            
 

                                                 
                                           
                                 
                             
                                                   
                                      
 
                        

                                 
 
                
                                                    

      
                        
 
                 
                                                              
     
 
                      

                           
                   
     
 

                                                        
                                                                

                                                                            
     
 
                                                                      

                                                                        
 





                                        
               

                             

                                                                   
                                             
         


                                                                                                    
         

                               
                                                               
            

                                                           
 
                      



                                      


                                
         
                                        
                                 
                    
                         
             

                       
  
                                                               
                                                                                                                   
                                        
                                                                

                                                 
  


                          


                           
                 
                                     
                                 




                           
             
         
      


                                  
 
                                         
                             



                                                             
                                      
                
                                                 

                                                             
                  
                                          

                                      





















                                   




                                   
                         
                         
                                                   
                               
                                                                    
                                                          

                                                              
                                          

                                                 

                                       






                                                                          
                             

                                     
                                   
                 
                                                           
                                                  
                      

                                   
                                                  




                                                  
                           
                            
                      
                                                         

                                                    
                                             

                                                 
                                       

                              
                                                  

                           
                                   

                         
                                      

                              
                                                      


                             
                                                      








                                   
                                       
                                                      














                                                   
                                                               
                                                      
                          
                                                  
                         
                                              
                                       

                                       

                          
                                   

                          
                                 

                          


                                                                   

                          
                


                                           
                                            

                          
                                               

                          
                                          

                          
                                           

                          
                                          

                          
                                          

                          
                                          
                          

                      
                           
                        


                              
                                     
                      
                                                       

                                                                

                                  
                                                  

                           
                             
                      
                           
                               





                                                                 
                    


                                                              
                 
                                                 


                          
                                                             
                                                           

                           

                                                         
                                               

                                                       
                                                                      
                             
                                         
                                         
                                           

                                      
                              
                                                           

                                                                 
 
                                                  



                                                              
                                         
                                               

                                    
                                                             
                                             

                     
                                                  
                      
                           
                                    
                                                  

                      
                        

                                          
                                                  


                           
                                                  

                          
                                        
                                           

                                                                                          

                                                                                          
                


                                                                                                           




                                                                  


                           
                                                                   
                                           

                                                                                          

                                                                                          
                


                                                                                                            




                                                                  


                        
                                      

                          
                                      
                      
 
                           


                                                                                                           
                                                                    


                                                                
                      
 
                           


                                                                                                              
                                                                    


                                                                
                      
 

                           
                                                            
                      
                                                   


                                                                                      
                      
                                                  
                                                                                                         
                                                        
                                                                    


                                                    


                           
                                      
                      
                                                                        
                                                             
                                           
                      
                        
                                               
                               

                                           
                            
                                                      
                        
                                                 
                                                      
                                  

                                                     

                                                                                                     

                                                                                        
                                                                   


                                                                    



                                                                               
                                                                                           
                                                                     

                                  
                                                 

                              
                 
                                        
                                  













                                                                                       


                           
                                                              
                               
                                                                   
                                             
                                                      
                        
                 
                                  




                                   
                                                                                                      




                                        
                                           
                                                                                                                      

                      
                                                         
                                      
                                                           
                               
                                                                      
                                                                 
                             
                                                     

                                                            
                                                        
                                  
                         

                                                             
                         
                                           
                     

                                                                   
 
                                                       

                                                      
                                                                                                            

                          
                                                                        
                                             
                                                      

                          

                                                                   

                      
 

                                     
 
                                 
                            


                                                                                                   
         






                                                                
                                      
            
                                                                            
 
                                           

                                                                 
                                                  
                    
                                                    
                           
                                       
                                                   
                    

                                             
                                                                                           

                                                                                           
                                               

                           


                                                          
                           
 

                       
 
   












                                                                          
 
                                            

 
                        
   
/* $Id$ */
/**
 * edit.c, 用來提供 bbs上的文字編輯器, 即 ve.
 * 現在這一個是惡搞過的版本, 比較不穩定, 用比較多的 cpu, 但是可以省下許多
 * 的記憶體 (以 Ptt為例, 在九千人上站的時候, 約可省下 50MB 的記憶體)
 * 如果您認為「拿 cpu換記憶體」並不合乎您的須求, 您可以考慮改使用修正前的
 * 版本 (Revision 782)
 *
 * 原本 ve 的做法是, 因為每一行最大可以輸入 WRAPMARGIN 個字, 於是就替每一
 * 行保留了 WRAPMARGIN 這麼大的空間 (約 512 bytes) . 但是實際上, 站在修正
 * 成本最小的考量上, 我們只須要使得游標所在這一行維持 WRAPMARGIN 這麼大,
 * 其他每一行其實不須要這麼多的空間. 於是這個 patch就在每次游標在行間移動
 * 的時候, 將原本的那行記憶體縮小, 再將新移到的那行重新加大, 以達成最小的
 * 記憶體用量.
 * 以上說的這個動作在 adjustline() 中完成, adjustline()另外包括修正數個
 * global pointer, 以避免 dangling pointer . 
 * 另外若定義 DEBUG, 在 textline_t 結構中將加入 mlength, 表示該行實際佔的
 * 記憶體大小. 以方便測試結果.
 *
 * XXX 由於各種操作都不會 maintain 區塊標記模式的 pointer/state, 
 * 因此對於會造成增刪 line 的功能, 都得自動取消標記模式(call block_cancel()).
 *
 * 20071230 piaip
 * BBSmovie 有人作出了 1.9G 的檔案, 看來要分 hard limit 跟 soft limit
 * [第 7426572/7426572 頁 (100%)  目前顯示: 第 163384551~163384573 行]
 * 當日調查 BBSmovie 看板與精華區,平均檔案皆在 5M 以下
 * 最大的為 16M 的 Haruhi OP (avi 轉檔 with massive ANSI)
 * [第 2953/2953 頁 (100%)  目前顯示: 第 64942~64964 行]
 * 另外互動迷宮的大小為
 * [第 1408/1408 頁 (100%)  目前顯示: 第 30940~30962 行]
 * 是以定義:
 * 32M 為 size limit 
 * 1M 為 line limit
 * 又,忽然發現之前 totaln 之類都是 short... 所以 65536 就夠了?
 * 後註: 似乎是用 announce 的 append 作出來的,有看到 > --- <- mark。
 *
 * FIXME screen resize 造成 b_lines 改變, 可能會造成 state 錯亂. 尤其是 tty
 * mode 隨時都可能 resize. TODO editor_internal_t 多個欄位記 screen size,
 * 當操作到一段落之後才 check b_lines 是否有改變.
 */
#include "bbs.h"

#define EDIT_SIZE_LIMIT (32768*1024)
#define EDIT_LINE_LIMIT (65530) // (1048576)

#ifndef POST_MONEY_RATIO
#define POST_MONEY_RATIO (0.5f)
#endif

#define ENTROPY_RATIO   (0.25f)
#define ENTROPY_MAX (MAX_POST_MONEY/ENTROPY_RATIO)

#if 0
#define register 
#define DEBUG
#define inline 
#endif

/**
 * data 欄位的用法:
 * 每次 allocate 一個 textline_t 時,會配給他 (sizeof(textline_t) + string
 * length - 1) 的大小。如此可直接存取 data 而不需額外的 malloc。
 */
typedef struct textline_t {
    struct textline_t *prev;
    struct textline_t *next;
    short           len;
#ifdef DEBUG
    short           mlength;
#endif
    char            data[1];
}               textline_t;

#define KEEP_EDITING    -2

enum {
    NOBODY, MANAGER, SYSOP
};

/**
 * 這個說明會將整個 edit.c 運作的概念帶過,主要會從 editor_internal_t 的
 * data structure 談起。對於每一個 data member 的詳細功能,請見 sturcture
 * 中的註解。
 *
 * 文章的內容 (以下稱 content) 以「行」為單位,主要以 firstline, lastline,
 * totaln 來記錄。
 * 
 * User 在畫面中看到的畫面,置於一個「 window 」中,這個 window 會在
 * content 上移動,window 裡面的範圍即為要 show 出來的範圍。它用了不少
 * 欄位來記錄,包括 currline, top_of_win, currln, currpnt, curr_window_line,
 * edit_margin。顯示出來的效果當然不只是靠這幾個資料,還會跟其他欄位有交互
 * 作用,例如 彩色編輯模式、特殊符號編輯 等等。其中最複雜的部分是在選取 block
 * (見後)的時候。
 *
 * editor 的使用上目前有五種 inclusive 的 mode:
 *   insert mode:
 *     插入/取代
 *   ansi mode:
 *     彩色編輯
 *   indent mode:
 *     自動縮排
 *   phone mode:
 *     特殊符號編輯
 *   raw mode:
 *     ignore Ctrl('S'), Ctrl('Q'), Ctrl('T')
 *     贊曰: 這有什麼用? 看起來是 modem 上傳用 (沒人在用這個了吧)
 *     拿來當 dbcs option 吧
 *
 * editor 支援了區塊選擇的功能(多行選取 或 單行中的片段),對於一個 selected
 * block,可以 cut, copy, cancel, 或者存到暫取檔,甚至是往左/右 shift。詳見
 * block_XXX。
 *
 * 用 Ctrl('Y') 刪除的那一行會被記到 deleted_line 這個欄位中。undelete_line()
 * 可以做 undelete 的動作。 deleted_line 初始值為 NULL,每次只允許存一行,所以
 * 在存下來時,發現值不為 NULL 會先做 free 的動作。editor 結束時,在
 * edit_buffer_destructor() 中如果發現有 deleted_line,會在這邊釋放掉。其他地
 * 方也有相同的作法,例如 searched_string。searched_string 是在搜尋文章內容
 * 時,要尋找的 key word。
 *
 * 還有一個有趣的特點,「括號匹配」!行為就如同在 vim 裡面一樣。呃,當然沒那
 * 麼強啦,但至少在含有 c-style comment 跟 c-style string 時是對的喔。這個動
 * 作定義於 match_paren() 中。
 *
 * 另外,如果有需要新增新的欄位,請將初始化(有需要的話)的動作寫在
 * edit_buffer_constructor 中。當然也有個 edit_buffer_destructor 可以使用。
 *
 * 此外,為了提供一個 reentrant 的 editor,prev 指向前一個 editor 的
 * editor_internal_t。enter_edit_buffer 跟 exit_edit_buffer 提供進出的介面,
 * 裡面分別會呼叫 constructor 跟 destructor。
 *
 * TODO
 * vedit 裡面有個 curr_buf->oldcurrline,用來記上一次的 currline。由於只有 currline 擁
 * 有 WRAPMARGIN 的空間,所以目前的作法是當 curr_buf->oldcurrline != currline 時,就
 * resize curr_buf->oldcurrline 跟 currline。但是糟糕的是目前必須人工追蹤 currline 的行
 * 為,而且若不幸遇到 curr_buf->oldcurrline 指到的那一行已經被 free 掉,就完了。最好是
 * 把這些東西包起來。不過我沒空做了,patch is welcome :P
 *
 * Victor Hsieh <victor@csie.org>
 * Thu, 03 Feb 2005 15:18:00 +0800
 */
typedef struct editor_internal_t {

    textline_t *firstline;  /* first line of the article. */
    textline_t *lastline;   /* last line of the article. */

    textline_t *currline;   /* current line of the article(window). */
    textline_t *blockline;  /* the first selected line of the block. */
    textline_t *top_of_win; /* top line of the article in the window. */

    textline_t *deleted_line;   /* deleted line. Just keep one deleted line. */
    textline_t *oldcurrline;

    int   currln;       /* current line of the article. */
    short currpnt;      /* current column of the article. */
    int   totaln;       /* last line of the article. */
    int   curr_window_line; /* current line to the window. */
    short last_margin;
    short edit_margin;      /* when the cursor moves out of range (say,
                   t_columns), shift this length of the string
                   so you won't see the first edit_margin-th
                   character. */
    short lastindent;
    int  blockln;       /* the row you started to select block. */
    char last_phone_mode;

    char ifuseanony     :1;
    char redraw_everything  :1;

    char insert_mode        :1;
    char ansimode       :1;
    char indent_mode        :1;
    char phone_mode     :1;
    char raw_mode       :1;

    char *searched_string;
    char *sitesig_string;
    char *(*substr_fp) ();

    char synparser;     // syntax parser

    struct editor_internal_t *prev;

} editor_internal_t;
// } __attribute__ ((packed))

static editor_internal_t *curr_buf = NULL;

static const char * const fp_bak = "bak";

// forward declare
static inline int has_block_selection(void);
static textline_t * alloc_line(short length);
static void block_cancel(void);

static const char * const BIG5[13] = {
  ",;:、﹑。?!‧﹗()〝〞‵′",
  "▁▂▃▄▅▆▇█▏▎▍▌▋▊▉▓ ",
  "○⊙◎●☆★□■▼▲▽△◇◆♀♂",
  "﹌﹏\︴¯_—∥∣▕/\╳╱╲∕﹨",
  "+-×÷√±=≡≠≒≦≧<>∵∴",
  "∞~∩∪∫∮&⊥∠∟⊿﹢﹣﹤﹥﹦",
  "↑↓←→↖↗↙↘",
  "【】「」『』〈〉《》〔〕{}︵︶",
  "︹︺︷︸︻︼︿﹀︽︾﹁﹂﹃﹄",
  "◢◣◥◤﹡*※§@⊕㊣…‥﹉﹍",
  "α\βγδεζηθικλμνξοπ",
  "ρστυφχψωΔΘΛΠΣΦΨΩ",
  "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ"
};

static const char * const BIG_mode[13] = {
  "標點",
  "圖塊",
  "標記",
  "標線",
  "數一",
  "數二",
  "箭頭",
  "括一",
  "括二",
  "其他",
  "希一",
  "希二",
  "數字"
};

static const char *table[8] = {
  "│─└┴┘├┼┤┌┬┐",
  "║═╚╩╝╠╬╣╔╦╗",
  "║─╙╨╜╟╫╢╓╥╖",
  "│═╘╧╛╞╪╡╒╤╕",
  "│─╰┴╯├┼┤╭┬╮",
  "║═╰╩╯╠╬╣╭╦╮",
  "║─╙╨╜╟╫╢╓╥╖",
  "│═╘╧╛╞╪╡╒╤╕"
};

static const char *table_mode[6] = {
  "直角",
  "彎弧",
  "┼",
  "╬",
  "╫",
  "╪"
};

#ifdef DBCSAWARE
static char mbcs_mode       =1;

#define IS_BIG5_HI(x) (0x81 <= (x) && (x) <= 0xfe)
#define IS_BIG5_LOS(x) (0x40 <= (x) && (x) <= 0x7e)
#define IS_BIG5_LOE(x) (0x80 <= (x) && (x) <= 0xfe)
#define IS_BIG5_LO(x) (IS_BIG5_LOS(x) || IS_BIG5_LOE(x))
#define IS_BIG5(hi,lo) (IS_BIG5_HI(hi) && IS_BIG5_LO(lo))
 
int mchar_len(unsigned char *str)
{
  return ((str[0] != '\0' && str[1] != '\0' && IS_BIG5(str[0], str[1])) ?
            2 :
            1);
}

#define FC_RIGHT (0)
#define FC_LEFT (~FC_RIGHT)

int fix_cursor(char *str, int pos, unsigned int dir)
{ 
  int newpos, w;
  
  for(newpos = 0;
      *str != '\0' &&
        (w = mchar_len((unsigned char*)str),                               
         newpos + 1 + (dir & (w - 1))) <= pos;
      str += w, newpos += w)
    ;

  return newpos;
}

#endif

/* 記憶體管理與編輯處理 */
static inline void
edit_buffer_constructor(editor_internal_t *buf)
{
    /* all unspecified columns are 0 */
    buf->blockln = -1;
    buf->insert_mode = 1;
    buf->redraw_everything = 1;
    buf->lastindent = -1;

    buf->oldcurrline = buf->currline = buf->top_of_win =
    buf->firstline = buf->lastline = alloc_line(WRAPMARGIN);

}

static inline void
enter_edit_buffer(void)
{
    editor_internal_t *p = curr_buf;
    curr_buf = (editor_internal_t *)malloc(sizeof(editor_internal_t));
    memset(curr_buf, 0, sizeof(editor_internal_t));
    curr_buf->prev = p;
    edit_buffer_constructor(curr_buf);
}

static inline void
free_line(textline_t *p)
{
    p->next = (textline_t*)0x12345678;
    p->prev = (textline_t*)0x87654321;
    p->len = -12345;
    free(p);
}

static inline void
edit_buffer_destructor(void)
{
    textline_t *p, *pnext;
    for (p = curr_buf->firstline; p; p = pnext) {
    pnext = p->next;
    free_line(p);
    }
    if (curr_buf->deleted_line != NULL)
    free_line(curr_buf->deleted_line);

    if (curr_buf->searched_string != NULL)
    free(curr_buf->searched_string);
    if (curr_buf->sitesig_string != NULL)
    free(curr_buf->sitesig_string);
}

static inline void
exit_edit_buffer(void)
{
    editor_internal_t *p = curr_buf;

    edit_buffer_destructor();
    curr_buf = p->prev;
    free(p);
}

/**
 * transform position ansix in an ansi string of textline_t to the same
 * string without escape code.
 * @return position in the string without escape code.
 */
static int
ansi2n(int ansix, textline_t * line)
{
    register char  *data, *tmp;
    register char   ch;

    data = tmp = line->data;

    while (*tmp) {
    if (*tmp == KEY_ESC) {
        while ((ch = *tmp) && !isalpha((int)ch))
        tmp++;
        if (ch)
        tmp++;
        continue;
    }
    if (ansix <= 0)
        break;
    tmp++;
    ansix--;
    }
    return tmp - data;
}

/**
 * opposite to ansi2n, according to given textline_t.
 * @return position in the string with escape code.
 */
static short
n2ansi(short nx, textline_t * line)
{
    register short  ansix = 0;
    register char  *tmp, *nxp;
    register char   ch;

    tmp = nxp = line->data;
    nxp += nx;

    while (*tmp) {
    if (*tmp == KEY_ESC) {
        while ((ch = *tmp) && !isalpha((int)ch))
        tmp++;
        if (ch)
        tmp++;
        continue;
    }
    if (tmp >= nxp)
        break;
    tmp++;
    ansix++;
    }
    return ansix;
}

/* 螢幕處理:輔助訊息、顯示編輯內容 */

static inline void
show_phone_mode_panel(void)
{
    int i;

    move(b_lines - 1, 0);
    clrtoeol();

    if (curr_buf->last_phone_mode < 20) {
    int len;
    prints(ANSI_COLOR(1;46) "【%s輸入】 ", BIG_mode[curr_buf->last_phone_mode - 1]);
    len = strlen(BIG5[curr_buf->last_phone_mode - 1]) / 2;
    for (i = 0; i < len; i++)
        prints(ANSI_COLOR(37) "%c" ANSI_COLOR(34) "%2.2s", 
            i + 'A', BIG5[curr_buf->last_phone_mode - 1] + i * 2);
    for (i = 0; i < 16 - len; i++)
        outs("   ");
    outs(ANSI_COLOR(37) " `1~9-=切換 Z表格" ANSI_RESET);
    }
    else {
    prints(ANSI_COLOR(1;46) "【表格繪製】 /=%s *=%s形   ",
        table_mode[(curr_buf->last_phone_mode - 20) / 4], 
        table_mode[(curr_buf->last_phone_mode - 20) % 4 + 2]);
    for (i = 0;i < 11;i++)
        prints(ANSI_COLOR(37) "%c" ANSI_COLOR(34) "%2.2s", i ? i + '/' : '.', 
            table[curr_buf->last_phone_mode - 20] + i * 2);
    outs(ANSI_COLOR(37) "          Z內碼 " ANSI_RESET);
    }
}

/**
 * Show the bottom status/help bar, and BIG5/table in phone_mode.
 */
static void
edit_msg(void)
{
    char buf[STRLEN];
    int n = curr_buf->currpnt;

    if (curr_buf->ansimode)     /* Thor: 作 ansi 編輯 */
    n = n2ansi(n, curr_buf->currline);
 
    if (curr_buf->phone_mode)
    show_phone_mode_panel();

    snprintf(buf, sizeof(buf),
        " (^Z/F1)說明 (^P/^G)插入符號/圖片 (^X/^Q)離開\t"
        "║%s│%c%c%c%c║%3d:%3d",
        curr_buf->insert_mode ? "插入" : "取代",
        curr_buf->ansimode ? 'A' : 'a',
        curr_buf->indent_mode ? 'I' : 'i',
        curr_buf->phone_mode ? 'P' : 'p',
        curr_buf->raw_mode ? 'R' : 'r',
        curr_buf->currln + 1, n + 1);

    vs_footer(" 編輯文章 ", buf);
}

//#define SLOW_CHECK_DETAIL
static void
edit_buffer_check_healthy(textline_t *line)
{
    assert(line);

    if (line->next)
    assert(line->next->prev == line);
    if (line->prev)
    assert(line->prev->next == line);
    assert(0 <= line->len);
    assert(line->len <= WRAPMARGIN);
#ifdef DEBUG
    assert(line->len <= line->mlength);
#endif
#ifdef SLOW_CHECK_DETAIL
    assert(strlen(line->data) == line->len);
#endif
}

static inline int visible_window_height(void);

static void
edit_check_healthy()
{
#ifdef SLOW_CHECK_DETAIL
    int i;
    textline_t *p;
#endif

    edit_buffer_check_healthy(curr_buf->firstline);
    assert(curr_buf->firstline->prev == NULL);

    edit_buffer_check_healthy(curr_buf->lastline);
    assert(curr_buf->lastline->next == NULL);

    edit_buffer_check_healthy(curr_buf->oldcurrline);

    // currline
    edit_buffer_check_healthy(curr_buf->currline);
    assert(0 <= curr_buf->currpnt && curr_buf->currpnt <= curr_buf->currline->len);

    if (curr_buf->deleted_line) {
    edit_buffer_check_healthy(curr_buf->deleted_line);
    assert(curr_buf->deleted_line->next == NULL);
    assert(curr_buf->deleted_line->prev == NULL);
    assert(curr_buf->deleted_line != curr_buf->firstline);
    assert(curr_buf->deleted_line != curr_buf->lastline);
    assert(curr_buf->deleted_line != curr_buf->currline);
    assert(curr_buf->deleted_line != curr_buf->blockline);
    assert(curr_buf->deleted_line != curr_buf->top_of_win);
    }

    // lines
    assert(0 <= curr_buf->currln);
    assert(curr_buf->currln <= curr_buf->totaln);

    // window
    assert(curr_buf->curr_window_line < visible_window_height());

#ifdef SLOW_CHECK_DETAIL
    // firstline -> currline (0 -> currln)
    p = curr_buf->firstline;
    for (i = 0; i < curr_buf->currln; i++) {
    edit_buffer_check_healthy(p);
    p = p->next;
    }
    assert(p == curr_buf->currline);

    // currline -> lastline (currln -> totaln)
    p = curr_buf->currline;
    for (i = 0; i < curr_buf->totaln - curr_buf->currln; i++) {
    edit_buffer_check_healthy(p);
    p = p->next;
    }
    assert(p == curr_buf->lastline);

    // top_of_win -> currline (curr_window_line)
    p = curr_buf->top_of_win;
    for (i = 0; i < curr_buf->curr_window_line; i++)
    p = p->next;
    assert(p == curr_buf->currline);

#endif

    // block
    assert(curr_buf->blockln < 0 || curr_buf->blockline);
    if (curr_buf->blockline) {
    edit_buffer_check_healthy(curr_buf->blockline);
#ifdef SLOW_CHECK_DETAIL
    p = curr_buf->firstline;
    for (i = 0; i < curr_buf->blockln; i++)
        p = p->next;
    assert(p == curr_buf->blockline);
#endif
    }
}

/**
 * return the middle line of the window.
 */
static inline int
middle_line(void)
{
    return p_lines / 2 + 1;
}

/**
 * Return the previous 'num' line.  Stop at the first line if there's
 * not enough lines.
 */
static textline_t *
back_line(textline_t * pos, int num, bool changeln)
{
    while (num-- > 0) {
    register textline_t *item;

    if (pos && (item = pos->prev)) {
        pos = item;
        if (changeln)
        curr_buf->currln--;
    }
    else
        break;
    }
    return pos;
}

static inline int
visible_window_height(void)
{
    if (curr_buf->phone_mode)
    return b_lines - 1;
    else
    return b_lines;
}

/**
 * Return the next 'num' line.  Stop at the last line if there's not
 * enough lines.
 */
static textline_t *
forward_line(textline_t * pos, int num, bool changeln)
{
    while (num-- > 0) {
    register textline_t *item;

    if (pos && (item = pos->next)) {
        pos = item;
        if (changeln)
        curr_buf->currln++;
    }
    else
        break;
    }
    return pos;
}

/**
 * move the cursor to the next line with ansimode fixed.
 */
static inline void
cursor_to_next_line(void)
{
    short pos;

    if (curr_buf->currline->next == NULL)
    return;

    curr_buf->currline = curr_buf->currline->next;
    curr_buf->curr_window_line++;
    curr_buf->currln++;

    if (curr_buf->ansimode) {
    pos = n2ansi(curr_buf->currpnt, curr_buf->currline->prev);
    curr_buf->currpnt = ansi2n(pos, curr_buf->currline);
    }
    else {
    curr_buf->currpnt = (curr_buf->currline->len > curr_buf->lastindent)
        ? curr_buf->lastindent : curr_buf->currline->len;
    }
}

/**
 * opposite to cursor_to_next_line.
 */
static inline void
cursor_to_prev_line(void)
{
    short pos;

    if (curr_buf->currline->prev == NULL)
    return;

    curr_buf->curr_window_line--;
    curr_buf->currln--;
    curr_buf->currline = curr_buf->currline->prev;

    if (curr_buf->ansimode) {
    pos = n2ansi(curr_buf->currpnt, curr_buf->currline->next);
    curr_buf->currpnt = ansi2n(pos, curr_buf->currline);
    }
    else {
    curr_buf->currpnt = (curr_buf->currline->len > curr_buf->lastindent)
        ? curr_buf->lastindent : curr_buf->currline->len;
    }
}

static inline void
edit_window_adjust(void)
{
    int offset = 0;
    if (curr_buf->curr_window_line < 0) {
    offset = curr_buf->curr_window_line;
    curr_buf->curr_window_line = 0;
    curr_buf->top_of_win = curr_buf->currline;
    }
    
    if (curr_buf->curr_window_line >= visible_window_height()) {
    offset = curr_buf->curr_window_line - visible_window_height() + 1;
    curr_buf->curr_window_line = visible_window_height() - 1;
    curr_buf->top_of_win = back_line(curr_buf->currline, visible_window_height() - 1, false);
    }

    if (offset == -1)
    rscroll();
    else if (offset == 1) {
    move(visible_window_height(), 0);
    clrtoeol();
    scroll();
    } else if (offset != 0) {
    curr_buf->redraw_everything = YEA;
    }
}

static inline void
edit_window_adjust_middle(void)
{
    if (curr_buf->currln < middle_line()) {
    curr_buf->top_of_win = curr_buf->firstline;
    curr_buf->curr_window_line = curr_buf->currln;
    } else {
    int i;
    textline_t *p = curr_buf->currline;
    curr_buf->curr_window_line = middle_line();
    for (i = curr_buf->curr_window_line; i; i--)
        p = p->prev;
    curr_buf->top_of_win = p;
    }
    curr_buf->redraw_everything = YEA;
}

/**
 * Get the current line number in the window now.
 */
static int
get_lineno_in_window(void)
{
    int             cnt = 0;
    textline_t     *p = curr_buf->currline;

    while (p && (p != curr_buf->top_of_win)) {
    cnt++;
    p = p->prev;
    }
    return cnt;
}

/**
 * shift given raw data s with length len to left by one byte.
 */
static void
raw_shift_left(char *s, int len)
{
    int i;
    for (i = 0; i < len && s[i] != 0; ++i)
    s[i] = s[i + 1];
}

/**
 * shift given raw data s with length len to right by one byte.
 */
static void
raw_shift_right(char *s, int len)
{
    int i;
    for (i = len - 1; i >= 0; --i)
    s[i + 1] = s[i];
}

/**
 * Return the pointer to the next non-space position.
 */
static char *
next_non_space_char(char *s)
{
    while (*s == ' ')
    s++;
    return s;
}

/**
 * allocate a textline_t with length length.
 */
static textline_t *
alloc_line(short length)
{
    textline_t *p;

    if ((p = (textline_t *) malloc(length + sizeof(textline_t)))) {
    memset(p, 0, length + sizeof(textline_t));
#ifdef DEBUG
    p->mlength = length;
#endif
    return p;
    }
    assert(p);
    abort_bbs(0);
    return NULL;
}

/**
 * clone a textline_t
 */
static textline_t *
clone_line(const textline_t *line)
{
    textline_t *p;

    p = alloc_line(line->len);
    p->len = line->len;
    strlcpy(p->data, line->data, p->len + 1);

    return p;
}

/**
 * Insert p after line in list. Keeps up with last line
 */
static void
insert_line(textline_t *line, textline_t *p)
{
    textline_t *n;

    if ((p->next = n = line->next))
    n->prev = p;
    else
    curr_buf->lastline = p;
    line->next = p;
    p->prev = line;
}

/**
 * delete_line deletes 'line' from the line list.
 * @param saved  true if you want to keep the line in deleted_line
 */
static void
delete_line(textline_t * line, int saved)
{
    register textline_t *p = line->prev;
    register textline_t *n = line->next;

    if (!p && !n) {
    line->data[0] = line->len = 0;
    return;
    }
    assert(line != curr_buf->top_of_win);
    if (n)
    n->prev = p;
    else
    curr_buf->lastline = p;
    if (p)
    p->next = n;
    else
    curr_buf->firstline = n;

    curr_buf->totaln--;

    if (saved) {
    if  (curr_buf->deleted_line != NULL)
        free_line(curr_buf->deleted_line);
    curr_buf->deleted_line = line;
    curr_buf->deleted_line->next = NULL;
    curr_buf->deleted_line->prev = NULL;
    }
    else {
    free_line(line);
    }
}

/**
 * Return the indent space number according to CURRENT line and the FORMER
 * line. It'll be the first line contains non-space character.
 * @return space number from the beginning to the first non-space character,
 *         return 0 if non or not in indent mode.
 */
static int
indent_space(void)
{
    textline_t     *p;
    int             spcs;

    if (!curr_buf->indent_mode)
    return 0;

    for (p = curr_buf->currline; p; p = p->prev) {
    for (spcs = 0; p->data[spcs] == ' '; ++spcs);
        /* empty loop */
    if (p->data[spcs])
        return spcs;
    }
    return 0;
}

/**
 * adjustline(oldp, len);
 * 用來將 oldp 指到的那一行, 重新修正成 len這麼長.
 *
 * 呼叫了 adjustline 後記得檢查有動到 currline, 如果是的話 oldcurrline 也要動
 *
 * In FreeBSD:
 * 在這邊一共做了兩次的 memcpy() , 第一次從 heap 拷到 stack ,
 * 把原來記憶體 free() 後, 又重新在 stack上 malloc() 一次,
 * 然後再拷貝回來.
 * 主要是用 sbrk() 觀察到的結果, 這樣子才真的能縮減記憶體用量.
 * 詳見 /usr/share/doc/papers/malloc.ascii.gz (in FreeBSD)
 */
static textline_t *
adjustline(textline_t *oldp, short len)
{
    // XXX write a generic version ?
    char tmpl[sizeof(textline_t) + WRAPMARGIN];
    textline_t *newp;

    assert(0 <= oldp->len && oldp->len <= WRAPMARGIN);

    memcpy(tmpl, oldp, oldp->len + sizeof(textline_t));
    free_line(oldp);

    newp = alloc_line(len);
    memcpy(newp, tmpl, len + sizeof(textline_t));
    if( oldp == curr_buf->firstline ) curr_buf->firstline = newp;
    if( oldp == curr_buf->lastline )  curr_buf->lastline  = newp;
    if( oldp == curr_buf->currline )  curr_buf->currline  = newp;
    if( oldp == curr_buf->blockline ) curr_buf->blockline = newp;
    if( oldp == curr_buf->top_of_win) curr_buf->top_of_win= newp;
    if( newp->prev != NULL ) newp->prev->next = newp;
    if( newp->next != NULL ) newp->next->prev = newp;
    //    vmsg("adjust %x to %x, length: %d", (int)oldp, (int)newp, len);
    return newp;
}

/**
 * split 'line' right before the character pos
 *
 * @return the latter line after splitting
 */
static textline_t *
split(textline_t * line, int pos)
{
    if (pos <= line->len) {
    register textline_t *p = alloc_line(WRAPMARGIN);
    register char  *ptr;
    int             spcs = indent_space();

    curr_buf->totaln++;

    p->len = line->len - pos + spcs;
    line->len = pos;

    memset(p->data, ' ', spcs);
    p->data[spcs] = 0;

    ptr = line->data + pos;
    if (curr_buf->indent_mode)
        ptr = next_non_space_char(ptr);
    strcat(p->data + spcs, ptr);
    ptr[0] = '\0';

    if (line == curr_buf->currline && pos <= curr_buf->currpnt) {
        line = adjustline(line, line->len);
        insert_line(line, p);
        // because p is allocated with fullsize, we can skip adjust.
        // curr_buf->oldcurrline = line;
        curr_buf->oldcurrline = curr_buf->currline = p;
        if (pos == curr_buf->currpnt)
        curr_buf->currpnt = spcs;
        else
        curr_buf->currpnt -= pos;
        curr_buf->curr_window_line++;
        curr_buf->currln++;

        /* split may cause cursor hit bottom */
        edit_window_adjust();
    } else {
        p = adjustline(p, p->len);
        insert_line(line, p);
    }
    curr_buf->redraw_everything = YEA;
    }
    return line;
}

/**
 * Insert a character ch to current line.
 *
 * The line will be split if the length is >= WRAPMARGIN.  It'll be split
 * from the last space if any, or start a new line after the last character.
 */
static void
insert_char(int ch)
{
    register textline_t *p = curr_buf->currline;
    register int    i = p->len;
    register char  *s;
    int             wordwrap = YEA;

    assert(curr_buf->currpnt <= i);

    block_cancel();
    if (curr_buf->currpnt < i && !curr_buf->insert_mode) {
    p->data[curr_buf->currpnt++] = ch;
    /* Thor: ansi 編輯, 可以overwrite, 不蓋到 ansi code */
    if (curr_buf->ansimode)
        curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, p), p);
    } else {
    raw_shift_right(p->data + curr_buf->currpnt, i - curr_buf->currpnt + 1);
    p->data[curr_buf->currpnt++] = ch;
    i = ++(p->len);
    }
    if (i < WRAPMARGIN)
    return;
    s = p->data + (i - 1);
    while (s != p->data && *s == ' ')
    s--;
    while (s != p->data && *s != ' ')
    s--;
    if (s == p->data) {
    wordwrap = NA;
    s = p->data + (i - 2);
    }
    p = split(p, (s - p->data) + 1);
    p = p->next;
    i = p->len;
    if (wordwrap && i >= 1) {
    if (p->data[i - 1] != ' ') {
        p->data[i] = ' ';
        p->data[i + 1] = '\0';
        p->len++;
    }
    }
}

/**
 * insert_char twice.
 */
static void
insert_dchar(const char *dchar) 
{
    insert_char(*dchar);
    insert_char(*(dchar+1));
}

static void
insert_tab(void)
{
    do {
    insert_char(' ');
    } while (curr_buf->currpnt & 0x7);
}

/**
 * Insert a string.
 *
 * All printable and ESC_CHR will be directly printed out.
 * '\t' will be printed to align every 8 byte.
 * '\n' will split the line.
 * The other character will be ignore.
 */
static void
insert_string(const char *str)
{
    char ch;

    block_cancel();
    while ((ch = *str++)) {
    if (isprint2(ch) || ch == ESC_CHR)
        insert_char(ch);
    else if (ch == '\t')
        insert_tab();
    else if (ch == '\n')
        split(curr_buf->currline, curr_buf->currpnt);
    }
}

/**
 * undelete the deleted line.
 *
 * return NULL if there's no deleted_line, otherwise, return currline.
 */
static textline_t *
undelete_line(void)
{
    textline_t *p;

    if (!curr_buf->deleted_line)
    return NULL;

    block_cancel();
    p = clone_line(curr_buf->deleted_line);

    // insert in front of currline
    p->prev = curr_buf->currline->prev;
    p->next = curr_buf->currline->next;
    if (curr_buf->currline->prev)
    curr_buf->currline->prev->next = p;
    curr_buf->currline->prev = p;
    curr_buf->totaln++;

    curr_buf->currline = adjustline(curr_buf->currline, curr_buf->currline->len);

    // maintain special line pointer
    if (curr_buf->top_of_win == curr_buf->currline)
    curr_buf->top_of_win = p;
    if (curr_buf->firstline == curr_buf->currline)
    curr_buf->firstline = p;

    // change currline 
    curr_buf->currline = p;
    curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
    curr_buf->currpnt = 0;

    curr_buf->redraw_everything = YEA;

    return curr_buf->currline;
}

/*
 * join $line and $line->next
 *
 * line: A1 A2
 * next: B1 B2
 * ....: C1 C2
 *
 * case B=empty:
 *  return YEA
 *
 * case A+B < WRAPMARGIN:
 *  line: A1 A2 B1 B2
 *  next: C1 C2
 *  return YEA
 *  NOTE It assumes $line has allocated WRAPMARGIN length of data buffer.
 *
 * case A+B1+B2 > WRAPMARGIN, A+B1<WRAPMARGIN
 *  line: A1 A2 B1
 *  next: B2 " "
 *  call join($next)
 */
static int
join(textline_t * line)
{
    register textline_t *n;
    register int    ovfl;

    if (!(n = line->next))
    return YEA;
    if (!*next_non_space_char(n->data))
    return YEA;


    ovfl = line->len + n->len - WRAPMARGIN;
    if (ovfl < 0) {
    strcat(line->data, n->data);
    line->len += n->len;
    delete_line(n, 0);
    return YEA;
    } else {
    register char  *s; /* the split point */

    s = n->data + n->len - ovfl - 1;
    while (s != n->data && *s == ' ')
        s--;
    while (s != n->data && *s != ' ')
        s--;
    if (s == n->data)
        return YEA;
    split(n, (s - n->data) + 1);
    assert(line->len + line->next->len < WRAPMARGIN);
    join(line);
    n = line->next;
    ovfl = n->len - 1;
    if (ovfl >= 0 && ovfl < WRAPMARGIN - 2) {
        s = &(n->data[ovfl]);
        if (*s != ' ') {
        strcat(s, " ");
        n->len++;
        }
    }
    line->next=adjustline(line->next, WRAPMARGIN);
    join(line->next);
    line->next=adjustline(line->next, line->next->len);
    return NA;
    }
}

static void
delete_char(void)
{
    register int    len;

    if ((len = curr_buf->currline->len)) {
    assert(curr_buf->currpnt < len);

    raw_shift_left(curr_buf->currline->data + curr_buf->currpnt, curr_buf->currline->len - curr_buf->currpnt + 1);
    curr_buf->currline->len--;
    }
}

static void
load_file(FILE * fp, off_t offSig)
{
    char buf[WRAPMARGIN + 2];
    int indent_mode0 = curr_buf->indent_mode;
    size_t szread = 0;

    assert(fp);
    curr_buf->indent_mode = 0;
    while (fgets(buf, sizeof(buf), fp))
    {
    szread += strlen(buf);
    if (offSig < 0 || szread <= offSig)
    {
        insert_string(buf);
    }
    else
    {
        // this is the site sig
        break;
    }
    }
    curr_buf->indent_mode = indent_mode0;
}

/* 暫存檔 */
const char           *
ask_tmpbuf(int y)
{
    static char     fp_buf[] = "buf.0";
    char     msg[] = "請選擇暫存檔 (0-9)[0]: ";
    char choice[2];

    msg[19] = fp_buf[4];
    do {
    if (!getdata(y, 0, msg, choice, sizeof(choice), DOECHO))
        choice[0] = fp_buf[4];
    } while (choice[0] < '0' || choice[0] > '9');
    fp_buf[4] = choice[0];
    return fp_buf;
}

static void
read_tmpbuf(int n)
{
    FILE           *fp;
    char            fp_tmpbuf[80];
    char            tmpfname[] = "buf.0";
    const char     *tmpf;
    char            ans[4] = "y";

    if (curr_buf->totaln >= EDIT_LINE_LIMIT)
    {
    vmsg("檔案已超過最大限制,無法再讀入暫存檔。");
    return;
    }

    if (0 <= n && n <= 9) {
    tmpfname[4] = '0' + n;
    tmpf = tmpfname;
    } else {
    tmpf = ask_tmpbuf(3);
    n = tmpf[4] - '0';
    }

    setuserfile(fp_tmpbuf, tmpf);
    if (n != 0 && n != 5 && more(fp_tmpbuf, NA) != -1)
    getdata(b_lines - 1, 0, "確定讀入嗎(Y/N)?[Y]", ans, sizeof(ans), LCECHO);
    if (*ans != 'n' && (fp = fopen(fp_tmpbuf, "r"))) {
    load_file(fp, -1);
    fclose(fp);
    }
}

static void
write_tmpbuf(void)
{
    FILE           *fp;
    char            fp_tmpbuf[80], ans[4];
    textline_t     *p;
    off_t       sz = 0;

    setuserfile(fp_tmpbuf, ask_tmpbuf(3));
    if (dashf(fp_tmpbuf)) {
    more(fp_tmpbuf, NA);
    getdata(b_lines - 1, 0, "暫存檔已有資料 (A)附加 (W)覆寫 (Q)取消?[A] ",
        ans, sizeof(ans), LCECHO);

    if (ans[0] == 'q')
        return;
    }
    if (ans[0] != 'w') // 'a'
    {
    sz = dashs(fp_tmpbuf);
    if (sz > EDIT_SIZE_LIMIT)
    {
        vmsg("暫存檔已超過大小限制,無法再附加。");
        return;
    }
    }
    if ((fp = fopen(fp_tmpbuf, (ans[0] == 'w' ? "w" : "a+")))) {
    for (p = curr_buf->firstline; p; p = p->next) {
        if (p->next || p->data[0])
        fprintf(fp, "%s\n", p->data);
    }
    fclose(fp);
    }
}

static void
erase_tmpbuf(void)
{
    char            fp_tmpbuf[80];
    char            ans[4] = "n";

    setuserfile(fp_tmpbuf, ask_tmpbuf(3));
    if (more(fp_tmpbuf, NA) != -1)
    getdata(b_lines - 1, 0, "確定刪除嗎(Y/N)?[N]",
        ans, sizeof(ans), LCECHO);
    if (*ans == 'y')
    unlink(fp_tmpbuf);
}

/**
 * 編輯器自動備份
 *(最多備份 512 行 (?))
 */
void
auto_backup(void)
{
    if (curr_buf == NULL)
    return;

    if (curr_buf->currline) {
    FILE           *fp;
    textline_t     *p, *v;
    char            bakfile[PATHLEN];
    int             count = 0;

    curr_buf->currline = NULL;

    setuserfile(bakfile, fp_bak);
    if ((fp = fopen(bakfile, "w"))) {
        for (p = curr_buf->firstline; p != NULL && count < 512; p = v, count++) {
        v = p->next;
        fprintf(fp, "%s\n", p->data);
        free_line(p);
        }
        fclose(fp);
    }
    }
}

/**
 * 取回編輯器備份
 */
void
restore_backup(void)
{
    char            bakfile[80], buf[80];

    setuserfile(bakfile, fp_bak);
    if (dashf(bakfile)) {
    vs_hdr("編輯器自動復原");
    getdata(1, 0, "您有一篇文章尚未完成,(S)寫入暫存檔 (Q)算了?[S] ",
        buf, 4, LCECHO);
    if (buf[0] != 'q') {
        setuserfile(buf, ask_tmpbuf(3));
        Rename(bakfile, buf);
    } else
        unlink(bakfile);
    }
}

/* 引用文章 */

static int
garbage_line(const char *str)
{
    int             qlevel = 0;

    while (*str == ':' || *str == '>') {
    if (*(++str) == ' ')
        str++;
    if (qlevel++ >= 1)
        return 1;
    }
    while (*str == ' ' || *str == '\t')
    str++;
    if (qlevel >= 1) {
    if (!strncmp(str, "※ ", 3) || !strncmp(str, "==>", 3) ||
        strstr(str, ") 提到:\n"))
        return 1;
    }
    return (*str == '\n');
}

static void
quote_strip_ansi_inline(unsigned char *is)
{
    unsigned char *os = is;

    while (*is)
    {
    if(*is != ESC_CHR)
        *os++ = *is;
    else
    {
        is ++;
        if(*is == '*')
        {
        /* ptt prints, keep it as normal */
        *os++ = '*';
        *os++ = '*';
        } 
        else 
        {
        /* normal ansi, strip them out. */
        while (*is && ANSI_IN_ESCAPE(*is))
            is++;
        }
    }
    is++;

    }

    *os = 0;
}

static void
do_quote(void)
{
    int             op;
    char            buf[512];

    getdata(b_lines - 1, 0, "請問要引用原文嗎(Y/N/All/Repost)?[Y] ",
        buf, 3, LCECHO);
    op = buf[0];

    if (op != 'n') {
    FILE           *inf;

    if ((inf = fopen(quote_file, "r"))) {
        char           *ptr;
        int             indent_mode0 = curr_buf->indent_mode;

        fgets(buf, sizeof(buf), inf);
        if ((ptr = strrchr(buf, ')')))
        ptr[1] = '\0';
        else if ((ptr = strrchr(buf, '\n')))
        ptr[0] = '\0';

        if ((ptr = strchr(buf, ':'))) {
        char           *str;

        while (*(++ptr) == ' ');

        /* 順手牽羊,取得 author's address */
        if ((curredit & EDIT_BOTH) && (str = strchr(quote_user, '.'))) {
            strcpy(++str, ptr);
            str = strchr(str, ' ');
            assert(str);
            str[0] = '\0';
        }
        } else
        ptr = quote_user;

        curr_buf->indent_mode = 0;
        insert_string("※ 引述《");
        insert_string(ptr);
        insert_string("》之銘言:\n");

        if (op != 'a')  /* 去掉 header */
        while (fgets(buf, sizeof(buf), inf) && buf[0] != '\n');
        /* FIXME by MH:
             如果 header 到內文中間沒有空行分隔,會造成 All 以外的模式
             都引不到內文。
         */

        if (op == 'a')
        while (fgets(buf, sizeof(buf), inf)) {
            insert_char(':');
            insert_char(' ');
            quote_strip_ansi_inline((unsigned char *)buf);
            insert_string(buf);
        }
        else if (op == 'r')
        while (fgets(buf, sizeof(buf), inf)) {
            /* repost, keep anything */
            // quote_strip_ansi_inline((unsigned char *)buf);
            insert_string(buf);
        }
        else {
        if (curredit & EDIT_LIST)   /* 去掉 mail list 之 header */
            while (fgets(buf, sizeof(buf), inf) && (!strncmp(buf, "※ ", 3)));
        while (fgets(buf, sizeof(buf), inf)) {
            if (!strcmp(buf, "--\n"))
            break;
            if (!garbage_line(buf)) {
            insert_char(':');
            insert_char(' ');
            quote_strip_ansi_inline((unsigned char *)buf);
            insert_string(buf);
            }
        }
        }
        curr_buf->indent_mode = indent_mode0;
        fclose(inf);
    }
    }
}

/**
 * 審查 user 引言的使用
 */
static int
check_quote(void)
{
    register textline_t *p = curr_buf->firstline;
    register char  *str;
    int             post_line;
    int             included_line;

    post_line = included_line = 0;
    while (p) {
    if (!strcmp(str = p->data, "--"))
        break;
    if (str[1] == ' ' && ((str[0] == ':') || (str[0] == '>')))
        included_line++;
    else {
        while (*str == ' ' || *str == '\t')
        str++;
        if (*str)
        post_line++;
    }
    p = p->next;
    }

    if ((included_line >> 2) > post_line) {
    move(4, 0);
    outs("本篇文章的引言比例超過 80%,請您做些微的修正:\n\n"
         ANSI_COLOR(1;33) "1) 增加一些文章 或  2) 刪除不必要之引言" ANSI_RESET);
    {
        char            ans[4];

        getdata(12, 12, "(E)繼續編輯 (W)強制寫入?[E] ",
            ans, sizeof(ans), LCECHO);
        if (ans[0] == 'w')
        return 0;
    }
    return 1;
    }
    return 0;
}

/* 檔案處理:讀檔、存檔、標題、簽名檔 */
off_t loadsitesig(const char *fname);

static void
read_file(const char *fpath, int splitSig)
{
    FILE  *fp;
    off_t offSig = -1;

    if (splitSig)
    offSig = loadsitesig(fpath);

    if ((fp = fopen(fpath, "r")) == NULL) {
    int fd;
    if ((fd = creat(fpath, 0600)) >= 0) {
        close(fd);
        return;
    }
    assert(fd >= 0);
    abort_bbs(0);
    }
    load_file(fp, offSig);
    fclose(fp);
}

void
write_header(FILE * fp,  const char *mytitle)
{
    assert(mytitle);
    if (curredit & EDIT_MAIL || curredit & EDIT_LIST) {
    fprintf(fp, "%s %s (%s)\n", str_author1, cuser.userid,
        cuser.nickname
    );
    } else {
    const char *ptr = mytitle;
    struct {
        char            author[IDLEN + 1];
        char            board[IDLEN + 1];
        char            title[66];
        time4_t         date;   /* last post's date */
        int             number; /* post number */
    }               postlog;

    memset(&postlog, 0, sizeof(postlog));
    strlcpy(postlog.author, cuser.userid, sizeof(postlog.author));
    if (curr_buf)
        curr_buf->ifuseanony = 0;
#ifdef HAVE_ANONYMOUS
    if (currbrdattr & BRD_ANONYMOUS) {
        int             defanony = (currbrdattr & BRD_DEFAULTANONYMOUS);

#if defined(PLAY_ANGEL) && defined(BN_ANGELPRAY)
        // dirty hack here... sorry
        if (HasUserPerm(PERM_ANGEL) && currboard 
        && strcmp(currboard, BN_ANGELPRAY) == 0)
        {
        char mynick[IDLEN+1];
        angel_load_my_fullnick(mynick, sizeof(mynick));
        getdata_str(3, 0, "請輸入想用的名字(輸入[r]為真名): ", 
            real_name, sizeof(real_name), DOECHO, mynick);
        } else
#endif // PLAY_ANGEL && BN_ANGELPRAY
        if (defanony)
        getdata(3, 0, "請輸入你想用的ID,也可直接按[Enter],"
         "或是按[r]用真名:", real_name, sizeof(real_name), DOECHO);
        else
        getdata(3, 0, "請輸入你想用的ID,也可直接按[Enter]使用原ID:",
            real_name, sizeof(real_name), DOECHO);
        if (!real_name[0] && defanony) {
        strlcpy(real_name, "Anonymous", sizeof(real_name));
        strlcpy(postlog.author, real_name, sizeof(postlog.author));
        if (curr_buf)
            curr_buf->ifuseanony = 1;
        } else {
        if (!strcmp("r", real_name) || (!defanony && !real_name[0]))
            strlcpy(postlog.author, cuser.userid, sizeof(postlog.author));
        else {
            snprintf(postlog.author, sizeof(postlog.author),
                 "%s.", real_name);
            if (curr_buf)
            curr_buf->ifuseanony = 1;
        }
        }
    }
#endif
    strlcpy(postlog.board, currboard, sizeof(postlog.board));
    if (!strncmp(ptr, str_reply, 4))
        ptr += 4;
    strlcpy(postlog.title, ptr, sizeof(postlog.title));
    postlog.date = now;
    postlog.number = 1;
    append_record(".post", (fileheader_t *) & postlog, sizeof(postlog));
#ifdef HAVE_ANONYMOUS
    if (currbrdattr & BRD_ANONYMOUS) {
        int             defanony = (currbrdattr & BRD_DEFAULTANONYMOUS);

        fprintf(fp, "%s %s (%s) %s %s\n", str_author1, postlog.author,
            (((!strcmp(real_name, "r") && defanony) ||
              (!real_name[0] && (!defanony))) ? cuser.nickname :
             "猜猜我是誰 ? ^o^"),
            local_article ? str_post2 : str_post1, currboard);
    } else {
        fprintf(fp, "%s %s (%s) %s %s\n", str_author1, cuser.userid,
            cuser.nickname,
            local_article ? str_post2 : str_post1, currboard);
    }
#else               /* HAVE_ANONYMOUS */
    fprintf(fp, "%s %s (%s) %s %s\n", str_author1, cuser.userid,
        cuser.nickname,
        local_article ? str_post2 : str_post1, currboard);
#endif              /* HAVE_ANONYMOUS */

    }
    fprintf(fp, "標題: %s\n時間: %s\n", mytitle, ctime4(&now));
}

off_t
loadsitesig(const char *fname)
{
    int fd = 0;
    off_t sz = 0, ret = -1;
    char *start, *sp;

    sz = dashs(fname);
    if (sz < 1)
    return -1;
    fd = open(fname, O_RDONLY);
    if (fd < 0)
    return -1;
    start = (char*)mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);
    if (start)
    {
    sp = start + sz - 4 - 1; // 4 = \n--\n
    while (sp > start)
    {
        if ((*sp == '\n' && strncmp(sp, "\n--\n", 4) == 0) ||
        (*sp == '\r' && strncmp(sp, "\r--\r", 4) == 0) )
        {
        size_t szSig = sz - (sp-start+1);
        ret = sp - start + 1;
        // allocate string
        curr_buf->sitesig_string = (char*) malloc (szSig + 1);
        if (curr_buf->sitesig_string)
        {
            memcpy(curr_buf->sitesig_string, sp+1, szSig);
            curr_buf->sitesig_string[szSig] = 0;
        }
        break;
        }
        sp --;
    }
    munmap(start, sz);
    }
    
    close(fd);
    return ret;
}

void
addsignature(FILE * fp, int ifuseanony)
{
    FILE           *fs;
    int             i;
    char            buf[WRAPMARGIN + 1];
    char            fpath[STRLEN];

    char            ch;

    if (!strcmp(cuser.userid, STR_GUEST)) {
    fprintf(fp, "\n--\n※ 發信站 :" BBSNAME "(" MYHOSTNAME
        ") \n◆ From: %s\n", FROMHOST);
    return;
    }
    if (!ifuseanony) {

    int browsing = 0;
    SigInfo     si;
    memset(&si, 0, sizeof(si));

browse_sigs:
    showsignature(fpath, &i, &si);

    if (si.total > 0){
        char msg[64];

        ch = isdigit(cuser.signature) ? cuser.signature : 'x';
        sprintf(msg,
            (browsing || (si.max > si.show_max))  ?
            "請選擇簽名檔 (1-9, 0=不加 n=翻頁 x=隨機)[%c]: ":
            "請選擇簽名檔 (1-9, 0=不加 x=隨機)[%c]: ",
            ch);
        getdata(0, 0, msg, buf, 4, LCECHO);

        if(buf[0] == 'n')
        {
        si.show_start = si.show_max + 1;
        if(si.show_start > si.max)
            si.show_start = 0;
        browsing = 1;
        goto browse_sigs;
        }

        if (!buf[0])
        buf[0] = ch;

        if (isdigit((int)buf[0]))
        ch = buf[0];
        else
        ch = '1' + random() % (si.max+1);
        pwcuSetSignature(buf[0]);

        if (ch != '0') {
        fpath[i] = ch;
        do
        {
            if ((fs = fopen(fpath, "r"))) {
            fputs("\n--\n", fp);
            for (i = 0; i < MAX_SIGLINES &&
                fgets(buf, sizeof(buf), fs); i++)
                fputs(buf, fp);
            fclose(fs);
            fpath[i] = ch;
            }
            else
            fpath[i] = '1' + (fpath[i] - '1' + 1) % (si.max+1);
        } while (!isdigit((int)buf[0]) && si.max > 0 && ch != fpath[i]);
        }
    }
    }
#ifdef HAVE_ORIGIN
#ifdef HAVE_ANONYMOUS
    if (ifuseanony)
    fprintf(fp, "\n--\n※ 發信站: " BBSNAME "(" MYHOSTNAME
        ") \n◆ From: %s\n", "匿名天使的家");
    else
#endif
    {
    char            temp[33];

    strlcpy(temp, FROMHOST, sizeof(temp));
    fprintf(fp, "\n--\n※ 發信站: " BBSNAME "(" MYHOSTNAME
        ") \n◆ From: %s\n", temp);
    }
#endif
}

#ifdef USE_POST_ENTROPY
static int
get_string_entropy(const char *s)
{
    int ent = 0;
    while (*s)
    {
    char c = *s++;
    if (!isascii(c) || isalnum(c))
        ent++;
    }
    return ent;
}
#endif

#ifdef EXP_EDIT_UPLOAD
static void upload_file(void);
#endif // EXP_EDIT_UPLOAD

// return   EDIT_ABORTED    if aborted
//      KEEP_EDITING    if keep editing
//      0       if write ok & exit
static int
write_file(const char *fpath, int saveheader, int *islocal, char mytitle[STRLEN], int upload, int chtitle, int *pentropy)
{
    FILE           *fp = NULL;
    textline_t     *p;
    char            ans[TTLEN], *msg;
    int             aborted = 0, line = 0, checksum[3], sum = 0, po = 1;
    int             entropy = 0;

    assert(!chtitle || mytitle);
    vs_hdr("檔案處理");
    move(1,0);

#ifdef EDIT_UPLOAD_ALLOWALL
    upload = 1;
#endif // EDIT_UPLOAD_ALLOWALL

    // common trail

    if (currstat == SMAIL)
    outs("[S]儲存");
    else if (local_article)
    outs("[L]站內信件 (S)儲存");
    else
    outs("[S]儲存 (L)站內信件");

#ifdef EXP_EDIT_UPLOAD
    if (upload)
    outs(" (U)上傳資料");
#endif // EXP_EDIT_UPLOAD

    if (chtitle)
    outs(" (T)改標題");

    outs(" (A)放棄 (E)繼續 (R/W/D)讀寫刪暫存檔");

    getdata(2, 0, "確定要儲存檔案嗎? ", ans, 2, LCECHO);

    // avoid lots pots
    if (ans[0] != 'a')
    sleep(1);

    switch (ans[0]) {
    case 'a':
    outs("文章" ANSI_COLOR(1) " 沒有 " ANSI_RESET "存入");
    aborted = EDIT_ABORTED;
    break;
    case 'e':
    return KEEP_EDITING;
#ifdef EXP_EDIT_UPLOAD
    case 'u':
    if (upload)
        upload_file();
    return KEEP_EDITING;
#endif // EXP_EDIT_UPLOAD
    case 'r':
    read_tmpbuf(-1);
    return KEEP_EDITING;
    case 'w':
    write_tmpbuf();
    return KEEP_EDITING;
    case 'd':
    erase_tmpbuf();
    return KEEP_EDITING;
    case 't':
    if (!chtitle)
        return KEEP_EDITING;
    move(3, 0);
    prints("舊標題:%s", mytitle);
    strlcpy(ans, mytitle, sizeof(ans));
    if (getdata_buf(4, 0, "新標題:", ans, sizeof(ans), DOECHO))
        strlcpy(mytitle, ans, STRLEN);
    return KEEP_EDITING;
    case 's':
    if (!HasUserPerm(PERM_LOGINOK)) {
        local_article = 1;
        // only report if local can be set.
        if (islocal)
        {
        mvouts(2, 0, "您尚未通過身份確認,只能 Local Save。\n");
        pressanykey();
        }
    } else
        local_article = 0;
    break;
    case 'l':
    local_article = 1;
    }

    if (!aborted) {

    if (saveheader && !(curredit & EDIT_MAIL) && check_quote())
        return KEEP_EDITING;

    assert(*fpath);
    if ((fp = fopen(fpath, "w")) == NULL) {
        assert(fp);
        abort_bbs(0);
    }
    if (saveheader)
        write_header(fp, mytitle);
    }
    if (!aborted) {
    for (p = curr_buf->firstline; p; p = p->next) {
        msg = p->data;

        if (p->next == NULL && !msg[0]) // ignore lastline is empty
        continue;

        trim(msg);

        line++;

        /* check crosspost */
        if (currstat == POSTING && po ) {
        int msgsum = StringHash(msg);
        if (msgsum) {
            if (postrecord.last_bid != currbid &&
            postrecord.checksum[po] == msgsum) {
            po++;
            if (po > 3) {
                postrecord.times++;
                postrecord.last_bid = currbid;
                po = 0;
            }
            } else
            po = 1;
            if (line >= curr_buf->totaln / 2 && sum < 3) {
            checksum[sum++] = msgsum;
            }
        }
        }
#ifdef USE_POST_ENTROPY
        // calculate the real content of msg
        if (entropy < ENTROPY_MAX)
        entropy += get_string_entropy(msg);
        else
#endif
        entropy = ENTROPY_MAX;
        // write the message body
        fprintf(fp, "%s\n", msg);
    }
    }
    curr_buf->currline = NULL;

    // what if currbid == 0? add currstat checking.
    if (currstat == POSTING &&
    postrecord.times > MAX_CROSSNUM-1 && 
    !is_hidden_board_friend(currbid, currutmp->uid))
    anticrosspost();

    if (po && sum == 3) {
    memcpy(&postrecord.checksum[1], checksum, sizeof(int) * 3);
        if(postrecord.last_bid != currbid)
        postrecord.times = 0;
    }

    *pentropy = entropy;
    if (aborted)
    return aborted;

    if (islocal)
    *islocal = local_article;

    if (curr_buf->sitesig_string)
    fprintf(fp, curr_buf->sitesig_string);

    if (currstat == POSTING || currstat == SMAIL)
    {
    addsignature(fp, curr_buf->ifuseanony);
    }
    else if (currstat == REEDIT)
    {
#ifndef ALL_REEDIT_LOG
    // why force signature in SYSOP board?
    if(strcmp(currboard, BN_SYSOP) == 0)
#endif
    {
        fprintf(fp,
            "※ 編輯: %-15s 來自: %-20s (%s)\n",
            cuser.userid, 
            FROMHOST,
            Cdate_mdHM(&now));
    }
    }

    fclose(fp);
    return 0;
}

static inline int
has_block_selection(void)
{
    return curr_buf->blockln >= 0;
}

/**
 * a block is continual lines of the article.
 */

/**
 * stop the block selection.
 */
static void
block_cancel(void)
{
    if (has_block_selection()) {
    curr_buf->blockln = -1;
    curr_buf->blockline = NULL;
    curr_buf->redraw_everything = YEA;
    }
}

static inline void
setup_block_begin_end(textline_t **begin, textline_t **end)
{
    if (curr_buf->currln >= curr_buf->blockln) {
    *begin = curr_buf->blockline;
    *end = curr_buf->currline;
    } else {
    *begin = curr_buf->currline;
    *end = curr_buf->blockline;
    }
}

#define BLOCK_TRUNCATE  0
#define BLOCK_APPEND    1
/**
 * save the selected block to file 'fname.'
 * mode: BLOCK_TRUNCATE  truncate mode
 *       BLOCK_APPEND    append mode
 */
static void
block_save_to_file(const char *fname, int mode)
{
    textline_t *begin, *end;
    char fp_tmpbuf[80];
    FILE *fp;

    if (!has_block_selection())
    return;

    setup_block_begin_end(&begin, &end);

    setuserfile(fp_tmpbuf, fname);
    if ((fp = fopen(fp_tmpbuf, mode == BLOCK_APPEND ? "a+" : "w+"))) {

    textline_t *p;

    for (p = begin; p != end; p = p->next)
        fprintf(fp, "%s\n", p->data);
    fprintf(fp, "%s\n", end->data);
    fclose(fp);
    }
}

/**
 * delete selected block
 */
static void
block_delete(void)
{
    textline_t *begin, *end;
    textline_t *p;

    if (!has_block_selection())
    return;

    setup_block_begin_end(&begin, &end);

    // the block region is (currln, block) or (blockln, currln).

    textline_t *pnext;
    int deleted_line_count = abs(curr_buf->currln - curr_buf->blockln) + 1;
    if (begin->prev)
    begin->prev->next = end->next;
    if (end->next)
    end->next->prev = begin->prev;

    if (curr_buf->currln > curr_buf->blockln) {
    // case (blockln, currln)
    curr_buf->currln = curr_buf->blockln;
    curr_buf->curr_window_line -= deleted_line_count - 1;
    } else {
    // case (currln, blockln)
    }
    curr_buf->totaln -= deleted_line_count;

    curr_buf->currline = end->next;
    if (curr_buf->currline == NULL) {
    curr_buf->currline = begin->prev;
    curr_buf->currln--;
    curr_buf->curr_window_line--;
    }
    if (curr_buf->currline == NULL) {
    assert(curr_buf->currln == -1);
    assert(curr_buf->totaln == -1);

    curr_buf->currline = alloc_line(WRAPMARGIN);
    curr_buf->currln++;
    curr_buf->totaln++;
    } else {
    curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
    }
    curr_buf->oldcurrline = curr_buf->currline;

    // maintain special line pointer
    if (curr_buf->firstline == begin)
    curr_buf->firstline = curr_buf->currline;
    if (curr_buf->lastline == end)
    curr_buf->lastline = curr_buf->currline;
    if (curr_buf->curr_window_line <= 0) {
    curr_buf->curr_window_line = 0;
    curr_buf->top_of_win = curr_buf->currline;
    }

    // remove buffer
    end->next = NULL;
    for (p = begin; p; p = pnext) {
    pnext = p->next;
    free_line(p);
    deleted_line_count--;
    }
    assert(deleted_line_count == 0);

    curr_buf->currpnt = 0;

    block_cancel();
}

static void
block_cut(void)
{
    if (!has_block_selection())
    return;

    block_save_to_file("buf.0", BLOCK_TRUNCATE);
    block_delete();
}

static void
block_copy(void)
{
    if (!has_block_selection())
    return;

    block_save_to_file("buf.0", BLOCK_TRUNCATE);

    block_cancel();
}

static void
block_prompt(void)
{
    char fp_tmpbuf[80];
    char tmpfname[] = "buf.0";
    char mode[2] = "w";          
    char choice[2];

    move(b_lines - 1, 0);
    clrtoeol();

    if (!getdata(b_lines - 1, 0, "把區塊移至暫存檔 (0:Cut, 5:Copy, 6-9, q: Cancel)[0] ", choice, sizeof(choice), LCECHO))
    choice[0] = '0';

    if (choice[0] < '0' || choice[0] > '9')
    goto cancel_block;

    if (choice[0] == '0') {
    block_cut();
    return;
    }
    else if (choice[0] == '5') {
    block_copy();
    return;
    }

    tmpfname[4] = choice[0];
    setuserfile(fp_tmpbuf, tmpfname);
    if (dashf(fp_tmpbuf)) {
    more(fp_tmpbuf, NA);
    getdata(b_lines - 1, 0, "暫存檔已有資料 (A)附加 (W)覆寫 (Q)取消?[W] ", mode, sizeof(mode), LCECHO);
    if (mode[0] == 'q')
        goto cancel_block;
    else if (mode[0] != 'a')
        mode[0] = 'w';
    }

    if (vans("刪除區塊(Y/N)?[N] ") != 'y')
    goto cancel_block;

    block_save_to_file(tmpfname, mode[0] == 'a' ? BLOCK_APPEND : BLOCK_TRUNCATE);

cancel_block:
    block_cancel();
}

static void
block_select(void)
{
    curr_buf->blockln = curr_buf->currln;
    curr_buf->blockline = curr_buf->currline;
}

/////////////////////////////////////////////////////////////////////////////
// Syntax Highlight

#define PMORE_USE_ASCII_MOVIE // disable this if you don't enable ascii movie
#define ENABLE_PMORE_ASCII_MOVIE_SYNTAX // disable if you don't want rich colour syntax

#ifdef PMORE_USE_ASCII_MOVIE
// pmore movie header support
unsigned char *
    mf_movieFrameHeader(unsigned char *p, unsigned char *end);

#endif // PMORE_USE_ASCII_MOVIE

enum {
    EOATTR_NORMAL   = 0x00,
    EOATTR_SELECTED = 0x01, // selected (reverse)
    EOATTR_MOVIECODE= 0x02, // pmore movie
    EOATTR_BBSLUA   = 0x04, // BBS Lua (header)
    EOATTR_COMMENT  = 0x08, // comment syntax

};

static const char * const luaKeywords[] = {
    "and",   "break", "do",  "else", "elseif",
    "end",   "for",   "if",  "in",   "not",  "or", 
    "repeat","return","then","until","while",
    NULL
};

static const char * const luaDataKeywords[] = {
    "false", "function", "local", "nil", "true",
    NULL
};

static const char * const luaFunctions[] = {
    "assert", "print", "tonumber", "tostring", "type",
    NULL
};

static const char * const luaMath[] = {
    "abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "cosh", "deg",
    "exp", "floor", "fmod", "frexp", "ldexp", "log", "log10", "max", "min",
    "modf", "pi", "pow", "rad", "random", "randomseed", "sin", "sinh", 
    "sqrt", "tan", "tanh",
    NULL
};

static const char * const luaTable[] = {
    "concat", "insert", "maxn", "remove", "sort",
    NULL
};

static const char * const luaString[] = {
    "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", 
    "lower", "match", "rep", "reverse", "sub", "upper", NULL
};

static const char * const luaBbs[] = {
    "ANSI_COLOR", "ANSI_RESET", "ESC", "addstr", "clear", "clock",
    "clrtobot", "clrtoeol", "color", "ctime", "getch","getdata",
    "getmaxyx", "getstr", "getyx", "interface", "kball", "kbhit", "kbreset", 
    "move", "moverel", "now", "outs", "pause", "print", "rect", "refresh",
    "setattr", "sitename", "sleep", "strip_ansi", "time", "title", 
    "userid", "usernick",
    NULL
};

static const char * const luaToc[] = {
    "author", "date", "interface", "latestref", 
    "notes", "title", "version",
    NULL
};

static const char * const luaBit[] = {
    "arshift", "band", "bnot", "bor", "bxor", "cast", "lshift", "rshift",
    NULL
};

static const char * const luaStore[] = {
    "USER", "GLOBAL", "iolimit", "limit", "load", "save",
    NULL
};

static const char * const luaLibs[] = {
    "bbs", "bit", "math", "store", "string", "table", "toc",
    NULL
};
static const char* const * const luaLibAPI[] = {
    luaBbs, luaBit, luaMath, luaStore, luaString, luaTable, luaToc,
    NULL
};

int synLuaKeyword(const char *text, int n, char *wlen)
{
    int i = 0;
    const char * const *tbl = NULL;
    if (*text >= 'A' && *text <= 'Z')
    {
    // normal identifier
    while (n-- > 0 && (isalnum(*text) || *text == '_'))
    {
        text++; 
        (*wlen) ++;
    }
    return 0;
    }
    if (*text >= '0' && *text <= '9')
    {
    // digits
    while (n-- > 0 && (isdigit(*text) || *text == '.' || *text == 'x'))
    {
        text++; 
        (*wlen) ++;
    }
    return 5;
    }
    if (*text == '#')
    {
    text++;
    (*wlen) ++;
    // length of identifier
    while (n-- > 0 && (isalnum(*text) || *text == '_'))
    {
        text++; 
        (*wlen) ++;
    }
    return -2;
    }

    // ignore non-identifiers
    if (!(*text >= 'a' && *text <= 'z'))
    return 0;

    // 1st, try keywords
    for (i = 0; luaKeywords[i] && *text >= *luaKeywords[i]; i++)
    {
    int l = strlen(luaKeywords[i]);
    if (n < l)
        continue;
    if (isalnum(text[l]))
        continue;
    if (strncmp(text, luaKeywords[i], l) == 0)
    {
        *wlen = l;
        return 3;
    }
    }
    for (i = 0; luaDataKeywords[i] && *text >= *luaDataKeywords[i]; i++)
    {
    int l = strlen(luaDataKeywords[i]);
    if (n < l)
        continue;
    if (isalnum(text[l]))
        continue;
    if (strncmp(text, luaDataKeywords[i], l) == 0)
    {
        *wlen = l;
        return 2;
    }
    }
    for (i = 0; luaFunctions[i] && *text >= *luaFunctions[i]; i++)
    {
    int l = strlen(luaFunctions[i]);
    if (n < l)
        continue;
    if (isalnum(text[l]))
        continue;
    if (strncmp(text, luaFunctions[i], l) == 0)
    {
        *wlen = l;
        return 6;
    }
    }
    for (i = 0; luaLibs[i]; i++)
    {
    int l = strlen(luaLibs[i]);
    if (n < l)
        continue;
    if (text[l] != '.' && text[l] != ':')
        continue;
    if (strncmp(text, luaLibs[i], l) == 0)
    {
        *wlen = l+1;
        text += l; text ++;
        n -= l; n--;
        break;
    }
    }

    tbl = luaLibAPI[i];
    if (!tbl)
    {
    // calcualte wlen
    while (n-- > 0 && (isalnum(*text) || *text == '_'))
    {
        text++; 
        (*wlen) ++;
    }
    return 0;
    }

    for (i = 0; tbl[i]; i++)
    {
    int l = strlen(tbl[i]);
    if (n < l)
        continue;
    if (isalnum(text[l]))
        continue;
    if (strncmp(text, tbl[i], l) == 0)
    {
        *wlen += l;
        return 6;
    }
    }
    // luaLib. only
    return -6;
}

void syn_pmore_render(char *os, int len, char *buf)
{
    // XXX buf should be same length as s.
    char *s = (char *)mf_movieFrameHeader((unsigned char*)os, (unsigned char*)os + len);
    char attr = 1;
    char iname = 0;
    char prefix = 0;

    memset(buf, 0, len);
    if (!len || !s) return;

    // render: frame header
    memset(buf, attr++, (s - os));
    len -= s - os;
    buf += s - os;

    while (len-- > 0)
    {
    switch (*s++)
    {
        case 'P':
        case 'E':
        *buf++ = attr++;
        return;

        case 'S':
        *buf++ = attr++;
        continue;

        case '0': case '1': case '2': case '3':
        case '4': case '5': case '6': case '7':
        case '8': case '9': case '.': 
        *buf++ = attr;
        while (len > 0 && isascii(*s) && 
            (isalnum(*s) || *s == '.') )
        {
            *buf++ = attr;
            len--; s++;
        }
        return;

        case '#':
        *buf++ = attr++;
        while (len > 0)
        {
            *buf++ = attr;
            if (*s == '#') attr++;
            len--; s++;
        }
        return;

        case ':':
        *buf++ = attr;
        while (len > 0 && isascii(*s) && 
            (isalnum(*s) || *s == ':') )
        {
            *buf++ = attr;
            len--;
            if (*s++ == ':') break;
        }
        attr++;
        continue;

        case 'I':
        case 'G':
        iname = 0;
        *buf++ = attr++;
        prefix = 1;
        while (len > 0 && 
            ( (isascii(*s) && isalnum(*s)) || 
              strchr("+-,:lpf", *s)) )
        {
            if (prefix)
            {
            if (!strchr(":lpf", *s))
                break;
            prefix = 0;
            }
            *buf++ = attr;
            if (*s == ',') 
            {
            attr++;
            prefix = 1;
            }
            s++; len--;
        }
        attr++;
        return; 

        case 'K':
        *buf++ = attr;
        if (*s != '#')
            return;
        *buf++ = attr; s++; len--; // #
        while (len >0)
        {
            *buf++ = attr;
            len--;
            if (*s++ == '#') break;
        }
        attr++;
        continue;


        default: // unknown
        return;
    }
    }
}

/**
 * Just like outs, but print out '*' instead of 27(decimal) in the given string.
 *
 * FIXME column could not start from 0
 */

static void
edit_outs_attr_n(const char *text, int n, int attr)
{
    int    column = 0;
    register unsigned char inAnsi = 0;
    register unsigned char ch;
    int doReset = 0;
    const char *reset = ANSI_RESET;

    // syntax attributes
    char fComment = 0,
     fSingleQuote = 0,
     fDoubleQuote = 0,
     fSquareQuote = 0,
     fWord = 0;

    // movie syntax rendering
#ifdef ENABLE_PMORE_ASCII_MOVIE_SYNTAX
    char movie_attrs[WRAPMARGIN+10] = {0};
    char *pmattr = movie_attrs, mattr = 0;
#endif

#ifdef COLORED_SELECTION
    if ((attr & EOATTR_SELECTED) && 
    (attr & ~EOATTR_SELECTED))
    {
    reset = ANSI_COLOR(0;7;36);
    doReset = 1;
    outs(reset);
    }
    else 
#endif // if not defined, color by  priority - selection first
    if (attr & EOATTR_SELECTED)
    {
    reset = ANSI_COLOR(0;7);
    doReset = 1;
    outs(reset);
    }
    else if (attr & EOATTR_MOVIECODE)
    {
    reset = ANSI_COLOR(0;36);
    doReset = 1;
    outs(reset);
#ifdef ENABLE_PMORE_ASCII_MOVIE_SYNTAX
    syn_pmore_render((char*)text, n, movie_attrs);
#endif
    }
    else if (attr & EOATTR_BBSLUA)
    {
    reset = ANSI_COLOR(0;1;31);
    doReset = 1;
    outs(reset);
    }
    else if (attr & EOATTR_COMMENT)
    {
    reset = ANSI_COLOR(0;1;34);
    doReset = 1;
    outs(reset);
    }

#ifdef DBCSAWARE
    /* 0 = N/A, 1 = leading byte printed, 2 = ansi in middle */
    register unsigned char isDBCS = 0;
#endif

    while ((ch = *text++) && (++column < t_columns) && n-- > 0)
    {
#ifdef ENABLE_PMORE_ASCII_MOVIE_SYNTAX
    mattr = *pmattr++;
#endif

    if(inAnsi == 1)
    {
        if(ch == ESC_CHR)
        outc('*');
        else
        {
        outc(ch);

        if(!ANSI_IN_ESCAPE(ch))
        {
            inAnsi = 0;
            outs(reset);
        }
        }

    } 
    else if(ch == ESC_CHR)
    {
        inAnsi = 1;
#ifdef DBCSAWARE
        if(isDBCS == 1)
        {
        isDBCS = 2;
        outs(ANSI_COLOR(1;33) "?");
        outs(reset);
        }
#endif
        outs(ANSI_COLOR(1) "*");
    }
    else
    {
#ifdef DBCSAWARE
        if(isDBCS == 1)
        isDBCS = 0;
        else if (isDBCS == 2)
        {
        /* ansi in middle. */
        outs(ANSI_COLOR(0;33) "?");
        outs(reset);
        isDBCS = 0;
        continue;
        }
        else
        if(IS_BIG5_HI(ch))
        {
            isDBCS = 1;
            // peak next char
            if(n > 0 && *text == ESC_CHR)
            continue;
        }
#endif

        // Lua Parser!
        if (!attr && curr_buf->synparser && !fComment)
        {
        // syntax highlight!
        if (fSquareQuote) {
            if (ch == ']' && n > 0 && *(text) == ']')
            {
            fSquareQuote = 0;
            doReset = 0;
            // directly print quotes
            outc(ch); outc(ch);
            text++, n--;
            outs(ANSI_RESET);
            continue;
            }
        } else if (fSingleQuote) {
            if (ch == '\'')
            {
            fSingleQuote = 0;
            doReset = 0;
            // directly print quotes
            outc(ch);
            outs(ANSI_RESET);
            continue;
            }
        } else if (fDoubleQuote) {
            if (ch == '"')
            {
            fDoubleQuote = 0;
            doReset = 0;
            // directly print quotes
            outc(ch);
            outs(ANSI_RESET);
            continue;
            }
        } else if (ch == '-' && n > 0 && *(text) == '-') {
            fComment = 1;
            doReset = 1;
            outs(ANSI_COLOR(0;1;34)); 
        } else if (ch == '[' && n > 0 && *(text) == '[') {
            fSquareQuote = 1;
            doReset = 1;
            fWord = 0;
            outs(ANSI_COLOR(1;35));
        } else if (ch == '\'' || ch == '"') {
            if (ch == '"')
            fDoubleQuote = 1;
            else
            fSingleQuote = 1;
            doReset = 1;
            fWord = 0;
            outs(ANSI_COLOR(1;35));
        } else {
            // normal words
            if (fWord)
            {
            // inside a word.
            if (--fWord <= 0){
                fWord = 0;
                doReset = 0;
                outc(ch);
                outs(ANSI_RESET);
                continue;
            }
            } else if (isalnum(tolower(ch)) || ch == '#') {
            char attr[] = ANSI_COLOR(0;1;37);
            int x = synLuaKeyword(text-1, n+1, &fWord);
            if (fWord > 0)
                fWord --;
            if (x != 0)
            {
                // sorry, fixed string here.
                // 7 = *[0;1;3?
                if (x<0) {  attr[4] = '0'; x= -x; }
                attr[7] = '0' + x;
                prints(attr);
                doReset = 1;
            }
            if (!fWord)
            {
                outc(ch);
                outs(ANSI_RESET);
                doReset = 0;
                continue;
            }
            }
        }
        }
        outc(ch);

#ifdef ENABLE_PMORE_ASCII_MOVIE_SYNTAX
        // pmore Movie Parser!
        if (attr & EOATTR_MOVIECODE)
        {
        // only render when attribute was changed.
        if (mattr != *pmattr)
        {
            if (*pmattr)
            {
            prints(ANSI_COLOR(1;3%d),
                8 - ((mattr-1) % 7+1) );
            } else {
            outs(ANSI_RESET);
            }
        }
        }
#endif // ENABLE_PMORE_ASCII_MOVIE_SYNTAX
    }
    } 

    // this must be ANSI_RESET, not "reset".
    if(inAnsi || doReset)
    outs(ANSI_RESET);
}

static void
edit_outs_attr(const char *text, int attr)
{
    edit_outs_attr_n(text, scr_cols, attr);
}

static void
edit_ansi_outs_n(const char *str, int n, int attr)
{
    char c;
    while (n-- > 0 && (c = *str++)) {
    if(c == ESC_CHR && *str == '*')
    {
        // ptt prints
        /* Because moving within ptt_prints is too hard
         * let's just display it as-is.
         */
        outc('*');
    } else {
        outc(c);
    }
    }
}

static void
edit_ansi_outs(const char *str, int attr)
{
    return edit_ansi_outs_n(str, strlen(str), attr);
}

static int 
detect_attr(const char *ps, size_t len)
{
    int attr = 0;

#ifdef PMORE_USE_ASCII_MOVIE
    if (mf_movieFrameHeader((unsigned char*)ps, (unsigned char*)ps+len))
    attr |= EOATTR_MOVIECODE;
#endif
#ifdef USE_BBSLUA
    if (bbslua_isHeader(ps, ps + len))
    {
    attr |= EOATTR_BBSLUA;
    if (!curr_buf->synparser)
    {
        curr_buf->synparser = 1;
        // if you need indent, toggle by hotkey.
        // enabling indent by default may cause trouble to copy pasters
        // curr_buf->indent_mode = 1;
    }
    }
#endif
    return attr;
}

static inline void
display_textline_internal(textline_t *p, int i)
{
    short tmp;
    void (*output)(const char *, int)       = edit_outs_attr;
    void (*output_n)(const char *, int, int)= edit_outs_attr_n;

    int attr = EOATTR_NORMAL;

    move(i, 0);
    clrtoeol();

    if (!p) {
    outc('~');
    outs(ANSI_CLRTOEND);
    return;
    }

    if (curr_buf->ansimode) {
    output = edit_ansi_outs;
    output_n = edit_ansi_outs_n;
    }

    tmp = curr_buf->currln - curr_buf->curr_window_line + i;

    // parse attribute of line 
    
    // selected attribute?
    if (has_block_selection() && 
        ( (curr_buf->blockln <= curr_buf->currln &&
           curr_buf->blockln <= tmp && tmp <= curr_buf->currln) ||
          (curr_buf->currln <= tmp && tmp <= curr_buf->blockln)) ) 
    {
    // outs(ANSI_REVERSE); // remove me when EOATTR is ready...
    attr |= EOATTR_SELECTED;
    }

    attr |= detect_attr(p->data, p->len);

#ifdef DBCSAWARE
    if(mbcs_mode && curr_buf->edit_margin > 0)
    {
    if(curr_buf->edit_margin >= p->len)
    {
        (*output)("", attr);
    } else {

        int newpnt = curr_buf->edit_margin;
        unsigned char *pdata = (unsigned char*)
        (&p->data[0] + curr_buf->edit_margin);

        if(mbcs_mode)
        newpnt = fix_cursor(p->data, newpnt, FC_LEFT);

        if(newpnt == curr_buf->edit_margin-1)
        {
        /* this should be always 'outs'? */
        // (*output)(ANSI_COLOR(1) "<" ANSI_RESET);
        outs(ANSI_COLOR(1) "<" ANSI_RESET);
        pdata++;
        }
        (*output)((char*)pdata, attr);
    }

    } else
#endif
    (*output)((curr_buf->edit_margin < p->len) ? 
        &p->data[curr_buf->edit_margin] : "", attr);

    if (attr)
    outs(ANSI_RESET);

    // workaround poor terminal
    outs(ANSI_CLRTOEND);
}

static void
refresh_window(void)
{
    register textline_t *p;
    register int    i;

    for (p = curr_buf->top_of_win, i = 0; i < visible_window_height(); i++) {
    display_textline_internal(p, i);

    if (p)
        p = p->next;
    }
    edit_msg();
}

static void
goto_line(int lino)
{
    if (lino > 0 && lino <= curr_buf->totaln + 1) {
    textline_t     *p;

    p = curr_buf->firstline;
    curr_buf->currln = lino - 1;

    while (--lino && p->next)
        p = p->next;

    if (p)
        curr_buf->currline = p;
    else {
        curr_buf->currln = curr_buf->totaln;
        curr_buf->currline = curr_buf->lastline;
    }

    curr_buf->currpnt = 0;

    edit_window_adjust_middle();
    }
    curr_buf->redraw_everything = YEA;
}

static void
prompt_goto_line(void)
{
    char buf[10];

    if (getdata(b_lines - 1, 0, "跳至第幾行:", buf, sizeof(buf), DOECHO))
    goto_line(atoi(buf));
}

/**
 * search string interactively.
 * @param mode 0: prompt
 *             1: forward
 *            -1: backward
 */
static void
search_str(int mode)
{
    const int max_keyword = 65;
    char *str;
    char            ans[4] = "n";

    if (curr_buf->searched_string == NULL) {
    if (mode != 0)
        return;
    curr_buf->searched_string = (char *)malloc(max_keyword * sizeof(char));
    curr_buf->searched_string[0] = 0;
    }

    str = curr_buf->searched_string;

    if (!mode) {
    if (getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:",
            str, max_keyword, DOECHO))
        if (*str) {
        if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
                ans, sizeof(ans), LCECHO) && *ans == 'y')
            curr_buf->substr_fp = strstr;
        else
            curr_buf->substr_fp = strcasestr;
        }
    }
    if (*str && *ans != 'q') {
    textline_t     *p;
    char           *pos = NULL;
    int             lino;
    bool found = false;

    if (mode >= 0) {
        for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->next, lino++) {
        int offset = (lino == curr_buf->currln ? MIN(curr_buf->currpnt + 1, curr_buf->currline->len) : 0);
        pos = (*curr_buf->substr_fp)(p->data + offset, str);
        if (pos) {
            found = true;
            break;
        }
        }
    } else {
        for (lino = curr_buf->currln, p = curr_buf->currline; p; p = p->prev, lino--) {
        pos = (*curr_buf->substr_fp)(p->data, str);
        if (pos &&
            (lino != curr_buf->currln || pos - p->data < curr_buf->currpnt)) {
            found = true;
            break;
        }
        }
    }
    if (found) {
        /* move window */
        curr_buf->currline = p;
        curr_buf->currln = lino;
        curr_buf->currpnt = pos - p->data;

        edit_window_adjust_middle();
    }
    }
    if (!mode)
    curr_buf->redraw_everything = YEA;
}

/**
 * move the cursor from bracket to corresponding bracket.
 */
static void
match_paren(void)
{
    char           *parens = "()[]{}";
    char           *ptype;
    textline_t     *p;
    int             lino;
    int             i = 0;
    bool found = false;
    char findch;
    char quotech = '\0';
    enum MatchState {
    MATCH_STATE_NORMAL,
    MATCH_STATE_C_COMMENT,
    MATCH_STATE_QUOTE
    };
    enum MatchState state = MATCH_STATE_NORMAL;
    int dir;
    int nested = 0;

    char cursorch = curr_buf->currline->data[curr_buf->currpnt];
    if (!(ptype = strchr(parens, cursorch)))
    return;

    dir = (ptype - parens) % 2 == 0 ? 1 : -1;
    findch = *(ptype + dir);

    p = curr_buf->currline;
    lino = curr_buf->currln;
    i = curr_buf->currpnt;
    while (p && !found) {
    // next position
    i += dir;
    while (p && i < 0) {
        p = p->prev;
        if (p)
        i = p->len - 1;
        lino--;
    } 
    while (p && i >= p->len) {
        p = p->next;
        i = 0;
        lino++;
    }
    if (!p)
        break;
    assert(0 <= i && i < p->len);

    // match char
    switch (state) {
        case MATCH_STATE_NORMAL:
        if (nested == 0 && p->data[i] == findch) {
            found = true;
            break;
        }
        if (p->data[i] == cursorch)
            nested++;
        else if (p->data[i] == findch)
            nested--;
        if (p->data[i] == '\'' || p->data[i] == '"') {
            quotech = p->data[i];
            state = MATCH_STATE_QUOTE;
        } else if ((i+dir) >= 0 && p->data[i] == '/' && p->data[i+dir] == '*') {
            state = MATCH_STATE_C_COMMENT;
            i += dir;
        }
        break;
        case MATCH_STATE_C_COMMENT:
        if ((i+dir) >= 0 && p->data[i] == '*' && p->data[i+dir] == '/') {
            state = MATCH_STATE_NORMAL;
            i += dir;
        }
        break;
        case MATCH_STATE_QUOTE:
        if (p->data[i] == quotech) {
            if (i==0 || p->data[i-1] != '\\')
            state = MATCH_STATE_NORMAL;
        }
        break;
    }
    }
    if (found) {
    int             top = curr_buf->currln - curr_buf->curr_window_line;
    int             bottom = top + visible_window_height() - 1;

    assert(p);
    curr_buf->currpnt = i;
    curr_buf->currline = p;
    curr_buf->curr_window_line += lino - curr_buf->currln;
    curr_buf->currln = lino;

    if (lino < top || lino > bottom) {
        edit_window_adjust_middle();
    }
    }
}

static void
currline_shift_left(void)
{
    int currpnt0;

    if (curr_buf->currline->len <= 0)
    return;

    currpnt0 = curr_buf->currpnt;
    curr_buf->currpnt = 0;
    delete_char();
    curr_buf->currpnt = (currpnt0 <= curr_buf->currline->len) ? currpnt0 : currpnt0 - 1;
    if (curr_buf->ansimode)
    curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, curr_buf->currline), curr_buf->currline);
}

static void
currline_shift_right(void)
{
    int currpnt0;

    if (curr_buf->currline->len >= WRAPMARGIN - 1)
    return;

    currpnt0 = curr_buf->currpnt;
    curr_buf->currpnt = 0;
    insert_char(' ');
    curr_buf->currpnt = currpnt0;
}

static void
cursor_to_next_word(void)
{
    while (curr_buf->currpnt < curr_buf->currline->len &&
        isalnum((int)curr_buf->currline->data[++curr_buf->currpnt]));
    while (curr_buf->currpnt < curr_buf->currline->len &&
        isspace((int)curr_buf->currline->data[++curr_buf->currpnt]));
}

static void
cursor_to_prev_word(void)
{
    while (curr_buf->currpnt && isspace((int)curr_buf->currline->data[--curr_buf->currpnt]));
    while (curr_buf->currpnt && isalnum((int)curr_buf->currline->data[--curr_buf->currpnt]));
    if (curr_buf->currpnt > 0)
    curr_buf->currpnt++;
}

static void
delete_current_word(void)
{
    while (curr_buf->currpnt < curr_buf->currline->len) {
    delete_char();
    if (!isalnum((int)curr_buf->currline->data[curr_buf->currpnt]))
        break;
    }
    while (curr_buf->currpnt < curr_buf->currline->len) {
    delete_char();
    if (!isspace((int)curr_buf->currline->data[curr_buf->currpnt]))
        break;
    }
}

/**
 * transform every "*[" in given string to KEY_ESC "["
 */
static void
transform_to_color(char *line)
{
    while (line[0] && line[1])
    if (line[0] == '*' && line[1] == '[') {
        line[0] = KEY_ESC;
        line += 2;
    } else
        ++line;
}

static void
block_color(void)
{
    textline_t     *begin, *end, *p;

    setup_block_begin_end(&begin, &end);

    p = begin;
    while (1) {
    assert(p);
    transform_to_color(p->data);
    if (p == end)
        break;
    else
        p = p->next;
    }
    block_cancel();
}

/**
 * insert ansi code
 */
static void
insert_ansi_code(void)
{
    int ch = curr_buf->insert_mode;
    curr_buf->insert_mode = curr_buf->redraw_everything = YEA;
    if (!curr_buf->ansimode)
    insert_string(ANSI_RESET);
    else {
    char            ans[4];
    move(b_lines - 2, 55);
    outs(ANSI_COLOR(1;33;40) "B" ANSI_COLOR(41) "R" ANSI_COLOR(42) "G" ANSI_COLOR(43) "Y" ANSI_COLOR(44) "L"
        ANSI_COLOR(45) "P" ANSI_COLOR(46) "C" ANSI_COLOR(47) "W" ANSI_RESET);
    if (getdata(b_lines - 1, 0,
            "請輸入  亮度/前景/背景[正常白字黑底][0wb]:",
            ans, sizeof(ans), LCECHO))
    {
        const char      t[] = "BRGYLPCW";
        char            color[15];
        char           *tmp, *apos = ans;
        int             fg, bg;

        strcpy(color, ESC_STR "[");
        if (isdigit((int)*apos)) {
        sprintf(color,"%s%c", color, *(apos++)); 
        if (*apos)
            strcat(color, ";");
        }
        if (*apos) {
        if ((tmp = strchr(t, toupper(*(apos++)))))
            fg = tmp - t + 30;
        else
            fg = 37;
        sprintf(color, "%s%d", color, fg);
        }
        if (*apos) {
        if ((tmp = strchr(t, toupper(*(apos++)))))
            bg = tmp - t + 40;
        else
            bg = 40;
        sprintf(color, "%s;%d", color, bg);
        }
        strcat(color, "m");
        insert_string(color);
    } else
            insert_string(ANSI_RESET);
    }
    curr_buf->insert_mode = ch;
}

static inline void
phone_mode_switch(void)
{
    if (curr_buf->phone_mode)
    curr_buf->phone_mode = 0;
    else {
    curr_buf->phone_mode = 1;
    if (!curr_buf->last_phone_mode)
        curr_buf->last_phone_mode = 2;
    }
}

/**
 * return coresponding phone char of given key c
 */
static const char* 
phone_char(char c)
{
    if (curr_buf->last_phone_mode > 0 && curr_buf->last_phone_mode < 20) {
    if (tolower(c)<'a'||(tolower(c)-'a') >= strlen(BIG5[curr_buf->last_phone_mode - 1]) / 2)
        return 0;
    return BIG5[curr_buf->last_phone_mode - 1] + (tolower(c) - 'a') * 2;
    }
    else if (curr_buf->last_phone_mode >= 20) {
    if (c == '.') c = '/';

    if (c < '/' || c > '9')
        return 0;

    return table[curr_buf->last_phone_mode - 20] + (c - '/') * 2;
    }
    return 0;
}

/**
 * When get the key for phone mode, handle it (e.g. edit_msg) and return the
 * key.  Otherwise return 0.
 */
static inline char
phone_mode_filter(char ch)
{
    if (!curr_buf->phone_mode)
    return 0;

    switch (ch) {
    case 'z':
    case 'Z':
        if (curr_buf->last_phone_mode < 20)
        curr_buf->last_phone_mode = 20;
        else
        curr_buf->last_phone_mode = 2;
        edit_msg();
        return ch;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        if (curr_buf->last_phone_mode < 20) {
        curr_buf->last_phone_mode = ch - '0' + 1;
        curr_buf->redraw_everything = YEA;
        return ch;
        }
        break;
    case '-':
        if (curr_buf->last_phone_mode < 20) {
        curr_buf->last_phone_mode = 11;
        curr_buf->redraw_everything = YEA;
        return ch;
        }
        break;
    case '=':
        if (curr_buf->last_phone_mode < 20) {
        curr_buf->last_phone_mode = 12;
        curr_buf->redraw_everything = YEA;
        return ch;
        }
        break;
    case '`':
        if (curr_buf->last_phone_mode < 20) {
        curr_buf->last_phone_mode = 13;
        curr_buf->redraw_everything = YEA;
        return ch;
        }
        break;
    case '/':
        if (curr_buf->last_phone_mode >= 20) {
        curr_buf->last_phone_mode += 4;
        if (curr_buf->last_phone_mode > 27)
            curr_buf->last_phone_mode -= 8;
        curr_buf->redraw_everything = YEA;
        return ch;
        }
        break;
    case '*':
        if (curr_buf->last_phone_mode >= 20) {
        curr_buf->last_phone_mode++;
        if ((curr_buf->last_phone_mode - 21) % 4 == 3)
            curr_buf->last_phone_mode -= 4;
        curr_buf->redraw_everything = YEA;
        return ch;
        }
        break;
    }

    return 0;
}

#ifdef EXP_EDIT_UPLOAD

static void
upload_file(void)
{
    size_t szdata = 0;
    int c = 1;
    char promptmsg = 0;

    clear();
    block_cancel();
    vs_hdr("上傳文字檔案");
    move(3,0);
    outs("利用本服務您可以上傳較大的文字檔 (但不計入稿費)。\n"
     "\n"
     "上傳期間您打的字暫時不會出現在螢幕上,除了 Ctrl-U 會被轉換為 ANSI \n"
     "控制碼的 ESC 外,其它特殊鍵一律沒有作用。\n"
     "\n"
     "請在您的電腦本機端複製好內容後貼上即可開始傳送。\n");

    do {
    if (!num_in_buf())
    {
        move(10, 0); clrtobot();
        prints("\n\n資料接收中... %u 位元組。\n", (unsigned int)szdata);
        outs(ANSI_COLOR(1) 
            "◆全部完成後按下 End 或 ^X/^Q/^C 即可回到編輯畫面。"
            ANSI_RESET "\n");
        promptmsg = 0;
    }

    c = igetch();
    if (c < 0x100 && isprint2(c))
    {
        insert_char(c);
        szdata ++;
    }
    else if (c == Ctrl('U') || c == ESC_CHR)
    {
        insert_char(ESC_CHR);
        szdata ++;
    }
    else if (c == Ctrl('I'))
    {
        insert_tab();
        szdata ++;
    }
    else if (c == KEY_ENTER)
    {
        split(curr_buf->currline, curr_buf->currpnt);
        curr_buf->oldcurrline = curr_buf->currline;
        szdata ++;
        promptmsg = 1;
    }

    if (!promptmsg)
        promptmsg = (szdata && szdata % 1024 == 0);

    // all other keys are ignored.
    } while (c != KEY_END && c != Ctrl('X') && 
         c != Ctrl('C') && c != Ctrl('Q') &&
         curr_buf->totaln <= EDIT_LINE_LIMIT &&
         szdata <= EDIT_SIZE_LIMIT);

    move(12, 0);
    prints("傳送結束: 收到 %u 位元組。", (unsigned int)szdata);
    vmsgf("回到編輯畫面");
}

#endif // EXP_EDIT_UPLOAD


/** 編輯處理:主程式、鍵盤處理
 * @param   title       NULL, 否則長度 STRLEN
 * @return  EDIT_ABORTED    abort
 *      >= 0        編輯錢數
 * 由於各處都以 == EDIT_ABORTED 判斷, 若想傳回其他負值要注意
 */
int
vedit2(const char *fpath, int saveheader, int *islocal, char title[STRLEN], int flags)
{
    char            last = 0;   /* the last key you press */
    int             ch, tmp;

    int             mode0 = currutmp->mode;
    int             destuid0 = currutmp->destuid;
    int             money = 0, entropy = 0;
    int             interval = 0;
    time4_t         th = now;
    int             count = 0, tin = 0, quoted = 0;
    char            trans_buffer[256];

    STATINC(STAT_VEDIT);
    currutmp->mode = EDITING;
    currutmp->destuid = currstat;

#ifdef DBCSAWARE
    mbcs_mode = (HasUserFlag(UF_DBCSAWARE)) ? 1 : 0;
#endif

    enter_edit_buffer();

    if (*fpath) {
    read_file(fpath, (flags & EDITFLAG_TEXTONLY) ? 1 : 0);
    }

    if (*quote_file) {
    do_quote();
    *quote_file = '\0';
    quoted = 1;
    }

    if( curr_buf->oldcurrline != curr_buf->firstline || 
    curr_buf->currline != curr_buf->firstline) {
    /* we must adjust because cursor (currentline) moved. */
    curr_buf->oldcurrline = curr_buf->currline = curr_buf->top_of_win =
           curr_buf->firstline= adjustline(curr_buf->firstline, WRAPMARGIN);
    }

    /* No matter you quote or not, just start the cursor from (0,0) */
    curr_buf->currpnt = curr_buf->currln = curr_buf->curr_window_line = 
    curr_buf->edit_margin = curr_buf->last_margin = 0;

    /* if quote, move to end of file. */
    if(quoted)
    {
    /* maybe do this in future. */
    }

    while (1) {
    edit_check_healthy();

    if (curr_buf->redraw_everything || has_block_selection()) {
        refresh_window();
        curr_buf->redraw_everything = NA;
    }
    if( curr_buf->oldcurrline != curr_buf->currline ){
        curr_buf->oldcurrline = adjustline(curr_buf->oldcurrline, curr_buf->oldcurrline->len);
        curr_buf->oldcurrline = curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
    }

    if (curr_buf->ansimode)
        ch = n2ansi(curr_buf->currpnt, curr_buf->currline);
    else
        ch = curr_buf->currpnt - curr_buf->edit_margin;
    move(curr_buf->curr_window_line, ch);

    ch = igetch();
    /* jochang debug */
    if ((interval = (now - th))) {
        th = now;
        if ((char)ch != last) {
        money++;
        last = (char)ch;
        }
    }
    if (interval && interval == tin)
          {  // Ptt : +- 1 秒也算
        count++;
            if(count>60) 
            {
             money = 0;
             count = 0;
/*
             log_file("etc/illegal_money",  LOG_CREAT | LOG_VF,
             ANSI_COLOR(1;33;46) "%s " ANSI_COLOR(37;45) " 用機器人發表文章 " ANSI_COLOR(37) " %s" ANSI_RESET "\n",
             cuser.userid, Cdate(&now));
             post_violatelaw(cuser.userid, BBSMNAME "系統警察", 
                 "用機器人發表文章", "強制離站");
             abort_bbs(0);
*/
            }
          }
    else if(interval){
        count = 0;
        tin = interval;
    }
#ifndef DBCSAWARE
    /* this is almost useless! */
    if (curr_buf->raw_mode) {
        switch (ch) {
        case Ctrl('S'):
        case Ctrl('Q'):
        case Ctrl('T'):
        continue;
        }
    }
#endif

    if (phone_mode_filter(ch))
        continue;

    if (ch < 0x100 && isprint2(ch)) {
        const char *pstr;
            if(curr_buf->phone_mode && (pstr=phone_char(ch)))
        insert_dchar(pstr); 
        else
        insert_char(ch);
        curr_buf->lastindent = -1;
    } else {
        if (ch == KEY_UP || ch == KEY_DOWN ){
        if (curr_buf->lastindent == -1)
            curr_buf->lastindent = curr_buf->currpnt;
        } else
        curr_buf->lastindent = -1;
        if (ch == KEY_ESC)
        switch (KEY_ESC_arg) {
        case ',':
            ch = Ctrl(']');
            break;
        case '.':
            ch = Ctrl('T');
            break;
        case 'v':
            ch = KEY_PGUP;
            break;
        case 'a':
        case 'A':
            ch = Ctrl('V');
            break;
        case 'X':
            ch = Ctrl('X');
            break;
        case 'q':
            ch = Ctrl('Q');
            break;
        case 'o':
            ch = Ctrl('O');
            break;
        case 's':
            ch = Ctrl('S');
            break;
        }

        switch (ch) {
        case KEY_F10:
        case Ctrl('X'): /* Save and exit */
        block_cancel();
        tmp = write_file(fpath, saveheader, islocal, title, 
            (flags & EDITFLAG_UPLOAD) ? 1 : 0,
            (flags & EDITFLAG_ALLOWTITLE) ? 1 : 0,
            &entropy);
        if (tmp != KEEP_EDITING) {
            currutmp->mode = mode0;
            currutmp->destuid = destuid0;

            exit_edit_buffer();

            // adjust final money
            money *= POST_MONEY_RATIO;
            // money or entropy?
            if (money > (entropy * ENTROPY_RATIO) && entropy >= 0)
            money = (entropy * ENTROPY_RATIO) + 1;

            if (!tmp)
            return money;
            else
            return tmp;
        }
        curr_buf->oldcurrline = curr_buf->currline;
        curr_buf->redraw_everything = YEA;
        break;
        case KEY_F5:
        prompt_goto_line();
        curr_buf->redraw_everything = YEA;
        break;
        case KEY_F8:
        t_users();
        curr_buf->redraw_everything = YEA;
        break;
        case Ctrl('W'):
        block_cut();
        break;
        case Ctrl('Q'): /* Quit without saving */
        grayout(0, b_lines-1, GRAYOUT_DARK);
        ch = vmsg("結束但不儲存 [y/N]? ");
        if (ch == 'y' || ch == 'Y') {
            currutmp->mode = mode0;
            currutmp->destuid = destuid0;
            exit_edit_buffer();
            return -1;
        }
        curr_buf->redraw_everything = YEA;
        break;
        case Ctrl('C'):
        insert_ansi_code();
        break;
        case KEY_ESC:
        switch (KEY_ESC_arg) {
        case 'U':
            t_users();
            curr_buf->redraw_everything = YEA;
            break;
        case 'i':
            t_idle();
            curr_buf->redraw_everything = YEA;
            break;
        case 'n':
            search_str(1);
            break;
        case 'p':
            search_str(-1);
            break;
        case 'L':
        case 'J':
            prompt_goto_line();
            curr_buf->redraw_everything = YEA;
            break;
        case ']':
            match_paren();
            break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            read_tmpbuf(KEY_ESC_arg - '0');
            curr_buf->oldcurrline = curr_buf->currline;
            curr_buf->redraw_everything = YEA;
            break;
        case 'l':   /* block delete */
        case ' ':
            if (has_block_selection())
            block_prompt();
            else
            block_select();
            break;
        case 'u':
            block_cancel();
            break;
        case 'c':
            block_copy();
            break;
        case 'y':
            curr_buf->oldcurrline = undelete_line();
            if (curr_buf->oldcurrline == NULL)
            curr_buf->oldcurrline = curr_buf->currline;
            break;
        case 'R':
#ifdef DBCSAWARE
        case 'r':
            mbcs_mode =! mbcs_mode;
#endif
            curr_buf->raw_mode ^= 1;
            break;
        case 'I':
            curr_buf->indent_mode ^= 1;
            break;
        case 'j':
            currline_shift_left();
            break;
        case 'k':
            currline_shift_right();
            break;
        case 'f':
            cursor_to_next_word();
            break;
        case 'b':
            cursor_to_prev_word();
            break;
        case 'd':
            delete_current_word();
            break;
        }
        break;
        case Ctrl('S'):
        case KEY_F3:
        search_str(0);
        break;
        case Ctrl('U'):
        insert_char(ESC_CHR);
        break;
        case Ctrl('V'): /* Toggle ANSI color */
        curr_buf->ansimode ^= 1;
        if (curr_buf->ansimode && has_block_selection())
            block_color();
        clear();
        curr_buf->redraw_everything = YEA;
        break;
        case Ctrl('I'):
        insert_tab();
        break;
        case KEY_ENTER:
        block_cancel();
        if (curr_buf->totaln >= EDIT_LINE_LIMIT)
        {
            vmsg("檔案已超過最大限制,無法再增加行數。");
            break;
        }

#ifdef MAX_EDIT_LINE
        if(curr_buf->totaln == 
            ((flags & EDITFLAG_ALLOWLARGE) ? 
             MAX_EDIT_LINE_LARGE : MAX_EDIT_LINE))
        {
            vmsg("已到達最大行數限制。");
            break;
        }
#endif
        split(curr_buf->currline, curr_buf->currpnt);
        curr_buf->oldcurrline = curr_buf->currline;
        break;
        case Ctrl('G'):
        {
            unsigned int    currstat0 = currstat;
            int mode0 = currutmp->mode;
            setutmpmode(EDITEXP);
            a_menu("編輯輔助器", "etc/editexp",
               (HasUserPerm(PERM_SYSOP) ? SYSOP : NOBODY),
               0,
               trans_buffer);
            currstat = currstat0;
            currutmp->mode = mode0;
        }
        if (trans_buffer[0]) {
            FILE *fp1;
            if ((fp1 = fopen(trans_buffer, "r"))) {
            int indent_mode0 = curr_buf->indent_mode;
                char buf[WRAPMARGIN + 2];

            curr_buf->indent_mode = 0;
            while (fgets(buf, sizeof(buf), fp1)) {
                if (!strncmp(buf, "作者:", 5) ||
                !strncmp(buf, "標題:", 5) ||
                !strncmp(buf, "時間:", 5))
                continue;
                insert_string(buf);
            }
            fclose(fp1);
            curr_buf->indent_mode = indent_mode0;
            edit_window_adjust();
            }
        }
        curr_buf->redraw_everything = YEA;
        break;
            case Ctrl('P'):
        phone_mode_switch();
                curr_buf->redraw_everything = YEA;
        break;

        case KEY_F1:
        case Ctrl('Z'): /* Help */
        more("etc/ve.hlp", YEA);
        curr_buf->redraw_everything = YEA;
        break;
        case Ctrl('L'):
        clear();
        curr_buf->redraw_everything = YEA;
        break;
        case KEY_LEFT:
        if (curr_buf->currpnt) {
            if (curr_buf->ansimode)
            curr_buf->currpnt = n2ansi(curr_buf->currpnt, curr_buf->currline);
            curr_buf->currpnt--;
            if (curr_buf->ansimode)
            curr_buf->currpnt = ansi2n(curr_buf->currpnt, curr_buf->currline);
#ifdef DBCSAWARE
            if(mbcs_mode)
              curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_LEFT);
#endif
        } else if (curr_buf->currline->prev) {
            curr_buf->curr_window_line--;
            curr_buf->currln--;
            curr_buf->currline = curr_buf->currline->prev;
            curr_buf->currpnt = curr_buf->currline->len;
        }
        break;
        case KEY_RIGHT:
        if (curr_buf->currline->len != curr_buf->currpnt) {
            if (curr_buf->ansimode)
            curr_buf->currpnt = n2ansi(curr_buf->currpnt, curr_buf->currline);
            curr_buf->currpnt++;
            if (curr_buf->ansimode)
            curr_buf->currpnt = ansi2n(curr_buf->currpnt, curr_buf->currline);
#ifdef DBCSAWARE
            if(mbcs_mode)
              curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_RIGHT);
#endif
        } else if (curr_buf->currline->next) {
            curr_buf->currpnt = 0;
            curr_buf->curr_window_line++;
            curr_buf->currln++;
            curr_buf->currline = curr_buf->currline->next;
        }
        break;
        case KEY_UP:
        cursor_to_prev_line();
        break;
        case KEY_DOWN:
        cursor_to_next_line();
        break;

        case Ctrl('B'):
        case KEY_PGUP:
        curr_buf->top_of_win = back_line(curr_buf->top_of_win, visible_window_height() - 1, false);
        curr_buf->currline = back_line(curr_buf->currline, visible_window_height() - 1, true);
        curr_buf->curr_window_line = get_lineno_in_window();
        if (curr_buf->currpnt > curr_buf->currline->len)
            curr_buf->currpnt = curr_buf->currline->len;
        curr_buf->redraw_everything = YEA;
        break;

        case Ctrl('F'):
        case KEY_PGDN:
        curr_buf->top_of_win = forward_line(curr_buf->top_of_win, visible_window_height() - 1, false);
        curr_buf->currline = forward_line(curr_buf->currline, visible_window_height() - 1, true);
        curr_buf->curr_window_line = get_lineno_in_window();
        if (curr_buf->currpnt > curr_buf->currline->len)
            curr_buf->currpnt = curr_buf->currline->len;
        curr_buf->redraw_everything = YEA;
        break;

        case KEY_END:
        case Ctrl('E'):
        curr_buf->currpnt = curr_buf->currline->len;
        break;
        case Ctrl(']'): /* start of file */
        curr_buf->currline = curr_buf->top_of_win = curr_buf->firstline;
        curr_buf->currpnt = curr_buf->currln = curr_buf->curr_window_line = 0;
        curr_buf->redraw_everything = YEA;
        break;
        case Ctrl('T'): /* tail of file */
        curr_buf->top_of_win = back_line(curr_buf->lastline, visible_window_height() - 1, false);
        curr_buf->currline = curr_buf->lastline;
        curr_buf->curr_window_line = get_lineno_in_window();
        curr_buf->currln = curr_buf->totaln;
        curr_buf->redraw_everything = YEA;
        curr_buf->currpnt = 0;
        break;
        case KEY_HOME:
        case Ctrl('A'):
        curr_buf->currpnt = 0;
        break;
        case Ctrl('O'): // better not use ^O - UNIX not sending.
        case KEY_INS:   /* Toggle insert/overwrite */
        curr_buf->insert_mode ^= 1;
        break;
        case KEY_BS:
        case KEY_BS2:   /* backspace */
        block_cancel();
        if (curr_buf->ansimode) {
            curr_buf->ansimode = 0;
            clear();
            curr_buf->redraw_everything = YEA;
        } else {
            if (curr_buf->currpnt == 0) {
            if (!curr_buf->currline->prev)
                break;
            curr_buf->curr_window_line--;
            curr_buf->currln--;

            curr_buf->currline = adjustline(curr_buf->currline, curr_buf->currline->len);
            curr_buf->currline = curr_buf->currline->prev;
            curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
            curr_buf->oldcurrline = curr_buf->currline;

            curr_buf->currpnt = curr_buf->currline->len;
            curr_buf->redraw_everything = YEA;
            if (curr_buf->currline->next == curr_buf->top_of_win) {
                curr_buf->top_of_win = curr_buf->currline;
                curr_buf->curr_window_line = 0;
            }
            if (*next_non_space_char(curr_buf->currline->next->data) == '\0') {
                delete_line(curr_buf->currline->next, 0);
                break;
            }
            join(curr_buf->currline);
            break;
            }
#ifndef DBCSAWARE
            curr_buf->currpnt--;
            delete_char();
#else
            {
              int newpnt = curr_buf->currpnt - 1;

              if(mbcs_mode)
                newpnt = fix_cursor(curr_buf->currline->data, newpnt, FC_LEFT);

              for(; curr_buf->currpnt > newpnt;)
              {
                curr_buf->currpnt --;
                delete_char();
              }
            }
#endif
        }
        break;
        case Ctrl('D'):
        case KEY_DEL:   /* delete current character */
        block_cancel();
        if (curr_buf->currline->len == curr_buf->currpnt) {
            join(curr_buf->currline);
            curr_buf->redraw_everything = YEA;
        } else {
#ifndef DBCSAWARE
            delete_char();
#else
            {
              int w = 1;

              if(mbcs_mode)
                w = mchar_len((unsigned char*)(curr_buf->currline->data + curr_buf->currpnt));

              for(; w > 0; w --)
                delete_char();
            }
#endif
            if (curr_buf->ansimode)
            curr_buf->currpnt = ansi2n(n2ansi(curr_buf->currpnt, curr_buf->currline), curr_buf->currline);
        }
        break;
        case Ctrl('Y'): /* delete current line */
        curr_buf->currpnt = 0;
        case Ctrl('K'): /* delete to end of line */
        block_cancel();
        if (ch == Ctrl('Y') || curr_buf->currline->len == 0) {
            textline_t     *p = curr_buf->currline->next;
            if (!p) {
            p = curr_buf->currline->prev;
            if (!p) {
                curr_buf->currline->data[0] = 0;
                curr_buf->currline->len = 0;
                break;
            }
            if (curr_buf->curr_window_line > 0) {
                curr_buf->curr_window_line--;
            }
            curr_buf->currln--;
            }
            if (curr_buf->currline == curr_buf->top_of_win)
            curr_buf->top_of_win = p;

            delete_line(curr_buf->currline, 1);
            curr_buf->currline = p;
            curr_buf->redraw_everything = YEA;
            curr_buf->oldcurrline = curr_buf->currline = adjustline(curr_buf->currline, WRAPMARGIN);
            break;
        }
        else if (curr_buf->currline->len == curr_buf->currpnt) {
            join(curr_buf->currline);
            curr_buf->redraw_everything = YEA;
            break;
        }
        curr_buf->currline->len = curr_buf->currpnt;
        curr_buf->currline->data[curr_buf->currpnt] = '\0';
        break;
        }

        if (curr_buf->currln < 0)
        curr_buf->currln = 0;

        edit_window_adjust();
#ifdef DBCSAWARE        
        if(mbcs_mode)
          curr_buf->currpnt = fix_cursor(curr_buf->currline->data, curr_buf->currpnt, FC_LEFT);
#endif
    }

    if (curr_buf->ansimode)
        tmp = n2ansi(curr_buf->currpnt, curr_buf->currline);
    else
        tmp = curr_buf->currpnt;

    if (tmp < t_columns - 1)
        curr_buf->edit_margin = 0;
    else
        curr_buf->edit_margin = tmp / (t_columns - 8) * (t_columns - 8);

    if (!curr_buf->redraw_everything) {
        if (curr_buf->edit_margin != curr_buf->last_margin) {
        curr_buf->last_margin = curr_buf->edit_margin;
        curr_buf->redraw_everything = YEA;
        } else {
        move(curr_buf->curr_window_line, 0);
        clrtoeol();
        if (curr_buf->ansimode)
            outs(curr_buf->currline->data);
        else
        {
            int attr = EOATTR_NORMAL;
            attr |= detect_attr(curr_buf->currline->data, curr_buf->currline->len);
            edit_outs_attr(&curr_buf->currline->data[curr_buf->edit_margin], attr);
        }
        outs(ANSI_RESET ANSI_CLRTOEND);
        edit_msg();
        }
    } else {
        curr_buf->last_margin = curr_buf->edit_margin;
    }
    } /* main event loop */

    exit_edit_buffer();
}

int
vedit(const char *fpath, int saveheader, int *islocal, char title[STRLEN])
{
    assert(title);
    return vedit2(fpath, saveheader, islocal, title, EDITFLAG_ALLOWTITLE);
}

/**
 * 編輯一般檔案 (非看板文章/信件).
 *
 * 因此不會有 header, title, local save 等參數.
 */
int
veditfile(const char *fpath)
{
    return vedit2(fpath, NA, NULL, NULL, 0);
}

/* vim:sw=4:nofoldenable
 */