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-2019, 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:- use_module(library(solution_sequences)).   60
   61editline_ok :-
   62    \+ current_prolog_flag(console_menu_version, qt),
   63    \+ current_prolog_flag(readline, readline),
   64    stream_property(user_input, tty(true)).
   65
   66:- use_foreign_library(foreign(libedit4pl)).   67
   68:- if(editline_ok).   69:- initialization el_wrap.   70:- endif.   71
   72:- meta_predicate
   73    el_addfn(+,+,+,3).   74
   75:- multifile
   76    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. */

 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).

   98el_wrap :-
   99    el_wrapped(user_input),
  100    !.
  101el_wrap :-
  102    stream_property(user_input, tty(true)), !,
  103    el_wrap(swipl, user_input, user_output, user_error),
  104    add_prolog_commands(user_input),
  105    forall(el_setup(user_input), true).
  106el_wrap.
  107
  108add_prolog_commands(Input) :-
  109    el_addfn(Input, complete, 'Complete atoms and files', complete),
  110    el_addfn(Input, show_completions, 'List completions', show_completions),
  111    el_addfn(Input, electric, 'Indicate matching bracket', electric),
  112    el_addfn(Input, isearch_history, 'Incremental search in history',
  113             isearch_history),
  114    el_bind(Input, ["^I",  complete]),
  115    el_bind(Input, ["^[?", show_completions]),
  116    el_bind(Input, ["^R",  isearch_history]),
  117    bind_electric(Input),
  118    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.
  256:- multifile
  257    prolog:history/2.  258
  259prolog:history(Input, add(Line)) :-
  260    el_add_history(Input, Line).
  261prolog:history(Input, load(File)) :-
  262    el_read_history(Input, File).
  263prolog:history(Input, save(File)) :-
  264    el_write_history(Input, File).
  265prolog:history(Input, load) :-
  266    el_history_events(Input, Events),
  267    '$reverse'(Events, RevEvents),
  268    forall('$member'(Ev, RevEvents),
  269           add_event(Ev)).
  270
  271add_event(Num-String) :-
  272    remove_dot(String, String1),
  273    '$save_history_event'(Num-String1).
  274
  275remove_dot(String0, String) :-
  276    string_concat(String, ".", String0),
  277    !.
  278remove_dot(String, String).
  279
  280
  281		 /*******************************
  282		 *        ELECTRIC CARET	*
  283		 *******************************/
 bind_electric(+Input) is det
