1:- module(lsp_refactor, [ rename_at_location/4 ]).    2
    3:- include('_lsp_path_add.pl').    4
    5:- use_module(library(apply_macros)).    6:- use_module(library(yall)).    7
    8:- use_module(lsp(lsp_highlights)).    9:- use_module(lsp(lsp_source), [loaded_source/1]).   10:- use_module(lsp(lsp_reading_source)).   11:- use_module(lsp(lsp_utils)).   12
   13rename_at_location(Uri, line_char(Line1, Char0), NewName, Edits) :-
   14    url_path(Uri, Path),
   15    % highlights_at_position gives us the location & span of the variables
   16    % using the 4-arity version instead of 3 so we can specify it should only match a variable
   17    lsp_highlights:highlights_at_position(Path, line_char(Line1, Char0), '$var'(_),
   18                                          Positions), !,
   19    maplist({NewName}/[P0, P1]>>put_dict(newText, P0, NewName, P1), Positions, Edits),
   20    atom_string(AUri, Uri), % dict key must be an atom
   21    dict_create(Edits, _, [AUri=Edits]).
   22rename_at_location(Uri, line_char(Line1, Char0), NewName, Edits) :-
   23    url_path(Uri, Path),
   24    clause_in_file_at_position(Clause, Path, line_char(Line1, Char0)),
   25    clause_references(Clause, RefLocations),
   26    clause_definitions(Clause, DefLocations),
   27    clause_exports(Clause, ExportLocations),
   28    clause_imports(Clause, ImportLocations),
   29    % TODO: rename pldoc as well?
   30    append([RefLocations, DefLocations, ExportLocations, ImportLocations], Locations),
   31    maplist({NewName}/[P0, P1]>>put_dict(newText, P0, NewName, P1), Locations, Edits0),
   32    setof(
   33        RefUri=DocEdits,
   34        setof(
   35            DocEdit,
   36            Edit^(
   37                member(Edit, Edits0),
   38                del_dict(uri, Edit, RefUri, DocEdit)
   39            ),
   40            DocEdits
   41        ),
   42        EditsList
   43    ),
   44    dict_create(Edits, @, EditsList).
   45
   46clause_references(Clause, LLocations) :-
   47    findall(
   48        Locations,
   49        ( loaded_source(Doc),
   50          url_path(DocUri, Doc),
   51          called_at(Doc, Clause, Locs0),
   52          % handle the case where Caller = imported(Path)?
   53          maplist({DocUri}/[D0, D]>>put_dict(uri, D0, DocUri, D), Locs0, Locations)
   54        ),
   55        LLocations0),
   56    append(LLocations0, LLocations).
   57
   58clause_definitions(Clause, Locations) :-
   59    name_callable(Clause, Callable),
   60    findall(
   61        Location,
   62        ( loaded_source(Doc),
   63          xref_source(Doc),
   64          xref_defined(Doc, Callable, local(_)),
   65          definition_location_in(Doc, Callable, Location)
   66        ),
   67        Locations).
   68
   69definition_location_in(Doc, Callable, Location) :-
   70    file_lines_start_end(Doc, LineCharRange),
   71    read_term_positions(Doc, TermPositions),
   72    url_path(DocUri, Doc),
   73    member(TermPos, TermPositions),
   74    ( ( get_dict(term, TermPos, (Callable :- _)),
   75        get_dict(subterm, TermPos, SubTermPos0),
   76        SubTermPos0 = term_position(_, _, _, _, [term_position(_, _, FFrom, FTo, _)|_])
   77      ) ; (
   78          get_dict(term, TermPos, Callable),
   79          get_dict(subterm, TermPos, SubTermPos0),
   80          SubTermPos0 = term_position(_, _, FFrom, FTo, _)
   81      ) ),
   82    file_offset_line_position(LineCharRange, FFrom, StartLine1, StartChar),
   83    succ(StartLine0, StartLine1),
   84    file_offset_line_position(LineCharRange, FTo, EndLine1, EndChar),
   85    succ(EndLine0, EndLine1),
   86    Location = @{uri: DocUri,
   87                 range: @{start: @{line: StartLine0, character: StartChar},
   88                          end: @{line: EndLine0, character: EndChar}}}.
   89
   90clause_exports(Clause, Locations) :-
   91    name_callable(Clause, Callable),
   92    findall(
   93        Location,
   94        ( loaded_source(Doc),
   95          xref_source(Doc),
   96          xref_defined(Doc, Callable, local(_)),
   97          export_location_in(Doc, Clause, Location)
   98        ),
   99        Locations).
  100
  101export_location_in(Doc, Clause, Location) :-
  102    file_lines_start_end(Doc, LineCharRange),
  103    read_term_positions(Doc, TermPositions),
  104    member(TermPos, TermPositions),
  105    get_dict(term, TermPos, (:- module(_, Exports))),
  106    member(Clause, Exports), !,
  107    find_in_term_with_positions(
  108        {Clause}/[X, _]>>( nonvar(X), X = Clause ),
  109        TermPos.term, TermPos.subterm,
  110        Matches, []),
  111    % matches are for the '/'(Name, Arity) term but we just want to change Name
  112    maplist([found_at(Name/_Arity, term_position(_F, _T, _FF, _FT, [Start-End|_]))]>>(
  113                position_to_match(LineCharRange, found_at(Name, Start-End))
  114            ),
  115            Matches, Locations),
  116    member(Location0, Locations),
  117    url_path(Uri, Doc),
  118    put_dict(uri, Location0, Uri, Location).
  119
  120clause_imports(Clause, Locations) :-
  121    name_callable(Clause, Callable),
  122    findall(
  123        Location,
  124        ( loaded_source(Doc),
  125          xref_source(Doc),
  126          % Use xref_defined with imported or called?
  127          xref_defined(Doc, Callable, imported(_)),
  128          import_location_in(Doc, Clause, Location)
  129        ),
  130        Locations).
  131
  132import_location_in(Doc, Clause, Location) :-
  133    file_lines_start_end(Doc, LineCharRange),
  134    read_term_positions(Doc, TermPositions),
  135    member(TermPos, TermPositions),
  136    get_dict(term, TermPos, (:- use_module(_, Imports))),
  137    member(Clause, Imports), !,
  138    find_in_term_with_positions(
  139        {Clause}/[X, _]>>( nonvar(X), X = Clause ),
  140        TermPos.term, TermPos.subterm,
  141        Matches, []),
  142    % matches are for the '/'(Name, Arity) term but we just want to change Name
  143    maplist([found_at(Name/_Arity, term_position(_F, _T, _FF, _FT, [Start-End|_]))]>>(
  144                position_to_match(LineCharRange, found_at(Name, Start-End))
  145            ),
  146            Matches, Locations),
  147    member(Location0, Locations),
  148    url_path(Uri, Doc),
  149    put_dict(uri, Location0, Uri, Location)