1:- module(lsp_colours, [file_colours/2,
    2                        file_range_colours/4,
    3                        token_types/1,
    4                        token_modifiers/1]).

LSP Colours

Module with predicates for colourizing Prolog code, via library(prolog_colour).

author
- James Cash */
   12:- use_module(library(apply), [maplist/4]).   13:- use_module(library(apply_macros)).   14:- use_module(library(debug), [debug/3]).   15:- use_module(library(lists), [numlist/3, nth0/3]).   16:- use_module(library(prolog_colour), [prolog_colourise_stream/3,
   17                                       prolog_colourise_term/4]).   18:- use_module(library(prolog_source), [read_source_term_at_location/3]).   19:- use_module(library(yall)).   20
   21:- include('_lsp_path_add.pl').   22:- use_module(lsp(lsp_changes), [doc_text/2]).   23:- use_module(lsp(lsp_utils), [seek_to_line/2,
   24                               linechar_offset/3]).   25
   26token_types([namespace,
   27             type,
   28             class,
   29             enum,
   30             interface,
   31             struct,
   32             typeParameter,
   33             parameter,
   34             variable,
   35             property,
   36             enumMember,
   37             event,
   38             function,
   39             member,
   40             macro,
   41             keyword,
   42             modifier,
   43             comment,
   44             string,
   45             number,
   46             regexp,
   47             operator
   48            ]).
   49token_modifiers([declaration,
   50                 definition,
   51                 readonly,
   52                 static,
   53                 deprecated,
   54                 abstract,
   55                 async,
   56                 modification,
   57                 documentation,
   58                 defaultLibrary
   59                ]).
   60
   61token_types_dict(Dict) :-
   62    token_types(Types),
   63    length(Types, Len),
   64    Len0 is Len - 1,
   65    numlist(0, Len0, Ns),
   66    maplist([Type, Idx, Type-Idx]>>true, Types, Ns,
   67            Pairs),
   68    dict_create(Dict, _, Pairs).
 file_colours(+File, -Colours) is det
True when Colours is a list of colour information corresponding to the file File.
   74file_colours(File, Tuples) :-
   75    file_colours_helper(File, Colours0),
   76    sort(2, @=<, Colours0, Colours),
   77    flatten_colour_terms(File, Colours, Tuples).
 file_range_colours(+File, +Start, +End, -Colours) is det
True when Colours is a list of colour information corresponding to file File covering the terms between Start and End. Note that it may go beyond either bound.
   84file_range_colours(File, Start, End, Tuples) :-
   85    file_term_colours_helper(File, Start, End, Colours0),
   86    sort(2, @=<, Colours0, Colours),
   87    flatten_colour_terms(File, Colours, Tuples).
   88
   89file_stream(File, S) :-
   90    ( doc_text(File, Changes)
   91    -> open_string(Changes, S)
   92    ;  open(File, read, S) ),
   93    set_stream(S, newline(posix)).
 flatten_colour_terms(+File, +ColourTerms, -Nums) is det
Convert the list of ColourTerms like =colour(Category, Start, Length)= to a flat list of numbers Nums in the format that LSP expects.
See also
- https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#textDocument_semanticTokens
  101flatten_colour_terms(File, ColourTerms, Nums) :-
  102    token_types_dict(TokenDict),
  103    setup_call_cleanup(
  104        file_stream(File, S),
  105        ( seek(S, 0, bof, _),
  106          phrase(colour_term_to_tuple(S, TokenDict, 0, 1, 0),
  107                 ColourTerms,
  108                 Nums)
  109        ),
  110        close(S)
  111    ).
  112
  113colour_term_to_tuple(Stream, Dict, LastOffset, LastLine, LastChar), R -->
  114    [colour(Type, NewOffset, Len)], !,
  115    { ( colour_type(Type, TypeCategory, Mods),
  116        get_dict(TypeCategory, Dict, TypeCode),
  117        mods_mask(Mods, ModMask) )
  118      -> Seek is NewOffset - LastOffset,
  119         Offset = NewOffset,
  120         stream_seek_line_position(Stream, Seek, Line, Char),
  121         ( Line == LastLine
  122         -> DeltaLine = 0,
  123            DeltaStart is Char - LastChar
  124         ; DeltaLine is Line - LastLine,
  125           DeltaStart = Char ),
  126         R = [DeltaLine, DeltaStart, Len, TypeCode, ModMask]
  127      ; R = [],
  128        Offset = LastOffset, Line = LastLine, Char = LastChar
  129    },
  130    colour_term_to_tuple(Stream, Dict, Offset, Line, Char).
  131colour_term_to_tuple(_, _, _, _, _) --> [].
 stream_offset_line_position(+Stream, +Seek, -Line, -Char) is det