Bind known close statements for electric input
  289bind_electric(Input) :-
  290    forall(bracket(_Open, Close), bind_code(Input, Close, electric)),
  291    forall(quote(Close), bind_code(Input, Close, electric)).
  292
  293bind_code(Input, Code, Command) :-
  294    string_codes(Key, [Code]),
  295    el_bind(Input, [Key, Command]).
 electric(+Input, +Char, -Continue) is det
  300electric(Input, Char, Continue) :-
  301    string_codes(Str, [Char]),
  302    el_insertstr(Input, Str),
  303    el_line(Input, line(Before, _)),
  304    (   string_codes(Before, Codes),
  305        nesting(Codes, 0, Nesting),
  306        reverse(Nesting, [Close|RevNesting])
  307    ->  (   Close = open(_,_)                   % open quote
  308        ->  Continue = refresh
  309        ;   matching_open(RevNesting, Close, _, Index)
  310        ->  string_length(Before, Len),         % Proper match
  311            Move is Index-Len,
  312            Continue = electric(Move, 500, refresh)
  313        ;   Continue = refresh_beep             % Not properly nested
  314        )
  315    ;   Continue = refresh_beep
  316    ).
  317
  318matching_open_index(String, Index) :-
  319    string_codes(String, Codes),
  320    nesting(Codes, 0, Nesting),
  321    reverse(Nesting, [Close|RevNesting]),
  322    matching_open(RevNesting, Close, _, Index).
  323
  324matching_open([Open|Rest], Close, Rest, Index) :-
  325    Open = open(Index,_),
  326    match(Open, Close),
  327    !.
  328matching_open([Close1|Rest1], Close, Rest, Index) :-
  329    Close1 = close(_,_),
  330    matching_open(Rest1, Close1, Rest2, _),
  331    matching_open(Rest2, Close, Rest, Index).
  332
  333match(open(_,Open),close(_,Close)) :-
  334    (   bracket(Open, Close)
  335    ->  true
  336    ;   Open == Close,
  337        quote(Open)
  338    ).
  339
  340bracket(0'(, 0')).
  341bracket(0'[, 0']).
  342bracket(0'{, 0'}).
  343
  344quote(0'\').
  345quote(0'\").
  346quote(0'\`).
  347
  348nesting([], _, []).
  349nesting([H|T], I, Nesting) :-
  350    (   bracket(H, _Close)
  351    ->  Nesting = [open(I,H)|Nest]
  352    ;   bracket(_Open, H)
  353    ->  Nesting = [close(I,H)|Nest]
  354    ),
  355    !,
  356    I2 is I+1,
  357    nesting(T, I2, Nest).
  358nesting([0'0, 0'\'|T], I, Nesting) :-
  359    !,
  360    phrase(skip_code, T, T1),
  361    difflist_length(T, T1, Len),
  362    I2 is I+Len+2,
  363    nesting(T1, I2, Nesting).
  364nesting([H|T], I, Nesting) :-
  365    quote(H),
  366    !,
  367    (   phrase(skip_quoted(H), T, T1)
  368    ->  difflist_length(T, T1, Len),
  369        I2 is I+Len+1,
  370        Nesting = [open(I,H),close(I2,H)|Nest],
  371        nesting(T1, I2, Nest)
  372    ;   Nesting = [open(I,H)]                   % Open quote
  373    ).
  374nesting([_|T], I, Nesting) :-
  375    I2 is I+1,
  376    nesting(T, I2, Nesting).
  377
  378difflist_length(List, Tail, Len) :-
  379    difflist_length(List, Tail, 0, Len).
  380
  381difflist_length(List, Tail, Len0, Len) :-
  382    List == Tail,
  383    !,
  384    Len = Len0.
  385difflist_length([_|List], Tail, Len0, Len) :-
  386    Len1 is Len0+1,
  387    difflist_length(List, Tail, Len1, Len).
  388
  389skip_quoted(H) -->
  390    [H],
  391    !.
  392skip_quoted(H) -->
  393    "\\", [H],
  394    !,
  395    skip_quoted(H).
  396skip_quoted(H) -->
  397    [_],
  398    skip_quoted(H).
  399
  400skip_code -->
  401    "\\", [_],
  402    !.
  403skip_code -->
  404    [_].
  405
  406
  407		 /*******************************
  408		 *           COMPLETION		*
  409		 *******************************/
 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.
  420:- dynamic
  421    last_complete/2.  422
  423complete(Input, _Char, Continue) :-
  424    el_line(Input, line(Before, After)),
  425    prolog:complete_input(Before, After, Delete, Completions),
  426    (   Completions = [One]
  427    ->  string_length(Delete, Len),
  428        el_deletestr(Input, Len),
  429        complete_text(One, Text),
  430        el_insertstr(Input, Text),
  431        Continue = refresh
  432    ;   Completions == []
  433    ->  Continue = refresh_beep
  434    ;   get_time(Now),
  435        retract(last_complete(TLast, Before)),
  436        Now - TLast < 2
  437    ->  nl(user_error),
  438        list_alternatives(Completions),
  439        Continue = redisplay
  440    ;   retractall(last_complete(_,_)),
  441        get_time(Now),
  442        asserta(last_complete(Now, Before)),
  443        common_competion(Completions, Extend),
  444        (   Delete == Extend
  445        ->  Continue = refresh_beep
  446        ;   string_length(Delete, Len),
  447            el_deletestr(Input, Len),
  448            el_insertstr(Input, Extend),
  449            Continue = refresh
  450        )
  451    ).
 show_completions(+Input, +Char, -Continue) is det
Editline command to show possible completions.
  457show_completions(Input, _Char, Continue) :-
  458    el_line(Input, line(Before, After)),
  459    prolog:complete_input(Before, After, _Delete, Completions),
  460    nl(user_error),
  461    list_alternatives(Completions),
  462    Continue = redisplay.
  463
  464complete_text(Text-_Comment, Text) :- !.
  465complete_text(Text, Text).
 common_competion(+Alternatives, -Common) is det
True when Common is the common prefix of all candidate Alternatives.
  471common_competion(Alternatives, Common) :-
  472    maplist(atomic, Alternatives),
  473    !,
  474    common_prefix(Alternatives, Common).
  475common_competion(Alternatives, Common) :-
  476    maplist(complete_text, Alternatives, AltText),
  477    !,
  478    common_prefix(AltText, Common).
 common_prefix(+Atoms, -Common) is det
True when Common is the common prefix of all Atoms.
  484common_prefix([A1|T], Common) :-
  485    common_prefix_(T, A1, Common).
  486
  487common_prefix_([], Common, Common).
  488common_prefix_([H|T], Common0, Common) :-
  489    common_prefix(H, Common0, Common1),
  490    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
  496common_prefix(A1, A2, Prefix) :-
  497    sub_atom(A1, 0, _, _, A2),
  498    !,
  499    Prefix = A2.
  500common_prefix(A1, A2, Prefix) :-
  501    sub_atom(A2, 0, _, _, A1),
  502    !,
  503    Prefix = A1.
  504common_prefix(A1, A2, Prefix) :-
  505    atom_codes(A1, C1),
  506    atom_codes(A2, C2),
  507    list_common_prefix(C1, C2, C),
  508    string_codes(Prefix, C).
  509
  510list_common_prefix([H|T0], [H|T1], [H|T]) :-
  511    !,
  512    list_common_prefix(T0, T1, T).
  513list_common_prefix(_, _, []).
 list_alternatives(+Alternatives)
List possible completions at the current point.
To be done
- currently ignores the Comment in Text-Comment alternatives.
  523list_alternatives(Alternatives) :-
  524    maplist(atomic, Alternatives),
  525    !,
  526    length(Alternatives, Count),
  527    maplist(atom_length, Alternatives, Lengths),
  528    max_list(Lengths, Max),
  529    tty_size(_, Cols),
  530    ColW is Max+2,
  531    Columns is max(1, Cols // ColW),
  532    RowCount is (Count+Columns-1)//Columns,
  533    length(Rows, RowCount),
  534    to_matrix(Alternatives, Rows, Rows),
  535    (   RowCount > 11
  536    ->  length(First, 10),
  537        Skipped is RowCount - 10,
  538        append(First, _, Rows),
  539        maplist(write_row(ColW), First),
  540        format(user_error, '... skipped ~D rows~n', [Skipped])
  541    ;   maplist(write_row(ColW), Rows)
  542    ).
  543list_alternatives(Alternatives) :-
  544    maplist(complete_text, Alternatives, AltText),
  545    list_alternatives(AltText).
  546
  547to_matrix([], _, Rows) :-
  548    !,
  549    maplist(close_list, Rows).
  550to_matrix([H|T], [RH|RT], Rows) :-
  551    !,
  552    add_list(RH, H),
  553    to_matrix(T, RT, Rows).
  554to_matrix(List, [], Rows) :-
  555    to_matrix(List, Rows, Rows).
  556
  557add_list(Var, Elem) :-
  558    var(Var), !,
  559    Var = [Elem|_].
  560add_list([_|T], Elem) :-
  561    add_list(T, Elem).
  562
  563close_list(List) :-
  564    append(List, [], _),
  565    !.
  566
  567write_row(ColW, Row) :-
  568    length(Row, Columns),
  569    make_format(Columns, ColW, Format),
  570    format(user_error, Format, Row).
  571
  572make_format(N, ColW, Format) :-
  573    format(string(PerCol), '~~w~~t~~~d+', [ColW]),
  574    Front is N - 1,
  575    length(LF, Front),
  576    maplist(=(PerCol), LF),
  577    append(LF, ['~w~n'], Parts),
  578    atomics_to_string(Parts, Format).
  579
  580
  581		 /*******************************
  582		 *             SEARCH		*
  583		 *******************************/
 isearch_history(+Input, +Char, -Continue) is det
Incremental search through the history. The behavior is based on GNU readline.
  590isearch_history(Input, _Char, Continue) :-
  591    el_line(Input, line(Before, After)),
  592    string_concat(Before, After, Current),
  593    string_length(Current, Len),
  594    search_print('', "", Current),
  595    search(Input, "", Current, 1, Line),
  596    el_deletestr(Input, Len),
  597    el_insertstr(Input, Line),
  598    Continue = redisplay.
  599
  600search(Input, For, Current, Nth, Line) :-
  601    el_getc(Input, Next),
  602    Next \== -1,
  603    !,
  604    search(Next, Input, For, Current, Nth, Line).
  605search(_Input, _For, _Current, _Nth, "").
  606
  607search(7, _Input, _, Current, _, Current) :-    % C-g: abort
  608    !,
  609    clear_line.
  610search(18, Input, For, Current, Nth, Line) :-   % C-r: search previous
  611    !,
  612    N2 is Nth+1,
  613    search_(Input, For, Current, N2, Line).
  614search(19, Input, For, Current, Nth, Line) :-   % C-s: search next
  615    !,
  616    N2 is max(1,Nth-1),
  617    search_(Input, For, Current, N2, Line).
  618search(127, Input, For, Current, _Nth, Line) :- % DEL/BS: shorten search
  619    sub_string(For, 0, _, 1, For1),
  620    !,
  621    search_(Input, For1, Current, 1, Line).
  622search(Char, Input, For, Current, Nth, Line) :-
  623    code_type(Char, cntrl),
  624    !,
  625    search_end(Input, For, Current, Nth, Line),
  626    el_push(Input, Char).
  627search(Char, Input, For, Current, _Nth, Line) :-
  628    format(string(For1), '~w~c', [For,Char]),
  629    search_(Input, For1, Current, 1, Line).
  630
  631search_(Input, For1, Current, Nth, Line) :-
  632    (   find_in_history(Input, For1, Current, Nth, Candidate)
  633    ->  search_print('', For1, Candidate)
  634    ;   search_print('failed ', For1, Current)
  635    ),
  636    search(Input, For1, Current, Nth, Line).
  637
  638search_end(Input, For, Current, Nth, Line) :-
  639    (   find_in_history(Input, For, Current, Nth, Line)
  640    ->  true
  641    ;   Line = Current
  642    ),
  643    clear_line.
  644
  645find_in_history(_, "", Current, _, Current) :-
  646    !.
  647find_in_history(Input, For, _, Nth, Line) :-
  648    el_history_events(Input, History),
  649    call_nth(( member(_N-Line, History),
  650               sub_string(Line, _, _, _, For)
  651             ),
  652             Nth),
  653    !.
  654
  655search_print(State, Search, Current) :-
  656    format(user_error, '\r(~wreverse-i-search)`~w\': ~w\e[0K',
  657           [State, Search, Current]).
  658
  659clear_line :-
  660    format(user_error, '\r\e[0K', [])