View source with raw comments or as raw
    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-2020, 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:- autoload(library(apply),[maplist/2,maplist/3]).   57:- autoload(library(lists),[reverse/2,max_list/2,append/3,member/2]).   58:- autoload(library(shlib),[use_foreign_library/1]).   59:- autoload(library(solution_sequences),[call_nth/2]).   60
   61
   62editline_ok :-
   63    \+ current_prolog_flag(console_menu_version, qt),
   64    \+ current_prolog_flag(readline, readline),
   65    stream_property(user_input, tty(true)).
   66
   67:- use_foreign_library(foreign(libedit4pl)).   68
   69:- if(editline_ok).   70:- initialization el_wrap.   71:- endif.   72
   73:- meta_predicate
   74    el_addfn(+,+,+,3).   75
   76:- multifile
   77    el_setup/1,                         % +Input
   78    prolog:complete_input/4.

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. */

 el_wrap is det
Enable using editline on the standard user streams if 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).

  100el_wrap :-
  101    el_wrapped(user_input),
  102    !.
  103el_wrap :-
  104    stream_property(user_input, tty(true)), !,
  105    el_wrap(swipl, user_input, user_output, user_error),
  106    add_prolog_commands(user_input),
  107    forall(el_setup(user_input), true).
  108el_wrap.
  109
  110add_prolog_commands(Input) :-
  111    el_addfn(Input, complete, 'Complete atoms and files', complete),
  112    el_addfn(Input, show_completions, 'List completions', show_completions),
  113    el_addfn(Input, electric, 'Indicate matching bracket', electric),
  114    el_addfn(Input, isearch_history, 'Incremental search in history',
  115             isearch_history),
  116    el_bind(Input, ["^I",  complete]),
  117    el_bind(Input, ["^[?", show_completions]),
  118    el_bind(Input, ["^R",  isearch_history]),
  119    bind_electric(Input),
  120    el_source(Input, _).
 el_wrap(+ProgName:atom, +In:stream, +Out:stream, +Error:stream) is det
Enable editline on the stream-triple <In,Out,Error>. From this moment on In is a handle to the command line editor.
Arguments:
ProgName- is the name of the invoking program, used when reading the editrc(5) file to determine which settings to use.
 el_setup(+In:stream) is nondet
This hooks is called as 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.
 el_wrapped(+In:stream) is semidet
True if In is a stream wrapped by el_wrap/3.
 el_unwrap(+In:stream) is det
Remove the libedit wrapper for In and the related output and error streams.
bug
- The wrapper creates FILE* handles that cannot be closed and thus wrapping and unwrapping implies a (modest) memory leak.
 el_source(+In:stream, +File) is det
Initialise editline by reading the contents of File. If File is unbound try $HOME/.editrc
 el_bind(+In:stream, +Args) is det
Invoke the libedit 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.

See also
- editrc(5) for more information.
 el_addfn(+Input:stream, +Command, +Help, :Goal) is det
Add a new command to the command line editor associated with Input. Command is the name of the command, Help is the help string printed with e.g. 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.

electric(Move, TimeOut, Continue)
Show electric caret at Move positions to the left of the normal cursor positions for the given TimeOut. Continue as defined by the Continue value.

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.

See also
- el_set(3) EL_ADDFN for details.
 el_line(+Input:stream, -Line) is det
Fetch the currently buffered input line. Line is a term 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.
 el_cursor(+Input:stream, +Move:integer) is det
Move the cursor Move character forwards (positive) or backwards (negative).
 el_insertstr(+Input:stream, +Text) is det
Insert Text at the cursor.
 el_deletestr(+Input:stream, +Count) is det
Delete Count characters before the cursor.
 el_history(+In:stream, ?Action) is det
Perform a generic action on the history. This provides an incomplete interface to history() from libedit. Supported actions are:
clear
Clear the history.
setsize(+Integer)
Set size of history to size elements.
setunique(+Boolean)
Set flag that adjacent identical event strings should not be entered into the history.
 el_history_events(+In:stream, -Events:list(pair)) is det
Unify Events with a list of pairs of the form Num-String, where Num is the event number and String is the associated string without terminating newline.
 el_add_history(+In:stream, +Line:text) is det
Add a line to the command line history.
 el_read_history(+In:stream, +File:file) is det
Read the history saved using el_write_history/2.
Arguments:
File- is a file specification for absolute_file_name/3.
 el_write_history(+In:stream, +File:file) is det
