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, VU University Amsterdam 7 CWI Amsterdam 8 All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in 19 the documentation and/or other materials provided with the 20 distribution. 21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 POSSIBILITY OF SUCH DAMAGE. 34*/ 35 36:- module(editline, 37 [ el_wrap/0, % wrap user_input, etc. 38 el_wrap/4, % +Prog, +Input, +Output, +Error 39 el_wrapped/1, % +Input 40 el_unwrap/1, % +Input 41 42 el_source/2, % +Input, +File 43 el_bind/2, % +Input, +Args 44 el_addfn/4, % +Input, +Name, +Help, :Goal 45 el_cursor/2, % +Input, +Move 46 el_line/2, % +Input, -Line 47 el_insertstr/2, % +Input, +Text 48 el_deletestr/2, % +Input, +Count 49 50 el_history/2, % +Input, ?Action 51 el_history_events/2, % +Input, -Events 52 el_add_history/2, % +Input, +Line 53 el_write_history/2, % +Input, +FileName 54 el_read_history/2 % +Input, +FileName 55 ]). 56:- use_module(library(console_input)). 57:- use_module(library(apply)). 58:- use_module(library(lists)). 59 60:- use_foreign_library(foreign(libedit4pl)). 61 62:- meta_predicate 63 el_addfn( , , , ). 64 65:- multifile 66 el_setup/1. % +Input
77:- initialization
78 el_wrap.
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).
91el_wrap :- 92 el_wrapped(user_input), 93 !. 94el_wrap :- 95 stream_property(user_input, tty(true)), !, 96 el_wrap(swipl, user_input, user_output, user_error), 97 add_prolog_commands(user_input), 98 forall(el_setup(user_input), true). 99el_wrap. 100 101add_prolog_commands(Input) :- 102 el_addfn(Input, complete, 'Complete atoms and files', complete), 103 el_addfn(Input, show_completions, 'List completions', show_completions), 104 el_addfn(Input, electric, 'Indicate matching bracket', electric), 105 el_bind(Input, ["^I", complete]), 106 el_bind(Input, ["^[?", show_completions]), 107 bind_electric(Input), 108 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.246:- multifile 247 prolog:history/2. 248 249prologhistory(Input, add(Line)) :- 250 el_add_history(Input, Line). 251prologhistory(Input, load(File)) :- 252 el_read_history(Input, File). 253prologhistory(Input, save(File)) :- 254 el_write_history(Input, File). 255prologhistory(Input, load) :- 256 el_history_events(Input, Events), 257 '$reverse'(Events, RevEvents), 258 forall('$member'(Ev, RevEvents), 259 add_event(Ev)). 260 261add_event(Num-String) :- 262 remove_dot(String, String1), 263 '$save_history_event'(Num-String1). 264 265remove_dot(String0, String) :- 266 string_concat(String, ".", String0), 267 !. 268remove_dot(String, String). 269 270 271 /******************************* 272 * ELECTRIC CARET * 273 *******************************/
279bind_electric(Input) :- 280 forall(bracket(_Open, Close), bind_code(Input, Close, electric)), 281 forall(quote(Close), bind_code(Input, Close, electric)). 282 283bind_code(Input, Code, Command) :- 284 string_codes(Key, [Code]), 285 el_bind(Input, [Key, Command]).
290electric(Input, Char, Continue) :- 291 string_codes(Str, [Char]), 292 el_insertstr(Input, Str), 293 el_line(Input, line(Before, _)), 294 ( string_codes(Before, Codes), 295 nesting(Codes, 0, Nesting), 296 reverse(Nesting, [Close|RevNesting]) 297 -> ( Close = open(_,_) % open quote 298 -> Continue = refresh 299 ; matching_open(RevNesting, Close, _, Index) 300 -> string_length(Before, Len), % Proper match 301 Move is Index-Len, 302 Continue = electric(Move, 500, refresh) 303 ; Continue = refresh_beep % Not properly nested 304 ) 305 ; Continue = refresh_beep 306 ). 307 308matching_open_index(String, Index) :- 309 string_codes(String, Codes), 310 nesting(Codes, 0, Nesting), 311 reverse(Nesting, [Close|RevNesting]), 312 matching_open(RevNesting, Close, _, Index). 313 314matching_open([Open|Rest], Close, Rest, Index) :- 315 Open = open(Index,_), 316 match(Open, Close), 317 !. 318matching_open([Close1|Rest1], Close, Rest, Index) :- 319 Close1 = close(_,_), 320 matching_open(Rest1, Close1, Rest2, _), 321 matching_open(Rest2, Close, Rest, Index). 322 323match(open(_,Open),close(_,Close)) :- 324 ( bracket(Open, Close) 325 -> true 326 ; Open == Close, 327 quote(Open) 328 ). 329 330bracket(0'(, 0')). 331bracket(0'[, 0']). 332bracket(0'{, 0'}). 333 334quote(0'\'). 335quote(0'\"). 336quote(0'\`). 337 338nesting([], _, []). 339nesting([H|T], I, Nesting) :- 340 ( bracket(H, _Close) 341 -> Nesting = [open(I,H)|Nest] 342 ; bracket(_Open, H) 343 -> Nesting = [close(I,H)|Nest] 344 ), 345 !, 346 I2 is I+1, 347 nesting(T, I2, Nest). 348nesting([0'0, 0'\'|T], I, Nesting) :- 349 !, 350 phrase(skip_code, T, T1), 351 difflist_length(T, T1, Len), 352 I2 is I+Len+2, 353 nesting(T1, I2, Nesting). 354nesting([H|T], I, Nesting) :- 355 quote(H), 356 !, 357 ( phrase(skip_quoted(H), T, T1) 358 -> difflist_length(T, T1, Len), 359 I2 is I+Len+1, 360 Nesting = [open(I,H),close(I2,H)|Nest], 361 nesting(T1, I2, Nest) 362 ; Nesting = [open(I,H)] % Open quote 363 ). 364nesting([_|T], I, Nesting) :- 365 I2 is I+1, 366 nesting(T, I2, Nesting). 367 368difflist_length(List, Tail, Len) :- 369 difflist_length(List, Tail, 0, Len). 370 371difflist_length(List, Tail, Len0, Len) :- 372 List == Tail, 373 !, 374 Len = Len0. 375difflist_length([_|List], Tail, Len0, Len) :- 376 Len1 is Len0+1, 377 difflist_length(List, Tail, Len1, Len). 378 379skip_quoted(H) --> 380 [H], 381 !. 382skip_quoted(H) --> 383 "\\", [H], 384 !, 385 skip_quoted(H). 386skip_quoted(H) --> 387 [_], 388 skip_quoted(H). 389 390skip_code --> 391 "\\", [_], 392 !. 393skip_code --> 394 [_]. 395 396 397 /******************************* 398 * COMPLETION * 399 *******************************/
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.410:- dynamic 411 last_complete/2. 412 413complete(Input, _Char, Continue) :- 414 el_line(Input, line(Before, After)), 415 prolog:complete_input(Before, After, Delete, Completions), 416 ( Completions = [One] 417 -> string_length(Delete, Len), 418 el_deletestr(Input, Len), 419 complete_text(One, Text), 420 el_insertstr(Input, Text), 421 Continue = refresh 422 ; Completions == [] 423 -> Continue = refresh_beep 424 ; get_time(Now), 425 retract(last_complete(TLast, Before)), 426 Now - TLast < 2 427 -> nl(user_error), 428 list_alternatives(Completions), 429 Continue = redisplay 430 ; retractall(last_complete(_,_)), 431 get_time(Now), 432 asserta(last_complete(Now, Before)), 433 common_competion(Completions, Extend), 434 ( Delete == Extend 435 -> Continue = refresh_beep 436 ; string_length(Delete, Len), 437 el_deletestr(Input, Len), 438 el_insertstr(Input, Extend), 439 Continue = refresh 440 ) 441 ).
447show_completions(Input, _Char, Continue) :- 448 el_line(Input, line(Before, After)), 449 prolog:complete_input(Before, After, _Delete, Completions), 450 nl(user_error), 451 list_alternatives(Completions), 452 Continue = redisplay. 453 454complete_text(Text-_Comment, Text) :- !. 455complete_text(Text, Text).
461common_competion(Alternatives, Common) :- 462 maplist(atomic, Alternatives), 463 !, 464 common_prefix(Alternatives, Common). 465common_competion(Alternatives, Common) :- 466 maplist(complete_text, Alternatives, AltText), 467 !, 468 common_prefix(AltText, Common).
474common_prefix([A1|T], Common) :- 475 common_prefix_(T, A1, Common). 476 477common_prefix_([], Common, Common). 478common_prefix_([H|T], Common0, Common) :- 479 common_prefix(H, Common0, Common1), 480 common_prefix_(T, Common1, Common).
486common_prefix(A1, A2, Prefix) :- 487 sub_atom(A1, 0, _, _, A2), 488 !, 489 Prefix = A2. 490common_prefix(A1, A2, Prefix) :- 491 sub_atom(A2, 0, _, _, A1), 492 !, 493 Prefix = A1. 494common_prefix(A1, A2, Prefix) :- 495 atom_codes(A1, C1), 496 atom_codes(A2, C2), 497 list_common_prefix(C1, C2, C), 498 string_codes(Prefix, C). 499 500list_common_prefix([H|T0], [H|T1], [H|T]) :- 501 !, 502 list_common_prefix(T0, T1, T). 503list_common_prefix(_, _, []).
513list_alternatives(Alternatives) :- 514 maplist(atomic, Alternatives), 515 !, 516 length(Alternatives, Count), 517 maplist(atom_length, Alternatives, Lengths), 518 max_list(Lengths, Max), 519 tty_size(_, Cols), 520 ColW is Max+2, 521 Columns is Cols // ColW, 522 RowCount is (Count+Columns-1)//Columns, 523 length(Rows, RowCount), 524 to_matrix(Alternatives, Rows, Rows), 525 ( RowCount > 11 526 -> length(First, 10), 527 Skipped is RowCount - 10, 528 append(First, _, Rows), 529 maplist(write_row(ColW), First), 530 format(user_error, '... skipped ~D rows~n', [Skipped]) 531 ; maplist(write_row(ColW), Rows) 532 ). 533list_alternatives(Alternatives) :- 534 maplist(complete_text, Alternatives, AltText), 535 list_alternatives(AltText). 536 537to_matrix([], _, Rows) :- 538 !, 539 maplist(close_list, Rows). 540to_matrix([H|T], [RH|RT], Rows) :- 541 !, 542 add_list(RH, H), 543 to_matrix(T, RT, Rows). 544to_matrix(List, [], Rows) :- 545 to_matrix(List, Rows, Rows). 546 547add_list(Var, Elem) :- 548 var(Var), !, 549 Var = [Elem|_]. 550add_list([_|T], Elem) :- 551 add_list(T, Elem). 552 553close_list(List) :- 554 append(List, [], _), 555 !. 556 557write_row(ColW, Row) :- 558 length(Row, Columns), 559 make_format(Columns, ColW, Format), 560 format(user_error, Format, Row). 561 562make_format(N, ColW, Format) :- 563 format(string(PerCol), '~~w~~t~~~d+', [ColW]), 564 Front is N - 1, 565 length(LF, Front), 566 maplist(=(PerCol), LF), 567 append(LF, ['~w~n'], Parts), 568 atomics_to_string(Parts, Format)
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. */