#!/usr/bin/env swipl

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This script may be used to open   edit windows on an existing SWI-Prolog
instance that has the built-in editor PceEmacs open. It works similar to
`emacsclient` for GNU-Emacs.

To make use of this to the max on Linux systems we must:

  - enable terminal hyperlinks using this in `~/.config/swi-prolog/init.pl`

       :- set_prolog_flag(hyperlink_term, true).

  - Create a desktop application link by adding a file to
    `~/.local/share/applications`, e.g. `edit.desktop`. This file
    contains something like below.  The ``%u`` ensures the entire
    URL from the hyperlink is passed

```
[Desktop Entry]
Exec=edit --no-wait %u
MimeType=application/x-perl;
Name=PceEmacs
Encoding=UTF-8
Type=Application
Terminal=false
Icon=/home/janw/.local/share/applications/swipl.png
Categories=Utility;Development;TextEditor;
Keywords=Text;Editor;

```

  - Install this script in ``$PATH`` as `edit` and make it executable

  - Add entries to the ``~/.config/mimeapps.list`` for the mime-types
    you want handled by this editor.  To the minimum add `x-perl` for
    *.pl files (or update the mime database to change .pl into
    ``application/x-prolog``).

        application/x-perl=edit.desktop

After these changes, errors and warnings from SWI-Prolog in the terminal
become clickable.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

:- use_module(library(socket)).
:- use_module(library(dcg/basics)).
:- use_module(library(filesex)).
:- use_module(library(lists)).
:- use_module(library(main)).
:- use_module(library(option)).
:- use_module(library(process)).
:- use_module(library(socket)).
:- use_module(library(uri)).

:- initialization(main,main).

main(Argv) :-
    argv_options(Argv, Positional, Options),
    (   Positional = [Spec]
    ->  target(Spec, File, FOptions),
        absolute_file_name(File, AbsFile),
        append(Options, FOptions, EOptions),
        call_editor(AbsFile, EOptions)
    ;   argv_usage(debug),
        halt(1)
    ).

opt_type(wait, wait, boolean).
opt_help(wait, "When false, do not wait").
opt_help(help(usage), "edit [--no-wait] Spec").

target(Spec, File, Options) :-
    sub_atom(Spec, 0, _, _, 'file://'),
    !,
    uri_file_name(Spec, File),
    uri_components(Spec, Components),
    uri_data(fragment, Components, Fragment),
    (   nonvar(Fragment)
    ->  atom_codes(Fragment, Codes),
        phrase(fragment_line(Options), Codes)
    ;   Options = []
    ).
target(Spec, File, Options) :-
    split_string(Spec, ":", "", Parts),
    (   append(Pre, [LS,CS], Parts),
        number_string(Line, LS),
        number_string(Column, CS)
    ->  atomic_list_concat(Pre, :, File),
        Options = [line(Line), column(Column)]
    ;   append(Pre, [LS], Parts),
        number_string(Line, LS)
    ->  atomic_list_concat(Pre, :, File),
        Options = [line(Line)]
    ;   File = Spec,
        Options = []
    ).

fragment_line([line(Line)|Extra]) -->
    opt_L,
    integer(Line),
    (   ":"
    ->  integer(Column),
        {Extra = [column(Column)]}
    ;   {Extra = []}
    ).

opt_L -->
    "L",
    !.
opt_L -->
    "".

call_editor(File, Options) :-
    pce_emacs_command(File, Options, Command),
    server_socket(SocketFile),
    unix_domain_socket(Socket),
    catch(tcp_connect(Socket, SocketFile),
          _,
          (tcp_close_socket(Socket), fail)),
    !,
    tcp_open_socket(Socket, Stream),
    format(Stream, '~q~n', [Command]),
    flush_output(Stream),
    (   option(wait(true), Options, true)
    ->  copy_stream_data(Stream, user_error)
    ;   true
    ).
call_editor(File, Options) :-
    (   option(line(Line), Options)
    ->  atom_concat(+, Line, Av1),
        Argv = [Av1|Argv1]
    ;   Argv = Argv1
    ),
    Argv1 = [File],
    process_create(path(emacs), Argv, []).


pce_emacs_command(File, EOptions, edit(File, Line, Column, Wait)) :-
    option(line(Line), EOptions, []),
    option(column(Column), EOptions, []),
    (   option(wait(false), EOptions)
    ->  Wait = nowait
    ;   Wait = wait
    ).

server_socket(Socket) :-
    getenv('DISPLAY', Display),
    split_string(Display, ":", "", ["",NS]),
    !,
    server_base(Base),
    format(atom(Socket), '~w.~w', [Base, NS]).
server_socket(Socket) :-
    gethostname(Local),
    server_base(Base),
    format(atom(Socket), '~w.~w', [Base, Local]).

server_base(Socket) :-
    absolute_file_name(app_config(xpce), Dir,
                       [ file_type(directory),
                         access(read)
                       ]),
    directory_file_path(Dir, emacs_server, Socket).
