1 /*
2  * Copyright (c) 1993-2017 by Alexander V. Lukyanov (lav@yars.free.net)
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 /* edit.c : main editor loop */
20 
21 #include <config.h>
22 #include <stdio.h>
23 #include <ctype.h>
24 #include <fcntl.h>
25 #include <sys/stat.h>
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif
29 #include <stdlib.h>
30 #include <string.h>
31 #include <locale.h>
32 #include <time.h>
33 #include <errno.h>
34 #ifdef HAVE_LANGINFO_H
35 # include <langinfo.h>
36 #endif
37 #include "edit.h"
38 #include "calc.h"
39 #include "keymap.h"
40 #include "block.h"
41 #include "screen.h"
42 #include "options.h"
43 #include "about.h"
44 #include "search.h"
45 #include "undo.h"
46 #ifdef WITH_MOUSE
47 # include "mouse.h"
48 #endif
49 
50 #include <getopt.h>
51 
52 #ifdef DISABLE_FILE_LOCKS
53 # define fcntl(x,y,z)	(0)
54 #endif
55 
56 #ifdef HAVE__NC_FREE_AND_EXIT
57 extern "C" {
58 extern void _nc_free_and_exit(int);
59 #define ExitProgram(code) _nc_free_and_exit(code)
60 };
61 #else
62 #define ExitProgram(code) exit(code)
63 #endif
64 
65 #include <alloca.h>
66 #include "localcharset.h"
67 
68 char  Program[256];
69 
70 extern  const char    MainHelp[];
71 
72 char  BakPath[256]="";
73 
74 char  PosName[256]="";
75 char  HstName[256]="";
76 
77 int   SavePos=1;
78 int   SaveHst=1;
79 
80 int   View=FALSE;
81 
82 char  *HOME,*TERM,*DISPLAY;
83 
84 int   UseColor=1;
85 int   UseTabs=1;
86 int   IndentSize=8;
87 int   BackspaceUnindents=1;
88 int   PreferPageTop=1;
89 
GoToLineNum(num line_num)90 void  GoToLineNum(num line_num)
91 {
92    CurrentPos=TextPoint(line_num,0);
93    SetStdCol();
94 }
95 
96 History CodeHistory;
getcode(const char * prompt)97 long getcode(const char *prompt)
98 {
99    long  i;
100    static char ch[10];
101    static bool getcode_active=false;
102 
103    if(getcode_active)
104       return(-1);
105 
106    getcode_active=true;
107 
108    if(getstring(prompt,ch,sizeof(ch)-1,&CodeHistory)<1)
109    {
110       getcode_active=false;
111       return(-1);
112    }
113    getcode_active=false;
114    i=strtol(ch,0,0);
115    return((int)i);
116 }
getcode_char()117 int getcode_char()
118 {
119    long ch=getcode("Char: ");
120    if(ch<0 || ch>=256)
121       return -1;
122    return (int)ch;
123 }
124 #if USE_MULTIBYTE_CHARS
getcode_wchar()125 wchar_t getcode_wchar()
126 {
127    long ch=getcode("Wide Char: ");
128    if(ch<0)
129       return -1;
130    return (wchar_t)ch;
131 }
132 #endif
133 
ProcessDragMark()134 void ProcessDragMark()
135 {
136    if(CurrentPos<*DragMark)
137    {
138       if(CurrentPos!=BlockBegin || (Text && stdcol>GetCol()))
139       {
140 	 BlockEnd=*DragMark;
141 	 UserSetBlockBegin();
142       }
143    }
144    else if(CurrentPos>=*DragMark)
145    {
146       if(CurrentPos!=BlockEnd || (Text && stdcol>GetCol()))
147       {
148 	 BlockBegin=*DragMark;
149 	 UserSetBlockEnd();
150       }
151    }
152 }
153 
Edit()154 void    Edit()
155 {
156    int      key;
157    int      action;
158    ActionProc proc;
159    num	 old_num_of_lines=-1;
160 
161    while(1)
162    {
163       SeekStdCol();
164       if(hex)
165          flag|=REDISPLAY_LINE;
166 
167       if(!hex && !View && old_num_of_lines!=TextEnd.Line())
168       {
169          flag|=REDISPLAY_AFTER;
170          old_num_of_lines=TextEnd.Line();
171       }
172 
173       if(DragMark)
174 	 ProcessDragMark();
175 
176       ClearMessage();
177       SyncTextWin();
178       SetCursor();
179 
180       action=GetNextAction();
181 
182       if(action==WINDOW_RESIZE)
183       {
184 	 CorrectParameters();
185 	 flag|=REDISPLAY_ALL;
186 	 continue;
187       }
188 
189       if(hex)
190       {
191          ShowMatchPos=0;
192          RedisplayLine();
193          ShowMatchPos=1;
194       }
195 
196 #ifdef WITH_MOUSE
197       if(action==MOUSE_ACTION)
198       {
199 	 MEVENT mev;
200 	 if(getmouse(&mev)==ERR)
201 	    continue;
202 	 if(InTextWin(mev.y,mev.x))
203 	    MouseInTextWin(mev);
204 	 else if(InScrollBar(mev.y,mev.x))
205 	    MouseInScrollBar(mev);
206       }
207 #endif // WITH_MOUSE
208 
209       if(action==QUIT_EDITOR)
210       {
211          Quit();
212          continue;
213       }
214 
215       proc=GetActionProc(action);
216       if(proc)
217       {
218 	 undo.BeginUndoGroup();
219 	 proc();
220 	 undo.EndUndoGroup();
221 	 continue;
222       }
223       if(action!=NO_ACTION)
224 	 continue;
225       if(StringTypedLen>1)
226 	 continue;
227       key=(byte)(StringTyped[0]);
228       if(hex && key=='\t')
229       {
230 	 ascii=!ascii;
231 	 continue;
232       }
233       if(View)
234 	 continue;
235       if(!ascii && hex)
236       {
237 	 if(key<0 || key>255)
238 	    continue;
239 	 if(isdigit(key))
240 	    key-='0';
241 	 else
242 	 {
243 	    key=toupper(key);
244 	    if(key>='A' && key<='F')
245 	       key-='A'-0x0A;
246 	    else
247 	    {
248 	       UnrefKey(key);
249 	       continue;
250 	    }
251 	 }
252 	 undo.BeginUndoGroup();
253 	 if(((insert && !right) || Eof())
254 	 && !buffer_mmapped)
255 	 {
256 	    InsertChar(0);
257 	    MoveLeft();
258 	    flag|=REDISPLAY_AFTER;
259 	 }
260 	 if(right)
261 	 {
262 	    if(ReplaceCharMove((Char()&0xF0)+key)==OK)
263 	       right=0;
264 	 }
265 	 else
266 	 {
267 	    if(ReplaceChar((Char()&0x0F)+(key<<4))==OK)
268 	       right=1;
269 	 }
270 	 flag|=REDISPLAY_LINE;
271 	 undo.EndUndoGroup();
272       }
273       else
274       {
275 	 if(key>255 || (key>=0 && key<' ' && key!='\n' && key!='\t'))
276 	 {
277 	    UnrefKey(key);
278 	    continue;
279 	 }
280 	 key=ModifyKey(key);
281 	 if(buffer_mmapped)
282 	 {
283 	    if(Eol())
284 	       flag|=REDISPLAY_AFTER;
285 	    else
286 	       flag|=REDISPLAY_LINE;
287 	    ReplaceCharMove(key);
288 	    SetStdCol();
289 	    continue;
290 	 }
291 	 if(hex)
292 	 {
293 	    if(insert)
294 	    {
295 	       InsertChar(key);
296 	       flag|=REDISPLAY_AFTER;
297 	    }
298 	    else
299 	    {
300 	       ReplaceCharMove(key);
301 	       SetStdCol();
302 	       flag|=REDISPLAY_LINE;
303 	    }
304 	    continue;
305 	 }
306 	 undo.BeginUndoGroup();
307 	 switch(key)
308 	 {
309 	 case('\n'):
310 	    UserNewLine();
311 	    break;
312 	 case('\t'):
313 	    UserIndent();
314 	    break;
315 	 default:   /* not a newline and not a tab */
316 	    if(insert || Eol() || (Char()=='\t' && Tabulate(GetCol())!=(GetCol()+1)))
317 	       UserInsertChar(key);
318 	    else
319 	       UserReplaceChar(key);
320 	    flag|=REDISPLAY_LINE;
321 	 }
322 	 undo.EndUndoGroup();
323       }
324    }
325 }
Quit()326 void    Quit()
327 {
328    if(AskToSave())
329       Terminate();
330 }
AskToSave()331 int     AskToSave()
332 {
333    if(modified && !View)
334    {
335       static struct menu Menu[]={
336       {"   &Yes   "},{"   &No   "},{" &Cancel "},
337       {NULL}};
338       int result=TRUE;
339 
340       switch(ReadMenuBox(Menu,HORIZ,"The file has been modified. Save?","",
341 	 VERIFY_WIN_ATTR,CURR_BUTTON_ATTR))
342       {
343       case('Y'):
344          errno=0;
345          result=(UserSave()==OK);
346          if(!result && modified)
347          {
348             UserSaveAs();
349             result=!modified;
350          }
351          break;
352       case(0):
353       case('C'):
354          result=FALSE;
355          break;
356       case('N'):
357          result=TRUE;
358       }
359       return(result);
360    }
361    SavePosition();
362    return(TRUE);
363 }
364 
365 #if defined(NCURSES_VERSION) || defined(__NCURSES_H)
366 #define NCUR
367 #endif
368 
InitCurses()369 void  InitCurses()
370 {
371 #ifdef NCUR
372    static bool init=false;
373 
374    if(init)
375    {
376       endwin();
377       initscr();  // for some old ncurses
378       doupdate();
379       return;
380    }
381 
382    initscr();
383    init=true;
384 
385 #else
386    static SCREEN *le_scr=NULL;
387 
388    if(le_scr!=NULL)
389    {
390       delscreen(le_scr);
391    }
392 
393    le_scr=newterm(NULL,stdout,stdin);
394    if(le_scr==NULL)
395    {
396       fprintf(stderr,"le: newterm() failed. Check your $TERM variable.\n");
397       ExitProgram(1);
398    }
399 #endif
400 
401    le_start_color();
402 
403    cbreak();
404    noecho();
405    nonl();
406    meta(stdscr,TRUE);
407    raw();
408    intrflush(stdscr,FALSE);
409    keypad(stdscr,TRUE);
410 
411    idlok(stdscr,useidl);
412    scrollok(stdscr,FALSE);
413 }
TermCurses()414 void  TermCurses()
415 {
416    move(LINES-1,0);
417    clrtoeol();
418    refresh();
419    endwin();
420 }
421 
422 int optUseColor=-1;
423 
Initialize()424 void    Initialize()
425 {
426    FILE    *f;
427 
428    InitModifyKeyTables();
429    init_chset();
430    initcalc();
431 
432 #ifndef MSDOS
433    char  *filename=(char*)alloca((strlen(HOME)|15)+17);
434    sprintf(filename,"%s/.le",HOME);
435    mkdir(filename,0700);
436    strcat(filename,"/tmp");
437    mkdir(filename,0700);
438    sprintf(HstName,"%s/.le/history2",HOME);
439 #else
440    sprintf(HstName,"%s/le.hst",HOME);
441 #endif
442 
443    InstallSignalHandlers();
444 
445    InitCurses();
446 
447    ReadConf();
448 
449    if(optUseColor!=-1)
450       UseColor=optUseColor;
451 
452    init_attrs();
453 
454    InitMenu();
455 
456    struct flock l;
457    l.l_type=F_RDLCK;
458    l.l_whence=SEEK_SET;
459    l.l_start=l.l_len=0;
460    MessageSync("Loading history...");
461    f=fopen(HstName,"rb");
462    if(f && fcntl(fileno(f),F_SETLKW,&l)!=-1)
463    {
464       PositionHistory.ReadFrom(f);
465       LoadHistory.ReadFrom(f);
466       SearchHistory.ReadFrom(f);
467       ShellHistory.ReadFrom(f);
468       PipeHistory.ReadFrom(f);
469       fclose(f);
470    }
471 
472    EditorReadKeymap();
473    RebuildKeyTree();
474 
475    LoadMainMenu();
476 }
Terminate()477 void    Terminate()
478 {
479    FILE    *f;
480 
481    alarm(0);
482    EmptyText();
483 
484    curs_set(1);
485 
486    if(strcmp(FileName,HstName))
487    {
488       if(SaveHst)
489       {
490          MessageSync("Saving history...");
491          int fd=open(HstName,O_RDWR|O_CREAT,0644);
492          struct flock l;
493 	 l.l_type=F_RDLCK;
494 	 l.l_whence=SEEK_SET;
495 	 l.l_start=l.l_len=0;
496          if(fd!=-1 && fcntl(fd,F_SETLKW,&l)!=-1)
497          {
498             f=fdopen(fd,"r+b");
499 
500             InodeHistory oldPositionHistory;
501             History  oldLoadHistory;
502             History  oldSearchHistory;
503             History  oldShellHistory;
504             History  oldPipeHistory;
505 
506             oldPositionHistory.ReadFrom(f);
507             oldLoadHistory.ReadFrom(f);
508             oldSearchHistory.ReadFrom(f);
509             oldShellHistory.ReadFrom(f);
510             oldPipeHistory.ReadFrom(f);
511             PositionHistory.Merge(oldPositionHistory);
512             LoadHistory.Merge(oldLoadHistory);
513             SearchHistory.Merge(oldSearchHistory);
514             ShellHistory.Merge(oldShellHistory);
515             PipeHistory.Merge(oldPipeHistory);
516 
517             rewind(f);
518 
519             PositionHistory.WriteTo(f);
520             LoadHistory.WriteTo(f);
521             SearchHistory.WriteTo(f);
522             ShellHistory.WriteTo(f);
523             PipeHistory.WriteTo(f);
524 
525 #ifdef HAVE_FTRUNCATE
526 	    fflush(f);
527 	    ftruncate(fd,ftell(f));
528 #endif
529             fclose(f);
530          }
531          close(fd);
532       }
533    }
534 
535    TermCurses();
536 
537    ExitProgram(0);
538 }
539 
PrintUsage(int arg)540 void  PrintUsage(int arg)
541 {
542    (void)arg;
543    printf("Usage: le [OPTIONS] [FILES...]\n"
544 	  "\n"
545 	  "-r  --read-only    permanent read only mode (view)\n"
546 	  "-h  --hex-mode     start in hex mode\n"
547 	  "-b  --black-white  black and white mode\n"
548 	  "-c  --color        color mode\n"
549 	  "    --config=FILE  use specified file instead of le.ini\n"
550 	  "    --dump-keymap  dump default keymap to stdout and exit\n"
551 	  "    --dump-colors  dump default color map to stdout and exit\n"
552 #if USE_MULTIBYTE_CHARS
553           "    --multibyte    force multibyte mode\n"
554           "    --no-multibyte disable multibyte mode\n"
555 #endif
556 	  "    --mmap         load file using mmap (read only)\n"
557 	  "    --mmap-rw      mmap read-write. Use with extreme caution,\n"
558 	  "                   especially on your hard disk!\n"
559 	  "                   All changes go directly to file/disk.\n"
560 	  "    --help         this description\n"
561 	  "    --version      print LE version\n"
562 	  "\n"
563 	  "The last file will be loaded. If no files specified, last readable file\n"
564 	  "from history will be loaded if the path is relative or it is the last.\n");
565    ExitProgram(1);
566 }
567 
568 #if USE_MULTIBYTE_CHARS
has_widechars()569 static bool has_widechars()
570 {
571    for(int c=' '; c<256; c++)
572    {
573       wint_t w=btowc(c);
574       if(w!=WEOF && w>=256)
575 	 return 1;
576    }
577    return 0;
578 }
579 #endif
580 
main(int argc,char ** argv)581 int     main(int argc,char **argv)
582 {
583    int   optView=-1,opteditmode=-1,optWarpLine=0;
584    int	 opt_use_mmap=-1;
585    int	 opt_mb_mode=-1;
586    int   opt;
587 
588    enum {
589       DUMP_KEYMAP=1024,
590       DUMP_COLORS,
591       PRINT_HELP,
592       PRINT_VERSION,
593       CONFIG_FILE,
594       USE_MMAP,
595       USE_MMAP_RW,
596 #if USE_MULTIBYTE_CHARS
597       MULTIBYTE,
598       NO_MULTIBYTE,
599 #endif
600       MAX_OPTION
601    };
602 
603    static struct option le_options[]=
604    {
605       {"help",no_argument,0,PRINT_HELP},
606       {"version",no_argument,0,PRINT_VERSION},
607       {"dump-keymap",no_argument,0,DUMP_KEYMAP},
608       {"dump-colors",no_argument,0,DUMP_COLORS},
609       {"read-only",no_argument,0,'r'},
610       {"hex-mode",no_argument,0,'h'},
611       {"black-white",no_argument,0,'b'},
612       {"color",no_argument,0,'c'},
613       {"config",required_argument,0,CONFIG_FILE},
614 #if USE_MULTIBYTE_CHARS
615       {"multibyte",no_argument,0,MULTIBYTE},
616       {"no-multibyte",no_argument,0,NO_MULTIBYTE},
617 #endif
618 #ifdef HAVE_MMAP
619       {"mmap",no_argument,0,USE_MMAP},
620       {"mmap-rw",no_argument,0,USE_MMAP_RW},
621 #endif
622       {0,0,0,0}
623    };
624 
625    char  newname[256];
626    newname[0]=0;
627 
628    strncpy(Program,le_basename(argv[0]),sizeof(Program));
629 
630 #if defined(CURSES_BOOL) && !defined(bool_redefined)
631    if(sizeof(bool) != sizeof(CURSES_BOOL))
632    {
633       fprintf(stderr,"%s: warning: curses library has wrong bool type. Expect trouble.\n",Program);
634       sleep(2);
635    }
636 #endif
637 
638 #ifdef __MSDOS__
639     HOME=".";
640     TERM="ansi";
641     _fmode=O_BINARY;
642 #else
643    setlocale(LC_ALL,"");
644 #if USE_MULTIBYTE_CHARS
645    if(has_widechars())
646       mb_mode=true;
647    const char *cs=locale_charset();
648    if(cs && !strcasecmp(cs,"UTF-8"))
649       mb_mode=true;
650 #endif
651 
652    HOME=getenv("HOME");
653    if(HOME==NULL)
654    {
655       fprintf(stderr,"Cannot get the value of HOME\n\r");
656       return(1);
657    }
658    TERM=getenv("TERM");
659    if(TERM==NULL)
660    {
661       fprintf(stderr,"Cannot get the value of TERM\n\r");
662       return(1);
663    }
664    DISPLAY=getenv("DISPLAY");
665 #endif
666 
667    while((opt=getopt_long(argc,argv,"rhbc",le_options,0))!=-1)
668    {
669       switch(opt)
670       {
671       case('r'):
672          optView=1;
673 #ifdef HAVE_MMAP
674 	 opt_use_mmap=1;
675 #endif
676          break;
677       case('h'):
678          opteditmode=HEXM;
679          break;
680       case('b'):
681          optUseColor=0;
682          break;
683       case('c'):
684 	 optUseColor=1;
685 	 break;
686       case('?'):
687 	 fprintf(stderr,"%s: Try `%s --help' for more information\n",Program,argv[0]);
688 	 ExitProgram(1);
689       case(DUMP_KEYMAP):
690 	 WriteActionMap(stdout);
691 	 ExitProgram(0);
692       case(DUMP_COLORS):
693 	 DumpDefaultColors(stdout);
694 	 ExitProgram(0);
695       case(PRINT_HELP):
696 	 PrintUsage(0);
697 	 ExitProgram(0);
698       case(PRINT_VERSION):
699 	 PrintVersion();
700 	 ExitProgram(0);
701       case(USE_MMAP):
702 	 opt_use_mmap=1;
703 	 if(optView==-1)
704 	    optView=2;
705 	 else
706 	    optView|=2;
707 	 opteditmode=HEXM;
708 	 break;
709       case(USE_MMAP_RW):
710 	 opt_use_mmap=1;
711 	 if(optView!=-1)
712 	    optView&=~2;
713 	 opteditmode=HEXM;
714 	 break;
715       case(CONFIG_FILE):
716 	 ExplicitInitName=true;
717 	 strncpy(InitName,optarg,sizeof(InitName)-1);
718 	 break;
719 #if USE_MULTIBYTE_CHARS
720       case MULTIBYTE:
721 	 opt_mb_mode=true;
722 	 break;
723       case NO_MULTIBYTE:
724 	 opt_mb_mode=false;
725 	 break;
726 #endif
727       }
728    }
729    if(optUseColor!=-1)
730       UseColor=optUseColor;
731 
732    Initialize();
733 
734    if(optView!=-1)
735       View=!!optView;
736    if(opteditmode!=-1)
737       editmode=opteditmode;
738    if(optUseColor!=-1)
739       UseColor=optUseColor;
740    if(opt_use_mmap!=-1)
741       buffer_mmapped=opt_use_mmap;
742 #if USE_MULTIBYTE_CHARS
743    if(opt_mb_mode!=-1)
744       mb_mode=opt_mb_mode;
745 #endif
746 
747    if(optind<argc-1 && argv[optind][0]=='+' && isdigit((unsigned char)argv[optind][1]))
748    {
749       optWarpLine=atoi(argv[optind]);
750       optind++;
751    }
752 
753    if(optind>=argc)
754    {
755       const HistoryLine *hl=0;
756       LoadHistory.Open();
757       bool first=true;
758       for(;;)
759       {
760 	 hl=LoadHistory.Prev();
761 	 if(!hl)
762 	    break;
763 	 const char *f=hl->get_line();
764 	 if(*f && (first || f[0]!='/')
765 	 && access(f,R_OK)!=-1)
766 	 {
767 	    strcpy(newname,f);
768 	    break;
769 	 }
770 	 first=false;
771       }
772 
773       if(!hl)
774       {
775          ShowAbout();
776          if(getstring("Load: ",newname,255,&LoadHistory,NULL,NULL)<1
777                                              || ChooseFileName(newname)<0)
778             Terminate();
779          HideAbout();
780       }
781    }
782    else
783    {
784       for(; optind<argc; optind++)
785          LoadHistory+=argv[optind];
786       sprintf(newname,"%.255s",argv[argc-1]);
787    }
788    if(newname[0] && file_check(newname)==ERR)
789    {
790       if(View || buffer_mmapped)
791 	 Terminate();
792       newname[0]=0;
793    }
794    if(LoadFile(newname)!=ERR)
795    {
796       if(optWarpLine>0)
797 	 GoToLineNum(optWarpLine-1);
798    }
799    Edit();
800    Terminate();
801    ExitProgram(0);
802    return 0;
803 }
804