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