Save editline history to File. The history may be reloaded using el_read_history/2.
Arguments:
File- is a file specification for absolute_file_name/3.
  258:- multifile
  259    prolog:history/2.  260
  261prolog:history(Input, add(Line)) :-
  262    el_add_history(Input, Line).
  263prolog:history(Input, load(File)) :-
  264    el_read_history(Input, File).
  265prolog:history(Input, save(File)) :-
  266    el_write_history(Input, File).
  267prolog:history(Input, load) :-
  268    el_history_events(Input, Events),
  269    '$reverse'(Events, RevEvents),
  270    forall('$member'(Ev, RevEvents),
  271           add_event(Ev)).
  272
  273add_event(Num-String) :-
  274    remove_dot(String, String1),
  275    '$save_history_event'(Num-String1).
  276
  277remove_dot(String0, String) :-
  278    string_concat(String, ".", String0),
  279    !.
  280remove_dot(String, String).
  281
  282
  283		 /*******************************
  284		 *        ELECTRIC CARET	*
  285		 *******************************/
 bind_electric(+Input) is det
Bind known close statements for electric input
  291bind_electric(Input) :-
  292    forall(bracket(_Open, Close), bind_code(Input, Close, electric)),
  293    forall(quote(Close), bind_code(Input, Close, electric)).
  294
  295bind_code(Input, Code, Command) :-
  296    string_codes(Key, [Code]),
  297    el_bind(Input, [Key, Command]).
 electric(+Input, +Char, -Continue) is det
  302electric(Input, Char, Continue) :-
  303    string_codes(Str, [Char]),
  304    el_insertstr(Input, Str),
  305    el_line(Input, line(Before, _)),
  306    (   string_codes(Before, Codes),
  307        nesting(Codes, 0, Nesting),
  308        reverse(Nesting, [Close|RevNesting])
  309    ->  (   Close = open(_,_)                   % open quote
  310        ->  Continue = refresh
  311        ;   matching_open(RevNesting, Close, _, Index)
  312        ->  string_length(Before, Len),         % Proper match
  313            Move is Index-Len,
  314            Continue = electric(Move, 500, refresh)
  315        ;   Continue = refresh_beep             % Not properly nested
  316        )
  317    ;   Continue = refresh_beep
  318    ).
  319
  320matching_open_index(String, Index) :-
  321    string_codes(String, Codes),
  322    nesting(Codes, 0, Nesting),
  323    reverse(Nesting, [Close|RevNesting]),
  324    matching_open(RevNesting, Close, _, Index).
  325
  326matching_open([Open|Rest], Close, Rest, Index) :-
  327    Open = open(Index,_),
  328    match(Open, Close),
  329    !.
  330matching_open([Close1|Rest1], Close, Rest, Index) :-
  331    Close1 = close(_,_),
  332    matching_open(Rest1, Close1, Rest2, _),
  333    matching_open(Rest2, Close, Rest, Index).
  334
  335match(open(_,Open),close(_,Close)) :-
  336    (   bracket(Open, Close)
  337    ->  true
  338    ;   Open == Close,
  339        quote(Open)
  340    ).
  341
  342bracket(0'(, 0')).
  343bracket(0'[, 0']).
  344bracket(0'{, 0'}).
  345
  346quote(0'\').
  347quote(0'\").
  348quote(0'\`).
  349
  350nesting([], _, []).
  351nesting([H|T], I, Nesting) :-
  352    (   bracket(H, _Close)
  353    ->  Nesting = [open(I,H)|Nest]
  354    ;   bracket(_Open, H)
  355    ->  Nesting = [close(I,H)|Nest]
  356    ),
  357    !,
  358    I2 is I+1,
  359    nesting(T, I2, Nest).
  360nesting([0'0, 0'\'|T], I, Nesting) :-
  361    !,
  362    phrase(skip_code, T, T1),
  363    difflist_length(T, T1, Len),
  364    I2 is I+Len+2,
  365    nesting(T1, I2, Nesting).
  366nesting([H|T], I, Nesting) :-
  367    quote(H),
  368    !,
  369    (   phrase(skip_quoted(H), T, T1)
  370    ->  difflist_length(T, T1, Len),
  371        I2 is I+Len+1,
  372        Nesting = [open(I,H),close(I2,H)|Nest],
  373        nesting(T1, I2, Nest)
  374    ;   Nesting = [open(I,H)]                   % Open quote
  375    ).
  376nesting([_|T], I, Nesting) :-
  377    I2 is I+1,
  378    nesting(T, I2, Nesting).
  379
  380difflist_length(List, Tail, Len) :-
  381    difflist_length(List, Tail, 0, Len).
  382
  383difflist_length(List, Tail, Len0, Len) :-
  384    List == Tail,
  385    !,
  386    Len = Len0.
  387difflist_length([_|List], Tail, Len0, Len) :-
  388    Len1 is Len0+1,
  389    difflist_length(List, Tail, Len1, Len).
  390
  391skip_quoted(H) -->
  392    [H],
  393    !.
  394skip_quoted(H) -->
  395    "\\", [H],
  396    !,
  397    skip_quoted(H).
  398skip_quoted(H) -->
  399    [_],
  400    skip_quoted(H).
  401
  402skip_code -->
  403    "\\", [_],
  404    !.
  405skip_code -->
  406    [_].
  407
  408
  409		 /*******************************
  410		 *           COMPLETION		*
  411		 *******************************/
 complete(+Input, +Char, -Continue) is det
Implementation of the registered 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.
  422:- dynamic
  423    last_complete/2.  424
  425complete(Input, _Char, Continue) :-
  426    el_line(Input, line(Before, After)),
  427    ensure_input_completion,
  428    prolog:complete_input(Before, After, Delete, Completions),
  429    (   Completions = [One]
  430    ->  string_length(Delete, Len),
  431        el_deletestr(Input, Len),
  432        complete_text(One, Text),
  433        el_insertstr(Input, Text),
  434        Continue = refresh
  435    ;   Completions == []
  436    ->  Continue = refresh_beep
  437    ;   get_time(Now),
  438        retract(last_complete(TLast, Before)),
  439        Now - TLast < 2
  440    ->  nl(user_error),
  441        list_alternatives(Completions),
  442        Continue = redisplay
  443    ;   retractall(last_complete(_,_)),
  444        get_time(Now),
  445        asserta(last_complete(Now, Before)),
  446        common_competion(Completions, Extend),
  447        (   Delete == Extend
  448        ->  Continue = refresh_beep
  449        ;   string_length(Delete, Len),
  450            el_deletestr(Input, Len),
  451            el_insertstr(Input, Extend),
  452            Continue = refresh
  453        )
  454    ).
  455
  456:- dynamic
  457    input_completion_loaded/0.  458
  459ensure_input_completion :-
  460    input_completion_loaded,
  461    !.
  462ensure_input_completion :-
  463    predicate_property(prolog:complete_input(_,_,_,_),
  464                       number_of_clauses(N)),
  465    N > 0,
  466    !.
  467ensure_input_completion :-
  468    exists_source(library(console_input)),
  469    !,
  470    use_module(library(console_input), []),
  471    asserta(input_completion_loaded).
  472ensure_input_completion.
 show_completions(+Input, +Char, -Continue) is det
Editline command to show possible completions.
  479show_completions(Input, _Char, Continue) :-
  480    el_line(Input, line(Before, After)),
  481    prolog:complete_input(Before, After, _Delete, Completions),
  482    nl(user_error),
  483    list_alternatives(Completions),
  484    Continue = redisplay.
  485
  486complete_text(Text-_Comment, Text) :- !.
  487complete_text(Text, Text).
 common_competion(+Alternatives, -Common) is det
True when Common is the common prefix of all candidate Alternatives.
  493common_competion(Alternatives, Common) :-
  494    maplist(atomic, Alternatives),
  495    !,
  496    common_prefix(Alternatives, Common).
  497common_competion(Alternatives, Common) :-
  498    maplist(complete_text, Alternatives, AltText),
  499    !,
  500    common_prefix(AltText, Common).
 common_prefix(+Atoms, -Common) is det
True when Common is the common prefix of all Atoms.
  506common_prefix([A1|T], Common) :-
  507    common_prefix_(T, A1, Common).
  508
  509common_prefix_([], Common, Common).
  510common_prefix_([H|T], Common0, Common) :-
  511    common_prefix(H, Common0, Common1),
  512    common_prefix_(T, Common1, Common).
 common_prefix(+A1, +A2, -Prefix:string) is det
True when Prefix is the common prefix of the atoms A1 and A2
  518common_prefix(A1, A2, Prefix) :-
  519    sub_atom(A1, 0, _, _, A2),
  520    !,
  521    Prefix = A2.
  522common_prefix(A1, A2, Prefix) :-
  523    sub_atom(A2, 0, _, _, A1),
  524    !,
  525    Prefix = A1.
  526common_prefix(A1, A2, Prefix) :-
  527    atom_codes(A1, C1),
  528    atom_codes(A2, C2),
  529    list_common_prefix(C1, C2, C),
  530    string_codes(Prefix, C).
  531
  532list_common_prefix([H|T0], [H|T1], [H|T]) :-
  533    !,
  534    list_common_prefix(T0, T1, T).
  535list_common_prefix(_, _, []).
 list_alternatives(+Alternatives)
List possible completions at the current point.
To be done
- currently ignores the Comment in Text-Comment alternatives.
  545list_alternatives(Alternatives) :-
  546    maplist(atomic, Alternatives),
  547    !,
  548    length(Alternatives, Count),
  549    maplist(atom_length, Alternatives, Lengths),
  550    max_list(Lengths, Max),
  551    tty_size(_, Cols),
  552    ColW is Max+2,
  553    Columns is max(1, Cols // ColW),
  554    RowCount is (Count+Columns-1)//Columns,
  555    length(Rows, RowCount),
  556    to_matrix(Alternatives, Rows, Rows),
  557    (   RowCount > 11
  558    ->  length(First, 10),
  559        Skipped is RowCount - 10,
  560        append(First, _, Rows),
  561        maplist(write_row(ColW), First),
  562        format(user_error, '... skipped ~D rows~n', [Skipped])
  563    ;   maplist(write_row(ColW), Rows)
  564    ).
  565list_alternatives(Alternatives) :-
  566    maplist(complete_text, Alternatives, AltText),
  567    list_alternatives(AltText).
  568
  569to_matrix([], _, Rows) :-
  570    !,
  571    maplist(close_list, Rows).
  572to_matrix([H|T], [RH|RT], Rows) :-
  573    !,
  574    add_list(RH, H),
  575    to_matrix(T, RT, Rows).
  576to_matrix(List, [], Rows) :-
  577    to_matrix(List, Rows, Rows).
  578
  579add_list(Var, Elem) :-
  580    var(Var), !,
  581    Var = [Elem|_].
  582add_list([_|T], Elem) :-
  583    add_list(T, Elem).
  584
  585close_list(List) :-
  586    append(List, [], _),
  587    !.
  588
  589write_row(ColW, Row) :-
  590    length(Row, Columns),
  591    make_format(Columns, ColW, Format),
  592    format(user_error, Format, Row).
  593
  594make_format(N, ColW, Format) :-
  595    format(string(PerCol), '~~w~~t~~~d+', [ColW]),
  596    Front is N - 1,
  597    length(LF, Front),
  598    maplist(=(PerCol), LF),
  599    append(LF, ['~w~n'], Parts),
  600    atomics_to_string(Parts, Format).
  601
  602
  603		 /*******************************
  604		 *             SEARCH		*
  605		 *******************************/
 isearch_history(+Input, +Char, -Continue) is det
Incremental search through the history. The behavior is based on GNU readline.
  612isearch_history(Input, _Char, Continue) :-
  613    el_line(Input, line(Before, After)),
  614    string_concat(Before, After, Current),
  615    string_length(Current, Len),
  616    search_print('', "", Current),
  617    search(Input, "", Current, 1, Line),
  618    el_deletestr(Input, Len),
  619    el_insertstr(Input, Line),
  620    Continue = redisplay.
  621
  622search(Input, For, Current, Nth, Line) :-
  623    el_getc(Input, Next),
  624    Next \== -1,
  625    !,
  626    search(Next, Input, For, Current, Nth, Line).
  627search(_Input, _For, _Current, _Nth, "").
  628
  629search(7, _Input, _, Current, _, Current) :-    % C-g: abort
  630    !,
  631    clear_line.
  632search(18, Input, For, Current, Nth, Line) :-   % C-r: search previous
  633    !,
  634    N2 is Nth+1,
  635    search_(Input, For, Current, N2, Line).
  636search(19, Input, For, Current, Nth, Line) :-   % C-s: search next
  637    !,
  638    N2 is max(1,Nth-1),
  639    search_(Input, For, Current, N2, Line).
  640search(127, Input, For, Current, _Nth, Line) :- % DEL/BS: shorten search
  641    sub_string(For, 0, _, 1, For1),
  642    !,
  643    search_(Input, For1, Current, 1, Line).
  644search(Char, Input, For, Current, Nth, Line) :-
  645    code_type(Char, cntrl),
  646    !,
  647    search_end(Input, For, Current, Nth, Line),
  648    el_push(Input, Char).
  649search(Char, Input, For, Current, _Nth, Line) :-
  650    format(string(For1), '~w~c', [For,Char]),
  651    search_(Input, For1, Current, 1, Line).
  652
  653search_(Input, For1, Current, Nth, Line) :-
  654    (   find_in_history(Input, For1, Current, Nth, Candidate)
  655    ->  search_print('', For1, Candidate)
  656    ;   search_print('failed ', For1, Current)
  657    ),
  658    search(Input, For1, Current, Nth, Line).
  659
  660search_end(Input, For, Current, Nth, Line) :-
  661    (   find_in_history(Input, For, Current, Nth, Line)
  662    ->  true
  663    ;   Line = Current
  664    ),
  665    clear_line.
  666
  667find_in_history(_, "", Current, _, Current) :-
  668    !.
  669find_in_history(Input, For, _, Nth, Line) :-
  670    el_history_events(Input, History),
  671    call_nth(( member(_N-Line, History),
  672               sub_string(Line, _, _, _, For)
  673             ),
  674             Nth),
  675    !.
  676
  677search_print(State, Search, Current) :-
  678    format(user_error, '\r(~wreverse-i-search)`~w\': ~w\e[0K',
  679           [State, Search, Current]).
  680
  681clear_line :-
  682    format(user_error, '\r\e[0K', [])