1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2017-2025, VU University Amsterdam 7 CWI Amsterdam 8 SWI-Prolog Solutions b.v. 9 All rights reserved. 10 11 Redistribution and use in source and binary forms, with or without 12 modification, are permitted provided that the following conditions 13 are met: 14 15 1. Redistributions of source code must retain the above copyright 16 notice, this list of conditions and the following disclaimer. 17 18 2. Redistributions in binary form must reproduce the above copyright 19 notice, this list of conditions and the following disclaimer in 20 the documentation and/or other materials provided with the 21 distribution. 22 23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 33 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 POSSIBILITY OF SUCH DAMAGE. 35*/ 36 37:- module(editline, 38 [ el_wrap/0, % wrap user_input, etc. 39 el_wrap/4, % +Prog, +Input, +Output, +Error 40 el_wrapped/1, % +Input 41 el_unwrap/1, % +Input 42 43 el_source/2, % +Input, +File 44 el_bind/2, % +Input, +Args 45 el_addfn/4, % +Input, +Name, +Help, :Goal 46 el_cursor/2, % +Input, +Move 47 el_line/2, % +Input, -Line 48 el_insertstr/2, % +Input, +Text 49 el_deletestr/2, % +Input, +Count 50 51 el_history/2, % +Input, ?Action 52 el_history_events/2, % +Input, -Events 53 el_add_history/2, % +Input, +Line 54 el_write_history/2, % +Input, +FileName 55 el_read_history/2 % +Input, +FileName 56 ]). 57:- autoload(library(apply),[maplist/2,maplist/3]). 58:- autoload(library(lists),[reverse/2,max_list/2,append/3,member/2]). 59:- autoload(library(solution_sequences),[call_nth/2]). 60:- if(current_prolog_flag(gui, true)). 61:- autoload(library(pce), [get/3, in_pce_thread_sync/1]). 62:- endif. 63 64editline_ok :- 65 \+ current_prolog_flag(console_menu_version, qt), 66 \+ current_prolog_flag(readline, readline), 67 stream_property(user_input, tty(true)). 68 69:- use_foreign_library(foreign(libedit4pl)). 70 71:- if(editline_ok). 72:- initialization el_wrap. 73:- endif. 74 75:- meta_predicate 76 el_addfn( , , , ). 77 78:- multifile 79 el_setup/1, % +Input 80 prolog:complete_input/4.
user_input
is connected to a terminal. This is the high level predicate used
for most purposes. The remainder of the library interface deals with
low level predicates that allows for applying and programming
libedit in non-standard situations.
The library is registered with ProgName set to swipl
(see
el_wrap/4).
102el_wrap :- 103 el_wrapped(user_input), 104 !. 105el_wrap :- 106 stream_property(user_input, tty(true)), !, 107 el_wrap(swipl, user_input, user_output, user_error), 108 add_prolog_commands(user_input), 109 forall(el_setup(user_input), true). 110el_wrap. 111 112add_prolog_commands(Input) :- 113 el_addfn(Input, complete, 'Complete atoms and files', complete), 114 el_addfn(Input, show_completions, 'List completions', show_completions), 115 el_addfn(Input, electric, 'Indicate matching bracket', electric), 116 el_addfn(Input, isearch_history, 'Incremental search in history', 117 isearch_history), 118 el_bind(Input, ["^I", complete]), 119 el_bind(Input, ["^[?", show_completions]), 120 el_bind(Input, ["^R", isearch_history]), 121 bind_electric(Input), 122 add_paste_quoted(Input), 123 el_source(Input, _).
forall(el_setup(Input), true)
after the
input stream has been wrapped, the default Prolog commands have been
added and the default user setup file has been sourced using
el_source/2. It can be used to define and bind additional commands.$HOME/.editrc
bind
command with the given arguments. The
example below lists the current key bindings.
?- el_bind(user_input, ['-a']).
The predicate el_bind/2 is typically used to bind commands defined using el_addfn/4. Note that the C proxy function has only the last character of the command as context to find the Prolog binding. This implies we cannot both bind e.g., "^[?" *and "?" to a Prolog function.
bind -a
(see el_bind/2) and Goal is called of the
associated key-binding is activated. Goal is called as
call(:Goal, +Input, +Char, -Continue)
where Input is the input stream providing access to the editor, Char
the activating character and Continue must be instantated with one
of the known continuation codes as defined by libedit: norm
,
newline
, eof
, arghack
, refresh
, refresh_beep
, cursor
,
redisplay
, error
or fatal
. In addition, the following Continue
code is provided.
The registered Goal typically used el_line/2 to fetch the input line and el_cursor/2, el_insertstr/2 and/or el_deletestr/2 to manipulate the input line.
Normally el_bind/2 is used to associate the defined command with a keyboard sequence.
line(Before,
After)
, where Before is a string holding the text before the
cursor and After is a string holding the text after the cursor.history()
from libedit. Supported actions are:
Num-String
, where
Num is the event number and String is the associated string
without terminating newline.261:- multifile 262 prolog:history/2. 263 264prologhistory(Input, add(Line)) :- 265 el_add_history(Input, Line). 266prologhistory(Input, load(File)) :- 267 el_read_history(Input, File). 268prologhistory(Input, save(File)) :- 269 el_write_history(Input, File). 270prologhistory(Input, load) :- 271 el_history_events(Input, Events), 272 '$reverse'(Events, RevEvents), 273 forall('$member'(Ev, RevEvents), 274 add_event(Ev)). 275 276add_event(Num-String) :- 277 remove_dot(String, String1), 278 '$save_history_event'(Num-String1). 279 280remove_dot(String0, String) :- 281 string_concat(String, ".", String0), 282 !. 283remove_dot(String, String). 284 285 286 /******************************* 287 * ELECTRIC CARET * 288 *******************************/
294bind_electric(Input) :- 295 forall(bracket(_Open, Close), bind_code(Input, Close, electric)), 296 forall(quote(Close), bind_code(Input, Close, electric)). 297 298bind_code(Input, Code, Command) :- 299 string_codes(Key, [Code]), 300 el_bind(Input, [Key, Command]).
305electric(Input, Char, Continue) :- 306 string_codes(Str, [Char]), 307 el_insertstr(Input, Str), 308 el_line(Input, line(Before, _)), 309 ( string_codes(Before, Codes), 310 nesting(Codes, 0, Nesting), 311 reverse(Nesting, [Close|RevNesting]) 312 -> ( Close = open(_,_) % open quote 313 -> Continue = refresh 314 ; matching_open(RevNesting, Close, _, Index) 315 -> string_length(Before, Len), % Proper match 316 Move is Index-Len, 317 Continue = electric(Move, 500, refresh) 318 ; Continue = refresh_beep % Not properly nested 319 ) 320 ; Continue = refresh_beep 321 ). 322 323matching_open_index(String, Index) :- 324 string_codes(String, Codes), 325 nesting(Codes, 0, Nesting), 326 reverse(Nesting, [Close|RevNesting]), 327 matching_open(RevNesting, Close, _, Index). 328 329matching_open([Open|Rest], Close, Rest, Index) :- 330 Open = open(Index,_), 331 match(Open, Close), 332 !. 333matching_open([Close1|Rest1], Close, Rest, Index) :- 334 Close1 = close(_,_), 335 matching_open(Rest1, Close1, Rest2, _), 336 matching_open(Rest2, Close, Rest, Index). 337 338match(open(_,Open),close(_,Close)) :- 339 ( bracket(Open, Close) 340 -> true 341 ; Open == Close, 342 quote(Open) 343 ). 344 345bracket(0'(, 0')). 346bracket(0'[, 0']). 347bracket(0'{, 0'}). 348 349quote(0'\'). 350quote(0'\"). 351quote(0'\`). 352 353nesting([], _, []). 354nesting([H|T], I, Nesting) :- 355 ( bracket(H, _Close) 356 -> Nesting = [open(I,H)|Nest] 357 ; bracket(_Open, H) 358 -> Nesting = [close(I,H)|Nest] 359 ), 360 !, 361 I2 is I+1, 362 nesting(T, I2, Nest). 363nesting([0'0, 0'\'|T], I, Nesting) :- 364 !, 365 phrase(skip_code, T, T1), 366 difflist_length(T, T1, Len), 367 I2 is I+Len+2, 368 nesting(T1, I2, Nesting). 369nesting([H|T], I, Nesting) :- 370 quote(H), 371 !, 372 ( phrase(skip_quoted(H), T, T1) 373 -> difflist_length(T, T1, Len), 374 I2 is I+Len+1, 375 Nesting = [open(I,H),close(I2,H)|Nest], 376 nesting(T1, I2, Nest) 377 ; Nesting = [open(I,H)] % Open quote 378 ). 379nesting([_|T], I, Nesting) :- 380 I2 is I+1, 381 nesting(T, I2, Nesting). 382 383difflist_length(List, Tail, Len) :- 384 difflist_length(List, Tail, 0, Len). 385 386difflist_length(List, Tail, Len0, Len) :- 387 List == Tail, 388 !, 389 Len = Len0. 390difflist_length([_|List], Tail, Len0, Len) :- 391 Len1 is Len0+1, 392 difflist_length(List, Tail, Len1, Len). 393 394skip_quoted(H) --> 395 [H], 396 !. 397skip_quoted(H) --> 398 "\\", [H], 399 !, 400 skip_quoted(H). 401skip_quoted(H) --> 402 [_], 403 skip_quoted(H). 404 405skip_code --> 406 "\\", [_], 407 !. 408skip_code --> 409 [_]. 410 411 412 /******************************* 413 * COMPLETION * 414 *******************************/
complete
editline function. The
predicate is called with three arguments, the first being the input
stream used to access the libedit functions and the second the
activating character. The last argument tells libedit what to do.
Consult el_set(3)
, EL_ADDFN
for details.425:- dynamic 426 last_complete/2. 427 428complete(Input, _Char, Continue) :- 429 el_line(Input, line(Before, After)), 430 ensure_input_completion, 431 prolog:complete_input(Before, After, Delete, Completions), 432 ( Completions = [One] 433 -> string_length(Delete, Len), 434 el_deletestr(Input, Len), 435 complete_text(One, Text), 436 el_insertstr(Input, Text), 437 Continue = refresh 438 ; Completions == [] 439 -> Continue = refresh_beep 440 ; get_time(Now), 441 retract(last_complete(TLast, Before)), 442 Now - TLast < 2 443 -> nl(user_error), 444 list_alternatives(Completions), 445 Continue = redisplay 446 ; retractall(last_complete(_,_)), 447 get_time(Now), 448 asserta(last_complete(Now, Before)), 449 common_competion(Completions, Extend), 450 ( Delete == Extend 451 -> Continue = refresh_beep 452 ; string_length(Delete, Len), 453 el_deletestr(Input, Len), 454 el_insertstr(Input, Extend), 455 Continue = refresh 456 ) 457 ). 458 459:- dynamic 460 input_completion_loaded/0. 461 462ensure_input_completion :- 463 input_completion_loaded, 464 !. 465ensure_input_completion :- 466 predicate_property(prolog:complete_input(_,_,_,_), 467 number_of_clauses(N)), 468 N > 0, 469 !. 470ensure_input_completion :- 471 exists_source(library(console_input)), 472 !, 473 use_module(library(console_input), []), 474 asserta(input_completion_loaded). 475ensure_input_completion.
482show_completions(Input, _Char, Continue) :- 483 el_line(Input, line(Before, After)), 484 prolog:complete_input(Before, After, _Delete, Completions), 485 nl(user_error), 486 list_alternatives(Completions), 487 Continue = redisplay. 488 489complete_text(Text-_Comment, Text) :- !. 490complete_text(Text, Text).
496common_competion(Alternatives, Common) :- 497 maplist(atomic, Alternatives), 498 !, 499 common_prefix(Alternatives, Common). 500common_competion(Alternatives, Common) :- 501 maplist(complete_text, Alternatives, AltText), 502 !, 503 common_prefix(AltText, Common).
509common_prefix([A1|T], Common) :- 510 common_prefix_(T, A1, Common). 511 512common_prefix_([], Common, Common). 513common_prefix_([H|T], Common0, Common) :- 514 common_prefix(H, Common0, Common1), 515 common_prefix_(T, Common1, Common).
521common_prefix(A1, A2, Prefix) :- 522 sub_atom(A1, 0, _, _, A2), 523 !, 524 Prefix = A2. 525common_prefix(A1, A2, Prefix) :- 526 sub_atom(A2, 0, _, _, A1), 527 !, 528 Prefix = A1. 529common_prefix(A1, A2, Prefix) :- 530 atom_codes(A1, C1), 531 atom_codes(A2, C2), 532 list_common_prefix(C1, C2, C), 533 string_codes(Prefix, C). 534 535list_common_prefix([H|T0], [H|T1], [H|T]) :- 536 !, 537 list_common_prefix(T0, T1, T). 538list_common_prefix(_, _, []).
548list_alternatives(Alternatives) :- 549 maplist(atomic, Alternatives), 550 !, 551 length(Alternatives, Count), 552 maplist(atom_length, Alternatives, Lengths), 553 max_list(Lengths, Max), 554 tty_size(_, Cols), 555 ColW is Max+2, 556 Columns is max(1, Cols // ColW), 557 RowCount is (Count+Columns-1)//Columns, 558 length(Rows, RowCount), 559 to_matrix(Alternatives, Rows, Rows), 560 ( RowCount > 11 561 -> length(First, 10), 562 Skipped is RowCount - 10, 563 append(First, _, Rows), 564 maplist(write_row(ColW), First), 565 format(user_error, '... skipped ~D rows~n', [Skipped]) 566 ; maplist(write_row(ColW), Rows) 567 ). 568list_alternatives(Alternatives) :- 569 maplist(complete_text, Alternatives, AltText), 570 list_alternatives(AltText). 571 572to_matrix([], _, Rows) :- 573 !, 574 maplist(close_list, Rows). 575to_matrix([H|T], [RH|RT], Rows) :- 576 !, 577 add_list(RH, H), 578 to_matrix(T, RT, Rows). 579to_matrix(List, [], Rows) :- 580 to_matrix(List, Rows, Rows). 581 582add_list(Var, Elem) :- 583 var(Var), !, 584 Var = [Elem|_]. 585add_list([_|T], Elem) :- 586 add_list(T, Elem). 587 588close_list(List) :- 589 append(List, [], _), 590 !. 591 592write_row(ColW, Row) :- 593 length(Row, Columns), 594 make_format(Columns, ColW, Format), 595 format(user_error, Format, Row). 596 597make_format(N, ColW, Format) :- 598 format(string(PerCol), '~~w~~t~~~d+', [ColW]), 599 Front is N - 1, 600 length(LF, Front), 601 maplist(=(PerCol), LF), 602 append(LF, ['~w~n'], Parts), 603 atomics_to_string(Parts, Format). 604 605 606 /******************************* 607 * SEARCH * 608 *******************************/
615isearch_history(Input, _Char, Continue) :- 616 el_line(Input, line(Before, After)), 617 string_concat(Before, After, Current), 618 string_length(Current, Len), 619 search_print('', "", Current), 620 search(Input, "", Current, 1, Line), 621 el_deletestr(Input, Len), 622 el_insertstr(Input, Line), 623 Continue = redisplay. 624 625search(Input, For, Current, Nth, Line) :- 626 el_getc(Input, Next), 627 Next \== -1, 628 !, 629 search(Next, Input, For, Current, Nth, Line). 630search(_Input, _For, _Current, _Nth, ""). 631 632search(7, _Input, _, Current, _, Current) :- % C-g: abort 633 !, 634 clear_line. 635search(18, Input, For, Current, Nth, Line) :- % C-r: search previous 636 !, 637 N2 is Nth+1, 638 search_(Input, For, Current, N2, Line). 639search(19, Input, For, Current, Nth, Line) :- % C-s: search next 640 !, 641 N2 is max(1,Nth-1), 642 search_(Input, For, Current, N2, Line). 643search(127, Input, For, Current, _Nth, Line) :- % DEL/BS: shorten search 644 sub_string(For, 0, _, 1, For1), 645 !, 646 search_(Input, For1, Current, 1, Line). 647search(Char, Input, For, Current, Nth, Line) :- 648 code_type(Char, cntrl), 649 !, 650 search_end(Input, For, Current, Nth, Line), 651 el_push(Input, Char). 652search(Char, Input, For, Current, _Nth, Line) :- 653 format(string(For1), '~w~c', [For,Char]), 654 search_(Input, For1, Current, 1, Line). 655 656search_(Input, For1, Current, Nth, Line) :- 657 ( find_in_history(Input, For1, Current, Nth, Candidate) 658 -> search_print('', For1, Candidate) 659 ; search_print('failed ', For1, Current) 660 ), 661 search(Input, For1, Current, Nth, Line). 662 663search_end(Input, For, Current, Nth, Line) :- 664 ( find_in_history(Input, For, Current, Nth, Line) 665 -> true 666 ; Line = Current 667 ), 668 clear_line. 669 670find_in_history(_, "", Current, _, Current) :- 671 !. 672find_in_history(Input, For, _, Nth, Line) :- 673 el_history_events(Input, History), 674 call_nth(( member(_N-Line, History), 675 sub_string(Line, _, _, _, For) 676 ), 677 Nth), 678 !. 679 680search_print(State, Search, Current) :- 681 format(user_error, '\r(~wreverse-i-search)`~w\': ~w\e[0K', 682 [State, Search, Current]). 683 684clear_line :- 685 format(user_error, '\r\e[0K', []). 686 687 688 /******************************* 689 * PASTE QUOTED * 690 *******************************/ 691 692:- if(current_prolog_flag(gui, true)). 693 694:- meta_predicate 695 with_quote_flags( , , ). 696 697add_paste_quoted(Input) :- 698 el_addfn(Input, paste_quoted, 'Paste as quoted atom', paste_quoted), 699 el_bind(Input, ["^Y", paste_quoted]).
707paste_quoted(Input, _Char, Continue) :- 708 clipboard_content(String), 709 quote_text(Input, String, Quoted), 710 el_insertstr(Input, Quoted), 711 Continue = refresh. 712 713quote_text(Input, String, Value) :- 714 el_line(Input, line(Before, _After)), 715 ( sub_string(Before, _, 1, 0, Quote) 716 -> true 717 ; Quote = "'" 718 ), 719 quote_text(Input, Quote, String, Value). 720 721quote_text(Input, "'", Text, Quoted) => 722 format(string(Quoted), '~q', [Text]), 723 el_deletestr(Input, 1). 724quote_text(Input, "\"", Text, Quoted) => 725 atom_string(Text, String), 726 with_quote_flags( 727 string, codes, 728 format(string(Quoted), '~q', [String])), 729 el_deletestr(Input, 1). 730quote_text(Input, "`", Text, Quoted) => 731 atom_string(Text, String), 732 with_quote_flags( 733 codes, string, 734 format(string(Quoted), '~q', [String])), 735 el_deletestr(Input, 1). 736quote_text(_, _, Text, Quoted) => 737 format(string(Quoted), '~q', [Text]). 738 739with_quote_flags(Double, Back, Goal) :- 740 current_prolog_flag(double_quotes, ODouble), 741 current_prolog_flag(back_quotes, OBack), 742 setup_call_cleanup( 743 ( set_prolog_flag(double_quotes, Double), 744 set_prolog_flag(back_quotes, Back) ), 745 Goal, 746 ( set_prolog_flag(double_quotes, ODouble), 747 set_prolog_flag(back_quotes, OBack) )). 748 749clipboard_content(Text) :- 750 in_pce_thread_sync(get(@(display), paste, primary, string(Text))). 751 752:- else. 753add_paste_quoted(_). 754:- endif.
BSD libedit based command line editing
This library wraps the BSD libedit command line editor. The binding provides a high level API to enable command line editing on the Prolog user streams and low level predicates to apply the library on other streams and program the library. */