1 /*  $Id: edit.c,v 1.1 2008-03-27 00:41:33 vsc Exp $
2 
3     Part of SWI-Prolog
4 
5     Author:        Jan Wielemaker
6     E-mail:        jan@swi.psy.uva.nl
7     WWW:           http://www.swi-prolog.org
8     Copyright (C): 1985-2002, University of Amsterdam
9 
10     This library is free software; you can redistribute it and/or
11     modify it under the terms of the GNU Lesser General Public
12     License as published by the Free Software Foundation; either
13     version 2.1 of the License, or (at your option) any later version.
14 
15     This library is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18     Lesser General Public License for more details.
19 
20     You should have received a copy of the GNU Lesser General Public
21     License along with this library; if not, write to the Free Software
22     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 */
24 
25 #define _MAKE_DLL 1
26 #undef _export
27 #include <windows.h>
28 #include <tchar.h>
29 #include "console.h"
30 #include "console_i.h"
31 #include "common.h"
32 #include <memory.h>
33 #include <string.h>
34 #include <ctype.h>
35 
36 #ifndef EOF
37 #define EOF -1
38 #endif
39 
40 typedef void (*function)(Line ln, int chr);	/* edit-function */
41 
42 static function dispatch_table[256];	/* general dispatch-table */
43 static function dispatch_meta[256];	/* ESC-char dispatch */
44 static RlcCompleteFunc _rlc_complete_function = rlc_complete_file_function;
45 
46 static void	init_line_package(RlcData b);
47 static void	bind_actions(void);
48 
49 #ifndef min
50 #define min(a, b) ((a) < (b) ? (a) : (b))
51 #define max(a, b) ((a) > (b) ? (a) : (b))
52 #endif
53 
54 #ifndef TRUE
55 #define TRUE 1
56 #define FALSE 0
57 #endif
58 
59 #ifndef EOS
60 #define EOS 0
61 #endif
62 
63 #ifndef ESC
64 #define ESC 27
65 #endif
66 
67 #define COMPLETE_NEWLINE 1
68 #define COMPLETE_EOF	 2
69 
70 #define ctrl(c)	((c) - '@')
71 #define META_OFFSET 128
72 #define meta(c) ((c) + META_OFFSET)
73 
74 		 /*******************************
75 		 *	       BUFFER		*
76 		 *******************************/
77 
78 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
79 make_room(Line, int room)
80 	Make n-characters space after the point.
81 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
82 
83 static void
make_room(Line ln,size_t room)84 make_room(Line ln, size_t room)
85 { while ( ln->size + room + 1 > ln->allocated )
86   { if ( !ln->data )
87     { ln->data = rlc_malloc(256 * sizeof(TCHAR));
88       ln->allocated = 256;
89     } else
90     { ln->allocated *= 2;
91       ln->data = rlc_realloc(ln->data, ln->allocated * sizeof(TCHAR));
92     }
93   }
94 
95   memmove(&ln->data[ln->point + room], &ln->data[ln->point],
96 	  (ln->size - ln->point)*sizeof(TCHAR));
97   ln->size += room;
98   if ( room > 0 )
99     ln->change_start = min(ln->change_start, ln->point);
100 }
101 
102 
103 static void
set_line(Line ln,const TCHAR * s)104 set_line(Line ln, const TCHAR *s)
105 { size_t len = _tcslen(s);
106 
107   ln->size = ln->point = 0;
108   make_room(ln, len);
109   _tcsncpy(ln->data, s, len);
110 }
111 
112 
113 static void
terminate(Line ln)114 terminate(Line ln)
115 { if ( !ln->data )
116   { ln->data = rlc_malloc(sizeof(TCHAR));
117     ln->allocated = 1;
118   }
119   ln->data[ln->size] = EOS;
120 }
121 
122 
123 static void
delete(Line ln,size_t from,size_t len)124 delete(Line ln, size_t from, size_t len)
125 { if ( from < 0 || from > ln->size || len < 0 || from + len > ln->size )
126     return;
127 
128   _tcsncpy(&ln->data[from], &ln->data[from+len], ln->size - (from+len));
129   ln->size -= len;
130 }
131 
132 
133 		 /*******************************
134 		 *	     POSITIONING	*
135 		 *******************************/
136 
137 static size_t
back_word(Line ln,size_t from)138 back_word(Line ln, size_t from)
139 { from = min(from, ln->size);
140   from = max(0, from);
141 
142   if ( ln->data )
143   { while(!rlc_is_word_char(ln->data[from-1]) && from > 0 )
144       from--;
145     while(rlc_is_word_char(ln->data[from-1]) && from > 0 )
146       from--;
147   }
148 
149   return from;
150 }
151 
152 static size_t
forw_word(Line ln,size_t from)153 forw_word(Line ln, size_t from)
154 { from = min(from, ln->size);
155   from = max(0, from);
156 
157   if ( ln->data )
158   { while(!rlc_is_word_char(ln->data[from]) && from < ln->size )
159       from++;
160     while(rlc_is_word_char(ln->data[from]) && from < ln->size )
161       from++;
162   }
163 
164   return from;
165 }
166 
167 		 /*******************************
168 		 *       EDITING FUNCTIONS	*
169 		 *******************************/
170 
171 static __inline void
changed(Line ln,size_t from)172 changed(Line ln, size_t from)
173 { ln->change_start = min(ln->change_start, from);
174 }
175 
176 
177 static void
insert_self(Line ln,int chr)178 insert_self(Line ln, int chr)
179 { make_room(ln, 1);
180   ln->data[ln->point++] = chr;
181 }
182 
183 
184 static void
backward_delete_character(Line ln,int chr)185 backward_delete_character(Line ln, int chr)
186 { if ( ln->point > 0 )
187   { memmove(&ln->data[ln->point-1], &ln->data[ln->point],
188 	    (ln->size - ln->point)*sizeof(TCHAR));
189     ln->size--;
190     ln->point--;
191   }
192 
193   changed(ln, ln->point);
194 }
195 
196 
197 static void
delete_character(Line ln,int chr)198 delete_character(Line ln, int chr)
199 { if ( ln->point < ln->size )
200   { ln->point++;
201     backward_delete_character(ln, chr);
202   }
203 }
204 
205 
206 static void
backward_character(Line ln,int chr)207 backward_character(Line ln, int chr)
208 { if ( ln->point > 0 )
209     ln->point--;
210 }
211 
212 
213 static void
forward_character(Line ln,int chr)214 forward_character(Line ln, int chr)
215 { if ( ln->point < ln->size )
216     ln->point++;
217 }
218 
219 
220 static void
backward_word(Line ln,int chr)221 backward_word(Line ln, int chr)
222 { ln->point = back_word(ln, ln->point);
223 }
224 
225 
226 static void
forward_word(Line ln,int chr)227 forward_word(Line ln, int chr)
228 { ln->point = forw_word(ln, ln->point);
229 }
230 
231 
232 static void
backward_delete_word(Line ln,int chr)233 backward_delete_word(Line ln, int chr)
234 { size_t from = back_word(ln, ln->point);
235 
236   memmove(&ln->data[from], &ln->data[ln->point],
237 	  (ln->size - ln->point)*sizeof(TCHAR));
238   ln->size -= ln->point - from;
239   ln->point = from;
240   changed(ln, from);
241 }
242 
243 
244 static void
forward_delete_word(Line ln,int chr)245 forward_delete_word(Line ln, int chr)
246 { size_t to = forw_word(ln, ln->point);
247 
248   memmove(&ln->data[ln->point], &ln->data[to], (ln->size - to)*sizeof(TCHAR));
249   ln->size -= to - ln->point;
250   changed(ln, ln->point);
251 }
252 
253 
254 static void
transpose_chars(Line ln,int chr)255 transpose_chars(Line ln, int chr)
256 { if ( ln->point > 0 && ln->point < ln->size )
257   { int c0 = ln->data[ln->point-1];
258     ln->data[ln->point-1] = ln->data[ln->point];
259     ln->data[ln->point] = c0;
260     changed(ln, ln->point-1);
261   }
262 }
263 
264 
265 static void
start_of_line(Line ln,int chr)266 start_of_line(Line ln, int chr)
267 { ln->point = 0;
268 }
269 
270 
271 static void
end_of_line(Line ln,int chr)272 end_of_line(Line ln, int chr)
273 { ln->point = ln->size;
274 }
275 
276 
277 static void
kill_line(Line ln,int chr)278 kill_line(Line ln, int chr)
279 { ln->size = ln->point;
280   changed(ln, ln->size);
281 }
282 
283 
284 static void
empty_line(Line ln,int chr)285 empty_line(Line ln, int chr)
286 { ln->size = ln->point = 0;
287   changed(ln, 0);
288 }
289 
290 
291 static void
enter(Line ln,int chr)292 enter(Line ln, int chr)
293 { ln->point = ln->size;
294 #ifdef DOS_CRNL
295   make_room(ln, 2);
296   ln->data[ln->point++] = '\r';
297   ln->data[ln->point++] = '\n';
298 #else
299   make_room(ln, 1);
300   ln->data[ln->point++] = '\n';
301 #endif
302   terminate(ln);
303   ln->complete = COMPLETE_NEWLINE;
304 }
305 
306 
307 static void
eof(Line ln,int chr)308 eof(Line ln, int chr)
309 { ln->point = ln->size;
310   terminate(ln);
311   ln->complete = COMPLETE_EOF;
312 }
313 
314 
315 static void
delete_character_or_eof(Line ln,int chr)316 delete_character_or_eof(Line ln, int chr)
317 { if ( ln->size == 0 )
318   { ln->point = ln->size;
319     terminate(ln);
320     ln->complete = COMPLETE_EOF;
321   } else
322     delete_character(ln, chr);
323 }
324 
325 
326 static void
undefined(Line ln,int chr)327 undefined(Line ln, int chr)
328 {
329 }
330 
331 
332 static void
interrupt(Line ln,int chr)333 interrupt(Line ln, int chr)
334 { raise(SIGINT);
335 }
336 
337 		 /*******************************
338 		 *		HISTORY		*
339 		 *******************************/
340 
341 static void
add_history(rlc_console c,const TCHAR * data)342 add_history(rlc_console c, const TCHAR *data)
343 { const TCHAR *s = data;
344 
345   while(*s && *s <= ' ')
346     s++;
347 
348   if ( *s )
349     rlc_add_history(c, s);
350 }
351 
352 
353 static void
backward_history(Line ln,int chr)354 backward_history(Line ln, int chr)
355 { const TCHAR *h;
356 
357   if ( rlc_at_head_history(ln->console) && ln->size > 0 )
358   { terminate(ln);
359     add_history(ln->console, ln->data);
360   }
361 
362   if ( (h = rlc_bwd_history(ln->console)) )
363   { set_line(ln, h);
364     ln->point = ln->size;
365   }
366 }
367 
368 
369 static void
forward_history(Line ln,int chr)370 forward_history(Line ln, int chr)
371 { if ( !rlc_at_head_history(ln->console) )
372   { const TCHAR *h = rlc_fwd_history(ln->console);
373 
374     if ( h )
375     { set_line(ln, h);
376       ln->point = ln->size;
377     }
378   } else
379     empty_line(ln, chr);
380 }
381 
382 		 /*******************************
383 		 *	      COMPLETE		*
384 		 *******************************/
385 
386 RlcCompleteFunc
rlc_complete_hook(RlcCompleteFunc new)387 rlc_complete_hook(RlcCompleteFunc new)
388 { RlcCompleteFunc old = _rlc_complete_function;
389 
390   _rlc_complete_function = new;
391 
392   return old;
393 }
394 
395 
396 static int
common(const TCHAR * s1,const TCHAR * s2,int insensitive)397 common(const TCHAR *s1, const TCHAR *s2, int insensitive)
398 { int n = 0;
399 
400   if ( !insensitive )
401   { while(*s1 && *s1 == *s2)
402     { s1++, s2++;
403       n++;
404     }
405     return n;
406   } else
407   { while(*s1)
408     { if ( _totlower(*s1) == _totlower(*s2) )
409       { s1++, s2++;
410 	n++;
411       } else
412 	break;
413     }
414     return n;
415   }
416 }
417 
418 
419 static void
complete(Line ln,int chr)420 complete(Line ln, int chr)
421 { if ( _rlc_complete_function )
422   { rlc_complete_data dbuf;
423     RlcCompleteData data = &dbuf;
424 
425     memset(data, 0, sizeof(dbuf));
426     data->line      = ln;
427     data->call_type = COMPLETE_INIT;
428 
429     if ( (*_rlc_complete_function)(data) )
430     { TCHAR match[COMPLETE_MAX_WORD_LEN];
431       int nmatches = 1;
432       size_t ncommon = _tcslen(data->candidate);
433       size_t patlen = ln->point - data->replace_from;
434 
435       _tcscpy(match, data->candidate);
436 
437       data->call_type = COMPLETE_ENUMERATE;
438       while( (*data->function)(data) )
439       { ncommon = common(match, data->candidate, data->case_insensitive);
440 	match[ncommon] = EOS;
441 	nmatches++;
442       }
443       data->call_type = COMPLETE_CLOSE;
444       (*data->function)(data);
445 
446       delete(ln, data->replace_from, patlen);
447       ln->point = data->replace_from;
448       make_room(ln, ncommon);
449       _tcsncpy(&ln->data[data->replace_from], match, ncommon);
450       ln->point += ncommon;
451       if ( nmatches == 1 && data->quote )
452 	insert_self(ln, data->quote);
453     }
454   }
455 }
456 
457 #define MAX_LIST_COMPLETIONS 256
458 
459 static void
list_completions(Line ln,int chr)460 list_completions(Line ln, int chr)
461 { if ( _rlc_complete_function )
462   { rlc_complete_data dbuf;
463     RlcCompleteData data = &dbuf;
464 
465     memset(data, 0, sizeof(dbuf));
466     data->line      = ln;
467     data->call_type = COMPLETE_INIT;
468 
469     if ( (*_rlc_complete_function)(data) )
470     { TCHAR *buf[COMPLETE_MAX_MATCHES];
471       int n, nmatches = 0;
472       size_t len = _tcslen(data->candidate) + 1;
473       size_t longest = len;
474       size_t cols;
475 
476       buf[nmatches] = rlc_malloc(len*sizeof(TCHAR));
477       _tcsncpy(buf[nmatches], data->candidate, len);
478       nmatches++;
479 
480       data->call_type = COMPLETE_ENUMERATE;
481       while( (*data->function)(data) )
482       { len = _tcslen(data->candidate) + 1;
483 	buf[nmatches] = rlc_malloc(len*sizeof(TCHAR));
484 	_tcsncpy(buf[nmatches], data->candidate, len);
485 	nmatches++;
486 	longest = max(longest, len);
487 
488 	if ( nmatches > COMPLETE_MAX_MATCHES )
489 	{ TCHAR *msg = _T("\r\n! Too many matches\r\n");
490 
491 	  while(*msg)
492 	    rlc_putchar(ln->console, *msg++);
493 	  ln->reprompt = TRUE;
494 	  data->call_type = COMPLETE_CLOSE;
495 	  (*data->function)(data);
496 	  return;
497 	}
498       }
499       data->call_type = COMPLETE_CLOSE;
500       (*data->function)(data);
501 
502       cols = ScreenCols(ln->console) / longest;
503       rlc_putchar(ln->console, '\r');
504       rlc_putchar(ln->console, '\n');
505 
506       for(n=0; n<nmatches; )
507       { TCHAR *s = buf[n];
508 	len = 0;
509 
510 	while(*s)
511 	{ len++;
512 	  rlc_putchar(ln->console, *s++);
513 	}
514 
515 	rlc_free(buf[n++]);
516 
517 	if ( n % cols == 0 )
518 	{ rlc_putchar(ln->console, '\r');
519 	  rlc_putchar(ln->console, '\n');
520 	} else
521 	{ while( len++ < longest )
522 	  rlc_putchar(ln->console, ' ');
523 	}
524       }
525       if ( nmatches % cols != 0 )
526       { rlc_putchar(ln->console, '\r');
527 	rlc_putchar(ln->console, '\n');
528       }
529 
530       ln->reprompt = TRUE;
531     }
532   }
533 }
534 
535 
536 		 /*******************************
537 		 *	      REPAINT		*
538 		 *******************************/
539 
540 static void
output(rlc_console b,TCHAR * s,size_t len)541 output(rlc_console b, TCHAR *s, size_t len)
542 { while(len-- > 0)
543   { if ( *s == '\n' )
544       rlc_putchar(b, '\r');
545     rlc_putchar(b, *s++);
546   }
547 }
548 
549 
550 static void
update_display(Line ln)551 update_display(Line ln)
552 { if ( ln->reprompt )
553   { const TCHAR *prompt = rlc_prompt(ln->console, NULL);
554     const TCHAR *s = prompt;
555 
556     rlc_putchar(ln->console, '\r');
557     while(*s)
558       rlc_putchar(ln->console, *s++);
559 
560     rlc_get_mark(ln->console, &ln->origin);
561 
562     ln->change_start = 0;
563     ln->reprompt = FALSE;
564   }
565 
566   rlc_goto_mark(ln->console, &ln->origin, ln->data, ln->change_start);
567   output(ln->console,
568 	 &ln->data[ln->change_start], ln->size - ln->change_start);
569   rlc_erase_from_caret(ln->console);
570   rlc_goto_mark(ln->console, &ln->origin, ln->data, ln->point);
571   rlc_update(ln->console);
572 
573   ln->change_start = ln->size;
574 }
575 
576 		 /*******************************
577 		 *	     TOPLEVEL		*
578 		 *******************************/
579 
580 TCHAR *
read_line(rlc_console b)581 read_line(rlc_console b)
582 { line ln;
583 
584   init_line_package(b);
585 
586   memset(&ln, 0, sizeof(line));
587   ln.console = b;
588   rlc_get_mark(b, &ln.origin);
589 
590   while(!ln.complete)
591   { int c;
592     rlc_mark m0, m1;
593     function func;
594 
595     rlc_get_mark(b, &m0);
596     if ( (c = getch(b)) == IMODE_SWITCH_CHAR )
597       return RL_CANCELED_CHARP;
598 
599     if ( c == EOF )
600     { eof(&ln, c);
601       update_display(&ln);
602       break;
603     } else if ( c == ESC )
604     { if ( (c = getch(b)) == IMODE_SWITCH_CHAR )
605 	return RL_CANCELED_CHARP;
606       if ( c > 256 )
607 	func = undefined;
608       else
609 	func = dispatch_meta[c&0xff];
610     } else
611     { if ( c >= 256 )
612 	func = insert_self;
613       else
614 	func = dispatch_table[c&0xff];
615     }
616 
617     rlc_get_mark(b, &m1);
618 
619     (*func)(&ln, c);
620     if ( m0.mark_x != m1.mark_x || m0.mark_y != m1.mark_y )
621       ln.reprompt = TRUE;
622     update_display(&ln);
623   }
624   rlc_clearprompt(b);
625 
626   add_history(b, ln.data);
627 
628   return ln.data;
629 }
630 
631 
632 		 /*******************************
633 		 *	     DISPATCH		*
634 		 *******************************/
635 
636 static void
init_dispatch_table()637 init_dispatch_table()
638 { static int done;
639 
640   if ( !done )
641   { int n;
642 
643     for(n=0; n<32; n++)
644       dispatch_table[n] = undefined;
645     for(n=32; n<256; n++)
646       dispatch_table[n] = insert_self;
647     for(n=0; n<256; n++)
648       dispatch_meta[n] = undefined;
649 
650     bind_actions();
651 
652     done = TRUE;
653   }
654 }
655 
656 
657 static void
init_line_package(RlcData b)658 init_line_package(RlcData b)
659 { init_dispatch_table();
660   rlc_init_history(b, 50);
661 }
662 
663 		 /*******************************
664 		 *	       BIND		*
665 		 *******************************/
666 
667 typedef struct _action
668 { char		*name;
669   function	 function;
670   unsigned char  keys[4];
671 } action, *Action;
672 
673 #define ACTION(n, f, k) { n, f, k }
674 
675 static action actions[] = {
676   ACTION("insert_self",		      insert_self,		 ""),
677   ACTION("backward_delete_character", backward_delete_character, "\b"),
678   ACTION("complete",		      complete,			 "\t"),
679   ACTION("enter",		      enter, 			 "\r\n"),
680   ACTION("start_of_line",	      start_of_line,		 {ctrl('A')}),
681   ACTION("backward_character",	      backward_character,        {ctrl('B')}),
682   ACTION("interrupt",		      interrupt,		 {ctrl('C')}),
683   ACTION("end_of_line",		      end_of_line,		 {ctrl('E')}),
684   ACTION("forward_character",	      forward_character,	 {ctrl('F')}),
685   ACTION("transpose_chars",	      transpose_chars,		 {ctrl('T')}),
686   ACTION("kill_line",		      kill_line,		 {ctrl('K')}),
687   ACTION("backward_history",	      backward_history,		 {ctrl('P')}),
688   ACTION("forward_history",	      forward_history,		 {ctrl('N')}),
689   ACTION("empty_line",		      empty_line,		 {ctrl('U')}),
690   ACTION("eof",			      eof,			 {ctrl('Z')}),
691 
692   ACTION("delete_character_or_eof",   delete_character_or_eof,	 {ctrl('D')}),
693   ACTION("delete_character",	      delete_character,		 {127}),
694   { "forward_word",		forward_word,  {meta(ctrl('F')), meta('f')}},
695   { "backward_word",		backward_word, {meta(ctrl('B')), meta('b')}},
696   { "forward_delete_word",	forward_delete_word, {meta(127), meta('d')}},
697   ACTION("list_completions",	      list_completions,     {meta('?')}),
698   ACTION("backward_delete_word",      backward_delete_word, {meta('\b')}),
699 
700   ACTION(NULL,			      NULL,			 "")
701 };
702 
703 int
rlc_bind(int chr,const char * fname)704 rlc_bind(int chr, const char *fname)
705 { if ( chr >= 0 && chr <= 256 )
706   { Action a = actions;
707 
708     for( ; a->name; a++ )
709     { if ( strcmp(a->name, fname) == 0 )
710       { if ( chr > META_OFFSET )
711 	  dispatch_meta[chr-META_OFFSET] = a->function;
712 	else
713 	  dispatch_table[chr] = a->function;
714 
715 	return TRUE;
716       }
717     }
718   }
719 
720   return FALSE;
721 }
722 
723 static void
bind_actions()724 bind_actions()
725 { Action a = actions;
726 
727   for( ; a->name; a++ )
728   { unsigned char *k = a->keys;
729 
730     for( ; *k; k++ )
731     { int chr = *k & 0xff;
732 
733       if ( chr > META_OFFSET )
734 	dispatch_meta[chr-META_OFFSET] = a->function;
735       else
736 	dispatch_table[chr] = a->function;
737     }
738   }
739 }
740 
741 
742