Advance Stream by Seek characters, then Line will be the current line number that Stream is now at and Char is the position within the line.
  138stream_seek_line_position(Stream, Seek, Line, LineChar) :-
  139    setup_call_cleanup(open_null_stream(NullStream),
  140                       ( set_stream(NullStream, newline(posix)),
  141                         copy_stream_data(Stream, NullStream, Seek) ),
  142                       close(NullStream)),
  143    stream_property(Stream, position(Pos)),
  144    stream_position_data(line_count, Pos, Line),
  145    % can't use line_position, because it counts tabs as 8 characters, which throws things off
  146    % stream_position_data(line_position, Pos, Char),
  147    line_position_characters(Stream, Pos, LineChar).
  148
  149line_position_characters(Stream, Pos, Char) :-
  150    stream_position_data(char_count, Pos, StartChar),
  151    stream_position_data(line_count, Pos, StartLine),
  152    ( StartLine > 1
  153    -> ( repeat,
  154         seek(Stream, -1, current, _),
  155         peek_code(Stream, 0'\n), !,
  156         get_code(Stream, _) )
  157    ; seek(Stream, 0, bof, _) ),
  158    character_count(Stream, StartOfLine),
  159    ( repeat,
  160      character_count(Stream, CharHere),
  161      get_code(Stream, _),
  162      CharHere >= StartChar, !
  163    ),
  164    Char is max(0, CharHere - StartOfLine),
  165    set_stream_position(Stream, Pos).
  166
  167colour_type(directive,                namespace, []).
  168
  169colour_type(head(_,              _),        function,  [declaration]).
  170% colour_type(head_term(_,              _),        function,  [declaration]).
  171colour_type(neck(directive),          operator,  [declaration]).
  172colour_type(neck(':-'),               operator,  [declaration]).
  173colour_type(neck(clause),             operator,  [definition]).
  174colour_type(neck(grammar_rule),       operator,  [definition]).
  175colour_type(goal(built_in,       A),        macro,     []) :- atom(A), !.
  176colour_type(goal(built_in,       _),        function,  [defaultLibrary]).
  177colour_type(goal(undefined,      _),        function,  []).
  178colour_type(goal(imported(_),    _),        function,  []).
  179colour_type(goal(local(_),       _),        function,  []).
  180colour_type(goal(extern(_,_),    _),        function,  []).
  181colour_type(goal(recursion,      _),        member,    []).
  182colour_type(goal(('dynamic'(_)), _),        member, []).
  183% colour_type(goal_term(built_in,       A),        macro,     []) :- atom(A), !.
  184% colour_type(goal_term(built_in,       _),        function,  [defaultLibrary]).
  185% colour_type(goal_term(undefined,      _),        function,  []).
  186% colour_type(goal_term(imported(_),    _),        function,  []).
  187% colour_type(goal_term(local(_),       _),        function,  []).
  188% colour_type(goal_term(extern(_,_),    _),        function,  []).
  189% colour_type(goal_term(recursion,      _),        member,    []).
  190% colour_type(goal_term(('dynamic'(_)), _),        member, []).
  191colour_type(atom,                     string,    []).
  192colour_type(var,                      variable,  []).
  193colour_type(singleton,                variable,  [readonly]).
  194colour_type(fullstop,                 operator,  []).
  195colour_type(control,                  operator,  []).
  196colour_type(dict_key,                 property,  []).
  197colour_type(dict_sep,                 operator,  []).
  198colour_type(string,                   string,    []).
  199colour_type(int,                      number,    []).
  200colour_type(comment(line),            comment,   []).
  201colour_type(comment(structured),      comment,   [documentation]).
  202colour_type(arity,                    parameter, []).
  203colour_type(functor,                  struct,    []).
  204colour_type(option_name,              struct,    []).
  205colour_type(predicate_indicator,      interface, []).
  206colour_type(predicate_indicator(_,    _),        interface, []).
  207colour_type(unused_import,            macro,     [deprecated]).
  208colour_type(undefined_import,         macro,     [deprecated]).
  209colour_type(dcg,                      regexp,    []).
  210colour_type(dcg(terminal),            regexp,    []).
  211colour_type(dcg(plain),               function,  []).
  212colour_type(dcg_right_hand_ctx,       regexp,    []).
  213colour_type(grammar_rule,             regexp,    []).
  214colour_type(identifier,               namespace, []).
  215colour_type(file(_),                  namespace, []).
  216colour_type(file_no_depend(_),        namespace, [abstract]).
  217colour_type(module(_),                namespace, []).
  218
  219mods_mask(Mods, Mask) :-
  220    mods_mask(Mods, 0, Mask).
  221
  222mods_mask([], Mask, Mask).
  223mods_mask([Mod|Mods], Mask0, Mask) :-
  224    token_modifiers(ModsList),
  225    nth0(N, ModsList, Mod),
  226    Mask1 is Mask0 \/ (1 << N),
  227    mods_mask(Mods, Mask1, Mask).
  228
  229%%% Helpers
 file_colours_helper(+Queue, +File) is det
Use prolog_colourise_stream/3 to accumulate a list of colour terms.
  235file_colours_helper(File, Info) :-
  236    Acc = acc([]),
  237    setup_call_cleanup(
  238        file_stream(File, S),
  239        prolog_colourise_stream(
  240            S, File,
  241            {Acc}/[Cat, Start, Len]>>(
  242                arg(1, Acc, Data),
  243                nb_setarg(1, Acc, [colour(Cat, Start, Len)|Data])
  244            )
  245        ),
  246        close(S)
  247    ),
  248    arg(1, Acc, Info).
  249
  250nearest_term_start(Stream, StartL, TermStart) :-
  251    read_source_term_at_location(Stream, _, [line(StartL), error(Error)]),
  252    ( nonvar(Error)
  253    -> ( LineBack is StartL - 1,
  254         nearest_term_start(Stream, LineBack, TermStart) )
  255    ;  TermStart = StartL
  256    ).
  257
  258file_term_colours_helper(File, line_char(StartL, _StartC), End, Info) :-
  259    Acc = acc([]),
  260    setup_call_cleanup(
  261        file_stream(File, S),
  262        ( nearest_term_start(S, StartL, TermLine),
  263          seek(S, 0, bof, _),
  264          set_stream_position(S, '$stream_position'(0,0,0,0)),
  265          seek_to_line(S, TermLine),
  266          colourise_terms_to_position(Acc, File, S, 0-0, End)
  267        ),
  268        close(S)
  269    ),
  270    arg(1, Acc, Info).
  271
  272colourise_terms_to_position(Acc, File, Stream, Prev, End) :-
  273    once(prolog_colourise_term(
  274        Stream, File,
  275        {Acc}/[Cat, Start, Len]>>(
  276            arg(1, Acc, Tail),
  277            nb_setarg(1, Acc, [colour(Cat, Start, Len)|Tail])),
  278        [])),
  279    stream_property(Stream, position(Pos)),
  280    stream_position_data(line_count, Pos, Line),
  281    stream_position_data(line_position, Pos, Char),
  282    End = line_char(EndL, EndC),
  283    ( Line-Char == Prev
  284    -> true
  285    ;  EndL =< Line
  286    -> true
  287    ;  ( EndL == Line, EndC =< Char )
  288    -> true
  289    ; colourise_terms_to_position(Acc, File, Stream, Line-Char, End)
  290    )