1:- module(lsp_completion, [completions_at/3]).

LSP Completion

This module implements code completion, based on defined predicates in the file & imports.

Uses lsp_changes in order to see the state of the buffer being edited.

author
- James Cash */
See also
- doc_text_fallback/2
   14:- use_module(library(apply), [ maplist/3 ]).   15:- use_module(library(lists), [ numlist/3 ]).   16:- use_module(library(prolog_xref), [ xref_defined/3, xref_source/2 ]).   17:- use_module(library(yall)).   18
   19:- include('_lsp_path_add.pl').   20
   21:- use_module(lsp(lsp_utils), [ linechar_offset/3 ]).   22:- use_module(lsp(lsp_changes), [ doc_text_fallback/2 ]).   23
   24part_of_prefix(Code) :- code_type(Code, prolog_var_start).
   25part_of_prefix(Code) :- code_type(Code, prolog_atom_start).
   26part_of_prefix(Code) :- code_type(Code, prolog_identifier_continue).
   27
   28get_prefix_codes(Stream, Offset, Codes) :-
   29    get_prefix_codes(Stream, Offset, [], Codes).
   30
   31get_prefix_codes(Stream, Offset0, Codes0, Codes) :-
   32    peek_code(Stream, Code),
   33    part_of_prefix(Code), !,
   34    succ(Offset1, Offset0),
   35    seek(Stream, Offset1, bof, Offset),
   36    get_prefix_codes(Stream, Offset, [Code|Codes0], Codes).
   37get_prefix_codes(_, _, Codes, Codes).
   38
   39prefix_at(File, Position, Prefix) :-
   40    doc_text_fallback(File, DocCodes),
   41    setup_call_cleanup(
   42        open_string(DocCodes, Stream),
   43        ( linechar_offset(Stream, Position, _),
   44          seek(Stream, -1, current, Offset),
   45          get_prefix_codes(Stream, Offset, PrefixCodes),
   46          string_codes(Prefix, PrefixCodes) ),
   47        close(Stream)
   48    ).
   49
   50completions_at(File, Position, Completions) :-
   51    prefix_at(File, Position, Prefix),
   52    xref_source(File, [silent(true)]),
   53    findall(
   54        Result,
   55        ( xref_defined(File, Goal, _),
   56          functor(Goal, Name, Arity),
   57          atom_concat(Prefix, _, Name),
   58          ( predicate_arguments(File, Name/Arity, Args) -> true ; args_str(Arity, Args) ),
   59          format(string(Func), "~w(~w)$0", [Name, Args]),
   60          format(string(Label), "~w/~w", [Name, Arity]),
   61          Result = _{label: Label,
   62                     insertText: Func,
   63                     insertTextFormat: 2} ),
   64        Completions,
   65        CompletionsTail
   66    ),
   67    findall(
   68        Result,
   69        ( predicate_property(system:Goal, built_in),
   70          functor(Goal, Name, Arity),
   71          atom_concat(Prefix, _, Name),
   72          \+ sub_atom(Name, 0, _, _, '$'),
   73          ( predicate_arguments(File, Name/Arity, Args) -> true ; args_str(Arity, Args) ),
   74          format(string(Func), "~w(~w)$0", [Name, Args]),
   75          format(string(Label), "~w/~w", [Name, Arity]),
   76          Result = _{label: Label,
   77                     insertText: Func,
   78                     insertTextFormat: 2} ),
   79        CompletionsTail
   80    ).
   81
   82predicate_arguments(File, Pred/Arity, ArgsStr) :-
   83    lsp_utils:predicate_help(File, Pred/Arity, HelpText),
   84    string_concat(Pred, "(", PredName),
   85    sub_string(HelpText, BeforeName, NameLen, _, PredName),
   86    sub_string(HelpText, BeforeClose, _, _, ")"),
   87    BeforeClose > BeforeName, !,
   88    ArgsStart is BeforeName + NameLen,
   89    ArgsLength is BeforeClose - ArgsStart,
   90    sub_string(HelpText, ArgsStart, ArgsLength, _, ArgsStr0),
   91    atomic_list_concat(Args, ', ', ArgsStr0),
   92    length(Args, Length),
   93    Arity = Length,
   94    numlist(1, Length, Nums),
   95    maplist([Arg, Num, S]>>format(string(S), "${~w:~w}", [Num, Arg]),
   96            Args, Nums, Args1),
   97    atomic_list_concat(Args1, ', ', ArgsStr).
   98
   99args_str(Arity, Str) :-
  100    numlist(1, Arity, Args),
  101    maplist([A, S]>>format(string(S), "${~w:_}", [A]),
  102            Args, ArgStrs),
  103    atomic_list_concat(ArgStrs, ', ', Str)