36
37:- module(editline,
38 [ el_wrap/0, 39 el_wrap/1, 40 el_wrap/4, 41 el_wrap/5, 42 el_wrapped/1, 43 el_unwrap/1, 44
45 el_source/2, 46 el_bind/2, 47 el_set/2, 48 el_get/2, 49 el_addfn/4, 50 el_cursor/2, 51 el_line/2, 52 el_insertstr/2, 53 el_deletestr/2, 54
55 el_history/2, 56 el_history_events/2, 57 el_add_history/2, 58 el_write_history/2, 59 el_read_history/2, 60
61 el_version/1 62 ]). 63:- autoload(library(apply),[maplist/2,maplist/3]). 64:- autoload(library(lists),[reverse/2,max_list/2,append/3,member/2]). 65:- autoload(library(solution_sequences),[call_nth/2]). 66:- autoload(library(option), [merge_options/3]). 67
68:- use_foreign_library(foreign(libedit4pl)). 69
70:- initialization el_wrap_if_ok. 71
72:- meta_predicate
73 el_addfn(+,+,+,3). 74
75:- multifile
76 el_setup/1, 77 prolog:complete_input/4. 78
79
87
88el_wrap_if_ok :-
89 \+ current_prolog_flag(readline, readline),
90 stream_property(user_input, tty(true)),
91 !,
92 el_wrap.
93el_wrap_if_ok.
94
118
119el_wrap :-
120 el_wrap([]).
121
122el_wrap(_) :-
123 el_wrapped(user_input),
124 !.
125el_wrap(Options) :-
126 stream_property(user_input, tty(true)), !,
127 findall(Opt, el_default(Opt), Defaults),
128 merge_options(Options, Defaults, Options1),
129 el_wrap(swipl, user_input, user_output, user_error, Options1),
130 add_prolog_commands(user_input),
131 ignore(el_set(user_input, wordchars("_"))),
132 forall(el_setup(user_input), true),
133 enable_bracketed_paste(user_input).
134el_wrap(_).
135
136el_default(history(Size)) :-
137 current_prolog_flag(history, Value),
138 ( integer(Value),
139 Value >= 0
140 -> Size = Value
141 ; Value == false
142 -> Size = 0
143 ).
144:- if(current_predicate(prolog_alert_signal/2)). 145el_default(alert_signo(SigNo)) :-
146 prolog_alert_signal(SigName, SigName),
147 current_signal(SigName, SigNo, _Handler).
148:- endif. 149
150add_prolog_commands(Input) :-
151 el_addfn(Input, complete, 'Complete atoms and files', complete),
152 el_addfn(Input, show_completions, 'List completions', show_completions),
153 el_addfn(Input, electric, 'Indicate matching bracket', electric),
154 el_addfn(Input, isearch_history, 'Incremental search in history',
155 isearch_history),
156 el_addfn(Input, bracketed_paste, 'Handle bracketed paste', bracketed_paste),
157 el_bind(Input, ["^I", complete]),
158 el_bind(Input, ["^[?", show_completions]),
159 el_bind(Input, ["^R", isearch_history]),
160 bind_electric(Input),
161 add_paste_quoted(Input),
162 el_source(Input, _).
163
172
173enable_bracketed_paste(Input) :-
174 ( el_get(Input, editor(vi))
175 -> el_bind(Input, ['-r', "\e[200~"]),
176 el_set(Input, bracketed_paste(false))
177 ; el_bind(Input, ["\e[200~", bracketed_paste]),
178 el_set(Input, bracketed_paste(true))
179 ).
180
193
194el_wrap(ProgName, In, Out, Error) :-
195 el_wrap(ProgName, In, Out, Error, []).
196
203
207
215
220
221
238
268
289
302
308
313
317
321
358
364
368
374
381
388
393
394:- multifile
395 prolog:history/2. 396
397prolog:history(Input, enabled) :-
398 !,
399 el_wrapped(Input),
400 el_history(Input, getsize(Size)),
401 Size > 0.
402prolog:history(Input, add(Line)) :-
403 !,
404 el_add_history(Input, Line).
405prolog:history(Input, load(File)) :-
406 !,
407 compat_read_history(Input, File).
408prolog:history(Input, save(File)) :-
409 !,
410 el_write_history(Input, File).
411prolog:history(Input, events(Events)) :-
412 !,
413 el_history_events(Input, Events).
414prolog:history(Input, Command) :-
415 public_command(Command),
416 !,
417 el_history(Input, Command).
418
419public_command(first(_Num, _String)).
420public_command(curr(_Num, _String)).
421public_command(event(_Num, _String)).
422public_command(prev_str(_Search, _Num, _String)).
423public_command(clear).
424
429
430compat_read_history(Input, File) :-
431 catch(el_read_history(Input, File), error(editline(_),_), fail),
432 !.
433compat_read_history(Input, File) :-
434 access_file(File, read),
435 setup_call_cleanup(
436 open(File, read, In, [encoding(utf8)]),
437 read_old_history(Input, In),
438 close(In)),
439 !.
440compat_read_history(_, _).
441
442read_old_history(Input, From) :-
443 catch('$raw_read'(From, Line), error(_,_), fail),
444 ( Line == end_of_file
445 -> true
446 ; string_concat(Line, '.', Event),
447 el_add_history(Input, Event),
448 read_old_history(Input, From)
449 ).
450
451 454
458
459bind_electric(Input) :-
460 forall(bracket(_Open, Close), bind_code(Input, Close, electric)),
461 forall(quote(Close), bind_code(Input, Close, electric)).
462
463bind_code(Input, Code, Command) :-
464 string_codes(Key, [Code]),
465 el_bind(Input, [Key, Command]).
466
467
469
470electric(Input, Char, Continue) :-
471 string_codes(Str, [Char]),
472 el_insertstr(Input, Str),
473 el_line(Input, line(Before, _)),
474 ( string_codes(Before, Codes),
475 nesting(Codes, 0, Nesting),
476 reverse(Nesting, [Close|RevNesting])
477 -> ( Close = open(_,_) 478 -> Continue = refresh
479 ; matching_open(RevNesting, Close, _, Index)
480 -> string_length(Before, Len), 481 Move is Index-Len,
482 Continue = electric(Move, 500, refresh)
483 ; Continue = refresh_beep 484 )
485 ; Continue = refresh_beep
486 ).
487
488matching_open_index(String, Index) :-
489 string_codes(String, Codes),
490 nesting(Codes, 0, Nesting),
491 reverse(Nesting, [Close|RevNesting]),
492 matching_open(RevNesting, Close, _, Index).
493
494matching_open([Open|Rest], Close, Rest, Index) :-
495 Open = open(Index,_),
496 match(Open, Close),
497 !.
498matching_open([Close1|Rest1], Close, Rest, Index) :-
499 Close1 = close(_,_),
500 matching_open(Rest1, Close1, Rest2, _),
501 matching_open(Rest2, Close, Rest, Index).
502
503match(open(_,Open),close(_,Close)) :-
504 ( bracket(Open, Close)
505 -> true
506 ; Open == Close,
507 quote(Open)
508 ).
509
510bracket(0'(, 0')).
511bracket(0'[, 0']).
512bracket(0'{, 0'}).
513
514quote(0'\').
515quote(0'\").
516quote(0'\`).
517
518nesting([], _, []).
519nesting([H|T], I, Nesting) :-
520 ( bracket(H, _Close)
521 -> Nesting = [open(I,H)|Nest]
522 ; bracket(_Open, H)
523 -> Nesting = [close(I,H)|Nest]
524 ),
525 !,
526 I2 is I+1,
527 nesting(T, I2, Nest).
528nesting([0'0, 0'\'|T], I, Nesting) :-
529 !,
530 phrase(skip_code, T, T1),
531 difflist_length(T, T1, Len),
532 I2 is I+Len+2,
533 nesting(T1, I2, Nesting).
534nesting([H|T], I, Nesting) :-
535 quote(H),
536 !,
537 ( phrase(skip_quoted(H), T, T1)
538 -> difflist_length(T, T1, Len),
539 I2 is I+Len+1,
540 Nesting = [open(I,H),close(I2,H)|Nest],
541 nesting(T1, I2, Nest)
542 ; Nesting = [open(I,H)] 543 ).
544nesting([_|T], I, Nesting) :-
545 I2 is I+1,
546 nesting(T, I2, Nesting).
547
548difflist_length(List, Tail, Len) :-
549 difflist_length(List, Tail, 0, Len).
550
551difflist_length(List, Tail, Len0, Len) :-
552 List == Tail,
553 !,
554 Len = Len0.
555difflist_length([_|List], Tail, Len0, Len) :-
556 Len1 is Len0+1,
557 difflist_length(List, Tail, Len1, Len).
558
559skip_quoted(H) -->
560 [H],
561 !.
562skip_quoted(H) -->
563 "\\", [H],
564 !,
565 skip_quoted(H).
566skip_quoted(H) -->
567 [_],
568 skip_quoted(H).
569
570skip_code -->
571 "\\", [_],
572 !.
573skip_code -->
574 [_].
575
576
577 580
588
589
590:- dynamic
591 last_complete/2. 592
593complete(Input, _Char, Continue) :-
594 el_line(Input, line(Before, After)),
595 ensure_input_completion,
596 prolog:complete_input(Before, After, Delete, Completions),
597 ( Completions = [One]
598 -> string_length(Delete, Len),
599 el_deletestr(Input, Len),
600 complete_text(One, Text),
601 el_insertstr(Input, Text),
602 Continue = refresh
603 ; Completions == []
604 -> Continue = refresh_beep
605 ; get_time(Now),
606 retract(last_complete(TLast, Before)),
607 Now - TLast < 2
608 -> nl(user_error),
609 list_alternatives(Completions),
610 Continue = redisplay
611 ; retractall(last_complete(_,_)),
612 get_time(Now),
613 asserta(last_complete(Now, Before)),
614 common_competion(Completions, Extend),
615 ( Delete == Extend
616 -> Continue = refresh_beep
617 ; string_length(Delete, Len),
618 el_deletestr(Input, Len),
619 el_insertstr(Input, Extend),
620 Continue = refresh
621 )
622 ).
623
624:- dynamic
625 input_completion_loaded/0. 626
627ensure_input_completion :-
628 input_completion_loaded,
629 !.
630ensure_input_completion :-
631 predicate_property(prolog:complete_input(_,_,_,_),
632 number_of_clauses(N)),
633 N > 0,
634 !.
635ensure_input_completion :-
636 exists_source(library(console_input)),
637 !,
638 use_module(library(console_input), []),
639 asserta(input_completion_loaded).
640ensure_input_completion.
641
642
646
647show_completions(Input, _Char, Continue) :-
648 el_line(Input, line(Before, After)),
649 prolog:complete_input(Before, After, _Delete, Completions),
650 nl(user_error),
651 list_alternatives(Completions),
652 Continue = redisplay.
653
654complete_text(Text-_Comment, Text) :- !.
655complete_text(Text, Text).
656
660
661common_competion(Alternatives, Common) :-
662 maplist(atomic, Alternatives),
663 !,
664 common_prefix(Alternatives, Common).
665common_competion(Alternatives, Common) :-
666 maplist(complete_text, Alternatives, AltText),
667 !,
668 common_prefix(AltText, Common).
669
673
674common_prefix([A1|T], Common) :-
675 common_prefix_(T, A1, Common).
676
677common_prefix_([], Common, Common).
678common_prefix_([H|T], Common0, Common) :-
679 common_prefix(H, Common0, Common1),
680 common_prefix_(T, Common1, Common).
681
685
686common_prefix(A1, A2, Prefix) :-
687 sub_atom(A1, 0, _, _, A2),
688 !,
689 Prefix = A2.
690common_prefix(A1, A2, Prefix) :-
691 sub_atom(A2, 0, _, _, A1),
692 !,
693 Prefix = A1.
694common_prefix(A1, A2, Prefix) :-
695 atom_codes(A1, C1),
696 atom_codes(A2, C2),
697 list_common_prefix(C1, C2, C),
698 string_codes(Prefix, C).
699
700list_common_prefix([H|T0], [H|T1], [H|T]) :-
701 !,
702 list_common_prefix(T0, T1, T).
703list_common_prefix(_, _, []).
704
705
706
712
713list_alternatives(Alternatives) :-
714 maplist(atomic, Alternatives),
715 !,
716 length(Alternatives, Count),
717 maplist(atom_length, Alternatives, Lengths),
718 max_list(Lengths, Max),
719 tty_size(_, Cols),
720 ColW is Max+2,
721 Columns is max(1, Cols // ColW),
722 RowCount is (Count+Columns-1)//Columns,
723 length(Rows, RowCount),
724 to_matrix(Alternatives, Rows, Rows),
725 ( RowCount > 11
726 -> length(First, 10),
727 Skipped is RowCount - 10,
728 append(First, _, Rows),
729 maplist(write_row(ColW), First),
730 format(user_error, '... skipped ~D rows~n', [Skipped])
731 ; maplist(write_row(ColW), Rows)
732 ).
733list_alternatives(Alternatives) :-
734 maplist(complete_text, Alternatives, AltText),
735 list_alternatives(AltText).
736
737to_matrix([], _, Rows) :-
738 !,
739 maplist(close_list, Rows).
740to_matrix([H|T], [RH|RT], Rows) :-
741 !,
742 add_list(RH, H),
743 to_matrix(T, RT, Rows).
744to_matrix(List, [], Rows) :-
745 to_matrix(List, Rows, Rows).
746
747add_list(Var, Elem) :-
748 var(Var), !,
749 Var = [Elem|_].
750add_list([_|T], Elem) :-
751 add_list(T, Elem).
752
753close_list(List) :-
754 append(List, [], _),
755 !.
756
757write_row(ColW, Row) :-
758 length(Row, Columns),
759 make_format(Columns, ColW, Format),
760 format(user_error, Format, Row).
761
762make_format(N, ColW, Format) :-
763 format(string(PerCol), '~~w~~t~~~d+', [ColW]),
764 Front is N - 1,
765 length(LF, Front),
766 maplist(=(PerCol), LF),
767 append(LF, ['~w~n'], Parts),
768 atomics_to_string(Parts, Format).
769
770
771 774
779
780isearch_history(Input, _Char, Continue) :-
781 el_line(Input, line(Before, After)),
782 string_concat(Before, After, Current),
783 string_length(Current, Len),
784 search_print('', "", Current),
785 search(Input, "", Current, 1, Line),
786 el_deletestr(Input, Len),
787 el_insertstr(Input, Line),
788 Continue = redisplay.
789
790search(Input, For, Current, Nth, Line) :-
791 el_getc(Input, Next),
792 Next \== -1,
793 !,
794 search(Next, Input, For, Current, Nth, Line).
795search(_Input, _For, _Current, _Nth, "").
796
797search(7, _Input, _, Current, _, Current) :- 798 !,
799 clear_line.
800search(18, Input, For, Current, Nth, Line) :- 801 !,
802 N2 is Nth+1,
803 search_(Input, For, Current, N2, Line).
804search(19, Input, For, Current, Nth, Line) :- 805 !,
806 N2 is max(1,Nth-1),
807 search_(Input, For, Current, N2, Line).
808search(127, Input, For, Current, _Nth, Line) :- 809 sub_string(For, 0, _, 1, For1),
810 !,
811 search_(Input, For1, Current, 1, Line).
812search(Char, Input, For, Current, Nth, Line) :-
813 code_type(Char, cntrl),
814 !,
815 search_end(Input, For, Current, Nth, Line),
816 el_push(Input, Char).
817search(Char, Input, For, Current, _Nth, Line) :-
818 format(string(For1), '~w~c', [For,Char]),
819 search_(Input, For1, Current, 1, Line).
820
821search_(Input, For1, Current, Nth, Line) :-
822 ( find_in_history(Input, For1, Current, Nth, Candidate)
823 -> search_print('', For1, Candidate)
824 ; search_print('failed ', For1, Current)
825 ),
826 search(Input, For1, Current, Nth, Line).
827
828search_end(Input, For, Current, Nth, Line) :-
829 ( find_in_history(Input, For, Current, Nth, Line)
830 -> true
831 ; Line = Current
832 ),
833 clear_line.
834
835find_in_history(_, "", Current, _, Current) :-
836 !.
837find_in_history(Input, For, _, Nth, Line) :-
838 el_history_events(Input, History),
839 call_nth(( member(_N-Line, History),
840 sub_string(Line, _, _, _, For)
841 ),
842 Nth),
843 !.
844
845search_print(State, Search, Current) :-
846 format(user_error, '\r(~wreverse-i-search)`~w\': ~w\e[0K',
847 [State, Search, Current]).
848
849clear_line :-
850 format(user_error, '\r\e[0K', []).
851
852
853 856
857:- meta_predicate
858 with_quote_flags(+,+,0). 859
860add_paste_quoted(Input) :-
861 current_prolog_flag(gui, true),
862 !,
863 el_addfn(Input, paste_quoted, 'Paste as quoted atom', paste_quoted),
864 el_bind(Input, ["^Y", paste_quoted]).
865add_paste_quoted(_).
866
872
873paste_quoted(Input, _Char, Continue) :-
874 clipboard_content(String),
875 quote_text(Input, String, Quoted),
876 el_insertstr(Input, Quoted),
877 Continue = refresh.
878
879quote_text(Input, String, Value) :-
880 el_line(Input, line(Before, _After)),
881 ( sub_string(Before, _, 1, 0, Quote)
882 -> true
883 ; Quote = "'"
884 ),
885 quote_text(Input, Quote, String, Value).
886
887quote_text(Input, "'", Text, Quoted) =>
888 format(string(Quoted), '~q', [Text]),
889 el_deletestr(Input, 1).
890quote_text(Input, "\"", Text, Quoted) =>
891 atom_string(Text, String),
892 with_quote_flags(
893 string, codes,
894 format(string(Quoted), '~q', [String])),
895 el_deletestr(Input, 1).
896quote_text(Input, "`", Text, Quoted) =>
897 atom_string(Text, String),
898 with_quote_flags(
899 codes, string,
900 format(string(Quoted), '~q', [String])),
901 el_deletestr(Input, 1).
902quote_text(_, _, Text, Quoted) =>
903 format(string(Quoted), '~q', [Text]).
904
905with_quote_flags(Double, Back, Goal) :-
906 current_prolog_flag(double_quotes, ODouble),
907 current_prolog_flag(back_quotes, OBack),
908 setup_call_cleanup(
909 ( set_prolog_flag(double_quotes, Double),
910 set_prolog_flag(back_quotes, Back) ),
911 Goal,
912 ( set_prolog_flag(double_quotes, ODouble),
913 set_prolog_flag(back_quotes, OBack) )).
914
915clipboard_content(Text) :-
916 current_prolog_flag(gui, true),
917 !,
918 autoload_call(in_pce_thread_sync(
919 autoload_call(
920 get(@(display), paste, primary, string(Text))))).
921clipboard_content("").
922
923
924 927
936
937bracketed_paste(Input, _Char, Continue) :-
938 collect_paste(Input, [], RevCodes),
939 reverse(RevCodes, Codes),
940 string_codes(Text, Codes),
941 el_insertstr(Input, Text),
942 Continue = refresh.
943
950
951collect_paste(Input, RevCodes, Result) :-
952 el_getc(Input, Char),
953 ( Char == -1 954 -> Result = RevCodes
955 ; paste_char(Char, Char1),
956 RevCodes1 = [Char1|RevCodes],
957 ( RevCodes1 = [0'~,0'1,0'0,0'2,0'[,0'\e|Rest] 958 -> Result = Rest
959 ; collect_paste(Input, RevCodes1, Result)
960 )
961 ).
962
969
970paste_char(0'\r, 0'\n) :- !. 971paste_char(C, C).
972
973
974 977
978:- multifile prolog:error_message//1. 979
980prolog:error_message(editline(Msg)) -->
981 [ 'editline: ~s'-[Msg] ]