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