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