1 /*
2 * Copyright (c) 1993-2013 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 #include <config.h>
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <string.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #include <sys/stat.h>
27 #include <stdlib.h>
28 #include <limits.h>
29 #include "edit.h"
30 #include "keymap.h"
31 #include "keynames.h"
32 #include "getch.h"
33 #include <term.h>
34
35 #include "block.h"
36 #include "options.h"
37 #include "keymap.h"
38 #include "format.h"
39 #include "search.h"
40 #include "colormnu.h"
41
42 unsigned char StringTyped[256];
43 int StringTypedLen;
44 int LastActionCode;
45 const char *ActionArgument;
46 int ActionArgumentLen;
47
48 int FuncKeysNum=12;
49
50 int MouseCounter=0;
51
52 const ActionNameProcRec ActionNameProcTable[]=
53 {
54 #include "action-name-func.h"
55 };
56
57 const struct {
58 const char *alias;
59 int code;
60 } ActionNameAliases[]={
61 { "quit-editor", A_ESCAPE },
62 { 0 }
63 };
64
65 enum
66 {
67 CODE_EQUAL,
68 CODE_PREFIX,
69 CODE_PAUSE,
70 CODE_NOT_EQUAL,
71 CODE_TOO_MUCH
72 };
73
74 const int MAX_DELAY=30000000;
75 const int HALF_DELAY=500;
76
77 struct KeyTreeNode
78 {
79 int maxdelay;
80 int action;
81 int keycode;
82 struct KeyTreeNode *sibling;
83 struct KeyTreeNode *child;
84 const char *arg;
85 };
86
87 const ActionCodeRec *ActionCodeTable=DefaultActionCodeTable;
88 ActionCodeRec *DynamicActionCodeTable;
89
GetActionName(int action)90 const char *GetActionName(int action)
91 {
92 if(action>=A__FIRST && action<=A__LAST)
93 return ActionNameProcTable[action-A__FIRST].name;
94 return(NULL);
95 }
96
GetActionCodeText(const char * code)97 const char *GetActionCodeText(const char *code)
98 {
99 static char code_text[1024];
100 char *store=code_text;
101
102 while(*code)
103 {
104 unsigned char the_code=*code++;
105 if(iscntrl(the_code))
106 {
107 if(the_code=='\033')
108 sprintf(store,"\\e");
109 else if(the_code<32)
110 sprintf(store,"^%c",the_code+'@');
111 else
112 sprintf(store,"\\%03o",the_code);
113 store+=strlen(store);
114 }
115 else
116 *(store++)=the_code;
117 }
118 *store=0;
119 return(code_text);
120 }
121
122 #define LEFT_BRACE '{'
123 #define RIGHT_BRACE '}'
124
PrettyCodeScore(const char * c)125 static int PrettyCodeScore(const char *c)
126 {
127 if(c==0)
128 return 1000000;
129
130 int score=0;
131 while(*c)
132 {
133 score++;
134
135 char term_name[256];
136 char *term_str;
137 int bracket;
138 int fk;
139 int shift;
140 char code_ch=*c;
141 switch(code_ch)
142 {
143 case('$'):
144 code_ch=*(++c);
145
146 if(code_ch==0)
147 break;
148
149 bracket=(code_ch==LEFT_BRACE);
150 c+=bracket;
151
152 term_str=term_name;
153 while(*c!=0 && (bracket?*c!=RIGHT_BRACE:isalnum((unsigned char)*c)) && term_str-term_name<255)
154 *term_str++=*c++;
155 *term_str=0;
156 if(!(bracket && *c==RIGHT_BRACE))
157 c--;
158
159 shift=0;
160 if(sscanf(term_name,"%1dkf%d",&shift,&fk)==2
161 || sscanf(term_name,"kf%d",&fk)==1)
162 {
163 if(shift)
164 score+=2+2*shift;
165 else
166 score+=2;
167 if(shift)
168 sprintf(term_name,"kf%d",shift*FuncKeysNum+fk);
169 }
170 else
171 score+=8;
172 term_str=tigetstr(term_name);
173 if(term_str==(char*)-1 || !term_str || !*term_str)
174 return 1000000;
175 break;
176 case('|'):
177 score+=5;
178 break;
179 case('^'):
180 case('\\'):
181 break;
182 case('\e'):
183 if(c[1]=='[') // terminal codes are not pretty
184 score+=6;
185 break;
186 }
187 c++;
188 }
189 return score;
190 }
191
ActionCodePrettyPrint(const char * c)192 const char *ActionCodePrettyPrint(const char *c)
193 {
194 static char code_text[1024];
195 char *store=code_text;
196 *store=0;
197
198 while(*c)
199 {
200 char term_name[256];
201 char *term_str;
202 int bracket;
203 int fk;
204 int shift;
205 unsigned char code_ch=*c;
206 switch(code_ch)
207 {
208 case('$'):
209 code_ch=*(++c);
210
211 if(code_ch==0)
212 break;
213
214 bracket=(code_ch==LEFT_BRACE);
215 c+=bracket;
216
217 term_str=term_name;
218 while(*c!=0 && (bracket?*c!=RIGHT_BRACE:isalnum((unsigned char)*c)) && term_str-term_name<255)
219 *term_str++=*c++;
220 *term_str=0;
221 if(!(bracket && *c==RIGHT_BRACE))
222 c--;
223
224 shift=0;
225 if((sscanf(term_name,"%1dkf%d",&shift,&fk)==2
226 || sscanf(term_name,"kf%d",&fk)==1) && shift<4)
227 {
228 static char shift_str_map[][3]={"","~","^","~^"};
229 store+=sprintf(store,"%sF%d",shift_str_map[shift],fk);
230 }
231 else
232 {
233 // FIXME.
234 store+=sprintf(store,"%s",term_name);
235 }
236 if(c[1] && c[1]!='|')
237 {
238 *store++=' ';
239 *store=0;
240 }
241 break;
242 case('|'):
243 *store++='+';
244 *store=0;
245 break;
246 case('^'):
247 if(c[1])
248 {
249 *store++='^';
250 *store++=toupper(*++c);
251 *store=0;
252 break;
253 }
254 goto default_l;
255 case('\\'):
256 code_ch=*(++c);
257 default:
258 default_l:
259 if(code_ch==27 && c[1]=='|' && c[2] && c[2]!='$')
260 {
261 *store++='M';
262 *store++='-';
263 *store=0;
264 c++;
265 }
266 else if(code_ch<32)
267 {
268 *store++='^';
269 *store++=code_ch+'@';
270 }
271 else if(code_ch==128)
272 {
273 *store++='^';
274 *store++='@';
275 }
276 else
277 *store++=code_ch;
278 *store=0;
279 }
280 c++;
281 }
282 return code_text;
283 }
284
ShortcutPrettyPrint(int c,const char * arg)285 const char *ShortcutPrettyPrint(int c,const char *arg)
286 {
287 static char code_text[1024];
288 char *store=code_text;
289
290 const char *best_code=0;
291 int best_score=1000000;
292 for(int i=0; ActionCodeTable[i].action!=-1; i++)
293 {
294 if(ActionCodeTable[i].action!=c || xstrcmp(ActionCodeTable[i].arg,arg))
295 continue;
296 const char *code=ActionCodeTable[i].code;
297 int score=PrettyCodeScore(code);
298 if(score<best_score)
299 {
300 best_code=code;
301 best_score=score;
302 }
303 }
304 if(best_code==0)
305 return 0;
306
307 strcpy(store,ActionCodePrettyPrint(best_code));
308 return code_text;
309 }
310
WriteActionMap(FILE * f)311 void WriteActionMap(FILE *f)
312 {
313 for(int i=0; ActionCodeTable[i].action!=-1; i++)
314 {
315 int pos=0;
316 const char *a_name=GetActionName(ActionCodeTable[i].action);
317 fputs(a_name,f);
318 pos+=strlen(a_name);
319 const char *arg=ActionCodeTable[i].arg;
320 if(arg) {
321 fputc('(',f),pos++;
322 while(*arg) {
323 char out=*arg;
324 char bsout=0;
325 switch(*arg) {
326 case '\n': bsout='n'; break;
327 case '\r': bsout='r'; break;
328 case '\t': bsout='t'; break;
329 case '_':
330 case '\\': bsout=*arg; break;
331 case ' ': out='_'; break;
332 }
333 if(bsout)
334 fputc('\\',f),pos++;
335 fputc(bsout?bsout:out,f),pos++;
336 arg++;
337 }
338 fputc(')',f),pos++;
339 }
340 fputc(' ',f),pos++;
341 while(pos<23)
342 fputc(' ',f),pos++;
343 fputs(GetActionCodeText(ActionCodeTable[i].code),f);
344 putc('\n',f);
345 }
346 }
347
GetActionProc(int action)348 ActionProc GetActionProc(int action)
349 {
350 if(action>=A__FIRST && action<=A__LAST)
351 return ActionNameProcTable[action-A__FIRST].proc;
352 return(NULL);
353 }
354
AddToKeyTree(KeyTreeNode * curr,int key_code,int delay,int action,const char * arg)355 static KeyTreeNode *AddToKeyTree(KeyTreeNode *curr,int key_code,int delay,int action,const char *arg)
356 {
357 KeyTreeNode *scan;
358 for(scan=curr->child; scan; scan=scan->sibling)
359 if(scan->keycode==key_code)
360 break;
361 if(!scan)
362 {
363 scan=new KeyTreeNode;
364 scan->maxdelay=delay;
365 scan->keycode=key_code;
366 scan->action=action;
367 scan->arg=arg;
368 scan->child=0;
369 scan->sibling=curr->child;
370 curr->child=scan;
371 }
372 else
373 {
374 if(scan->action==NO_ACTION) {
375 scan->action=action;
376 scan->arg=arg;
377 }
378 }
379 return(scan);
380 }
381
382 #define LEFT_BRACE '{'
383 #define RIGHT_BRACE '}'
384
BuildKeyTree(const ActionCodeRec * ac_table)385 KeyTreeNode *BuildKeyTree(const ActionCodeRec *ac_table)
386 {
387 KeyTreeNode *top=0;
388 char term_name[256];
389 char *term_str;
390 int bracket;
391 int fk;
392
393 top=new KeyTreeNode;
394 top->keycode=-1;
395 top->action=NO_ACTION;
396 top->arg=0;
397 top->maxdelay=MAX_DELAY;
398 top->child=0;
399 top->sibling=0;
400
401 while(ac_table->action!=-1)
402 {
403 int fk_mask=0;
404 int fk_num=0;
405 while(fk_mask < (1<<fk_num))
406 {
407 KeyTreeNode *curr=top;
408
409 const char *code=ac_table->code;
410 int delay=MAX_DELAY;
411
412 fk_num=0;
413 while(*code)
414 {
415 int shift=0;
416 int key_code=0;
417
418 char code_ch=*code;
419 switch(code_ch)
420 {
421 case('$'):
422 code_ch=*(++code);
423
424 if(code_ch==0)
425 break;
426
427 bracket=(code_ch==LEFT_BRACE);
428 code+=bracket;
429
430 term_str=term_name;
431 while(*code!=0 && (bracket?*code!=RIGHT_BRACE:isalnum((unsigned char)*code)) && term_str-term_name<255)
432 *term_str++=*code++;
433 *term_str=0;
434 if(bracket && *code==RIGHT_BRACE)
435 code++;
436
437 if(sscanf(term_name,"%1dkf%d",&shift,&fk)==2)
438 sprintf(term_name,"kf%d",shift*FuncKeysNum+fk);
439
440 if((fk_mask&(1<<fk_num))==0)
441 {
442 fallback:
443 key_code=FindKeyCode(term_name);
444 }
445 else
446 {
447 term_str=tigetstr(term_name);
448 if(term_str==NULL || term_str==(char*)-1)
449 goto fallback;
450 while(term_str[0] && term_str[1])
451 {
452 curr=AddToKeyTree(curr,(unsigned char)term_str[0],delay,NO_ACTION,NULL);
453 delay=HALF_DELAY;
454 term_str++;
455 }
456 key_code=(unsigned char)term_str[0];
457 if(key_code==0)
458 goto fallback;
459 }
460 fk_num++;
461 break;
462 case('|'):
463 delay=MAX_DELAY;
464 code++;
465 continue;
466 break;
467 case('^'):
468 if(code[1])
469 {
470 code_ch=toupper(*++code)-'@';
471 if(!code_ch)
472 code_ch|=0200;
473 }
474 goto default_l;
475 case('\\'):
476 code_ch=*(++code);
477 default:
478 default_l:
479 key_code=(unsigned char)code_ch;
480 code++;
481 }
482
483 // now add the key_code to the tree
484 curr=AddToKeyTree(curr,key_code,delay,
485 (*code?NO_ACTION:ac_table->action),
486 (*code?NULL:ac_table->arg));
487 delay=HALF_DELAY;
488 }
489
490 fk_mask++;
491 }
492 ac_table++;
493 }
494
495 return top;
496 }
497
498 KeyTreeNode *KeyTree=0;
499
FreeKeyTree(KeyTreeNode * kt)500 void FreeKeyTree(KeyTreeNode *kt)
501 {
502 if(!kt)
503 return;
504 FreeKeyTree(kt->sibling);
505 FreeKeyTree(kt->child);
506 delete kt;
507 }
508
RebuildKeyTree()509 void RebuildKeyTree()
510 {
511 FreeKeyTree(KeyTree);
512 KeyTree=BuildKeyTree(ActionCodeTable);
513 }
514
FindActionCode(const char * ActionName)515 int FindActionCode(const char *ActionName)
516 {
517 int lo=A__FIRST;
518 int hi=A__LAST+1;
519
520 while(lo<hi) {
521 int mid=(lo+hi)/2;
522 int cmp=strcmp(ActionName,GetActionName(mid));
523 if(cmp==0)
524 return mid;
525 if(cmp>0)
526 lo=mid+1;
527 else
528 hi=mid;
529 }
530
531 // try aliases (there are few, no need for bsearch)
532 for(int i=0; ActionNameAliases[i].alias; i++)
533 if(!strcmp(ActionName,ActionNameAliases[i].alias))
534 return ActionNameAliases[i].code;
535
536 return -1;
537 }
538
ParseActionNameArg(char * action,const char ** arg)539 int ParseActionNameArg(char *action,const char **arg)
540 {
541 // extract the action parameter
542 *arg=NULL;
543 char *end=action+strlen(action);
544 char *par1=strchr(action,'(');
545 if(par1 && end[-1]==')') {
546 *par1=0;
547 end[-1]=0;
548 *arg=par1+1;
549 }
550 // convert the action name to code
551 return FindActionCode(action);
552 }
553
ParseActionArgumentAlloc(const char * arg)554 char *ParseActionArgumentAlloc(const char *arg)
555 {
556 if(!arg || !*arg)
557 return NULL;
558 char *alloc=(char*)malloc(strlen(arg)+1);
559 char *store=alloc;
560 while(*arg) {
561 switch(*arg) {
562 case '_':
563 *store++=' ';
564 arg++;
565 break;
566 case '\\':
567 arg++;
568 switch(*arg) {
569 case '\0':
570 *store++='\\';
571 break;
572 case 'n': *store++='\n'; arg++; break;
573 case 'r': *store++='\r'; arg++; break;
574 case 't': *store++='\t'; arg++; break;
575 default:
576 *store++=*arg++;
577 }
578 break;
579 default:
580 *store++=*arg++;
581 }
582 }
583 *store=0;
584 return alloc;
585 }
586
ReadActionMap(FILE * f)587 void ReadActionMap(FILE *f)
588 {
589 FreeActionCodeTable();
590
591 char ActionName[1024];
592 const char *ActionArg;
593 char ActionCode[256];
594 char *store;
595 int ch;
596 ActionCodeRec *NewTable=NULL;
597 int CurrTableSize=0;
598 int CurrTableCell=0;
599
600 for(;;) /* line cycle */
601 {
602 store=ActionName;
603 for(;;) /* action name cycle */
604 {
605 ch=fgetc(f);
606 if(ch==EOF || isspace(ch))
607 break;
608 if(store-ActionName<(int)sizeof(ActionName)-1)
609 *(store++)=ch;
610 }
611 *store=0;
612
613 int action_found=ParseActionNameArg(ActionName,&ActionArg);
614 if(action_found==-1)
615 {
616 while(ch!='\n' && ch!=EOF)
617 ch=fgetc(f);
618 if(ch==EOF)
619 break;
620 continue;
621 }
622
623 /* skip spaces between action name and action code */
624 while(ch!='\n' && ch!=EOF && isspace(ch))
625 ch=fgetc(f);
626
627 if(ch==EOF || ch=='\n')
628 break;
629
630 store=ActionCode;
631 for(;;)
632 {
633 if(ch=='\\')
634 {
635 ch=fgetc(f);
636 switch(ch)
637 {
638 case('e'):
639 ch=27;
640 break;
641 case('n'):
642 ch=10;
643 break;
644 case('r'):
645 ch=13;
646 break;
647 case('t'):
648 ch=9;
649 break;
650 case('b'):
651 ch=8;
652 break;
653 default:
654 if(isdigit(ch))
655 {
656 ungetc(ch,f);
657 fscanf(f,"%3o",&ch);
658 }
659 else
660 {
661 ungetc(ch,f);
662 ch='\\';
663 }
664 }
665 }
666 if(ch=='\000')
667 ch=128;
668
669 if(store-ActionCode<(int)sizeof(ActionCode)-1)
670 *(store++)=ch;
671
672 ch=fgetc(f);
673 if(ch==EOF || isspace(ch))
674 break;
675 }
676 *store=0;
677
678 if(CurrTableSize<=CurrTableCell)
679 {
680 if(NewTable==NULL)
681 NewTable=(ActionCodeRec*)malloc((CurrTableSize=16)*sizeof(*NewTable));
682 else
683 NewTable=(ActionCodeRec*)realloc(NewTable,(CurrTableSize*=2)*sizeof(*NewTable));
684 if(!NewTable)
685 {
686 fprintf(stderr,"le: Not enough memory!\n");
687 exit(1);
688 }
689 }
690 NewTable[CurrTableCell].action=action_found;
691 NewTable[CurrTableCell].code=strdup(ActionCode);
692 NewTable[CurrTableCell].arg=ParseActionArgumentAlloc(ActionArg);
693 if(NewTable[CurrTableCell].code==NULL)
694 {
695 fprintf(stderr,"le: Not enough memory!\n");
696 exit(1);
697 }
698 CurrTableCell++;
699 }
700
701 NewTable=(ActionCodeRec*)realloc(NewTable,(CurrTableCell+1)*sizeof(*NewTable));
702 if(!NewTable)
703 {
704 fprintf(stderr,"le: Not enough memory!\n");
705 exit(1);
706 }
707 NewTable[CurrTableCell].action=-1;
708 NewTable[CurrTableCell].code=NULL;
709
710 ActionCodeTable=NewTable;
711 DynamicActionCodeTable=NewTable;
712 }
713
FreeActionCodeTable()714 void FreeActionCodeTable()
715 {
716 if(DynamicActionCodeTable)
717 {
718 for(int i=0; DynamicActionCodeTable[i].code; i++)
719 free(DynamicActionCodeTable[i].code);
720 free(DynamicActionCodeTable);
721 DynamicActionCodeTable=0;
722 }
723 ActionCodeTable=0;
724 }
725
GetNextAction()726 int GetNextAction()
727 {
728 unsigned char *store;
729 int key;
730 static KeyTreeNode kt_mb = { HALF_DELAY, NO_ACTION, -1, NULL, NULL, NULL };
731
732 store=StringTyped;
733 StringTypedLen=0;
734 *store=0;
735 ActionArgument=NULL;
736
737 KeyTreeNode *kt=KeyTree;
738
739 for(;;) // loop for a whole key sequence
740 {
741 int time_passed=0;
742
743 for(;;) // loop for one key
744 {
745 int delay=-1;
746 KeyTreeNode *scan;
747
748 for(scan=kt->child; scan; scan=scan->sibling)
749 if(delay==-1 ||
750 (delay>scan->maxdelay && scan->maxdelay<time_passed))
751 delay=scan->maxdelay;
752
753 if(delay==MAX_DELAY)
754 key=GetKey();
755 else if(delay==-1 || time_passed>=delay)
756 goto return_action;
757 else
758 key=GetKey(delay-time_passed);
759
760 #ifdef KEY_RESIZE
761 if(key==KEY_RESIZE)
762 return WINDOW_RESIZE;
763 #else // !KEY_RESIZE
764 extern int resize_flag;
765 if(resize_flag && kt==KeyTree)
766 {
767 if(key!=ERR)
768 ungetch(key);
769 CheckWindowResize();
770 return WINDOW_RESIZE;
771 }
772 #endif // !KEY_RESIZE
773
774 if(key==ERR)
775 { // no key in the time interval
776 time_passed=delay;
777 continue;
778 }
779
780 #ifdef WITH_MOUSE
781 if(key==KEY_MOUSE)
782 {
783 if(kt==KeyTree)
784 return MOUSE_ACTION;
785 MEVENT mev;
786 int limit=100; // workaround for ncurses bug
787 while(getmouse(&mev)==OK && limit-->0)
788 ; // flush mouse event queue
789 continue;
790 }
791 #endif
792
793 if(key<=UCHAR_MAX)
794 {
795 *(store++)=key;
796 *store=0;
797 StringTypedLen++;
798 }
799
800 for(scan=kt->child; scan; scan=scan->sibling)
801 if(scan->keycode==key || (key==0 && scan->keycode==128))
802 break;
803 if(!scan)
804 {
805 if(StringTypedLen>1 && kt->action==NO_ACTION && StringTyped[0]<32) {
806 // We've got an unknown sequence.
807 // It is likely that it is a bit longer that we've already got,
808 // so try to flush it.
809 napms(10);
810 flushinp();
811 }
812 #if USE_MULTIBYTE_CHARS
813 // check for partial mb chars
814 if(mb_mode && StringTypedLen>0 && kt->action==NO_ACTION && StringTyped[0]>=128) {
815 mbtowc(0,0,0);
816 wchar_t wc;
817 int mb_size=mbtowc(&wc,(const char*)StringTyped,StringTypedLen);
818 if(mb_size<=0) {
819 kt=&kt_mb;
820 continue;
821 }
822 }
823 #endif
824 return_action:
825 if(kt->action==REFRESH_SCREEN)
826 clearok(stdscr,1); // force repaint for next refresh
827 timeout(-1);
828 ActionArgument=kt->arg;
829 ActionArgumentLen=xstrlen(ActionArgument);
830 return(LastActionCode=kt->action);
831 }
832 kt=scan;
833 }
834 }
835 }
836
GetActionArgument(const char * prompt,History * history,const char * help,const char * title)837 const char *GetActionArgument(const char *prompt,History* history,const char *help,const char *title)
838 {
839 static char *buf[A__LAST-A__FIRST+1];
840 static int buf_len[A__LAST-A__FIRST+1];
841 if(ActionArgument)
842 return ActionArgument;
843 char **b=buf+LastActionCode-A__FIRST;
844 int *len=buf_len+LastActionCode-A__FIRST;
845 const int maxlen=256;
846 if(!*b)
847 *b=(char*)malloc(maxlen);
848 if(!*b) {
849 NoMemory();
850 return NULL;
851 }
852 int res=getstring(prompt,*b,maxlen-1,history,len,help,title);
853 if(res==-1)
854 return NULL;
855 ActionArgument=*b;
856 ActionArgumentLen=*len;
857 return *b;
858 }
859