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, 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(+,+,+,3).   64
   65:- multifile
   66    el_setup/1.                         % +Input

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

   77:- initialization
   78    el_wrap.
 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).

   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, _).
 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.
  246:- multifile
  247    prolog:history/2.  248
  249prolog:history(Input, add(Line)) :-
  250    el_add_history(Input, Line).
  251prolog:history(Input, load(File)) :-
  252    el_read_history(Input, File).
  253prolog:history(Input, save(File)) :-
  254    el_write_history(Input, File).
  255prolog:history(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		 *******************************/
 bind_electric(+Input) is det
Bind known close statements for electric input
  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]).
 electric(+Input, +Char, -Continue) is det
  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(+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.
  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    ).
 show_completions(+Input, +Char, -Continue) is det
Editline command to show possible completions.
  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).
 common_competion(+Alternatives, -Common) is det
True when Common is the common prefix of all candidate Alternatives.
  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).
 common_prefix(+Atoms, -Common) is det
True when Common is the common prefix of all Atoms.
  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).
 common_prefix(+A1, +A2, -Prefix:string) is det
True when Prefix is the common prefix of the atoms A1 and A2
  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(_, _, []).
 list_alternatives(+Alternatives)
List possible completions at the current point.
To be done
- currently ignores the Comment in Text-Comment alternatives.
  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)