36
37:- module(swish_highlight,
38 [ current_highlight_state/2, 39 man_predicate_summary/2 40 ]). 41:- use_module(library(debug)). 42:- use_module(library(settings)). 43:- use_module(library(http/http_dispatch)). 44:- use_module(library(http/html_write)). 45:- use_module(library(http/http_json)). 46:- use_module(library(http/http_path), []). 47:- use_module(library(http/http_parameters)). 48:- use_module(library(http/http_cors)). 49:- use_module(library(pairs)). 50:- use_module(library(apply)). 51:- use_module(library(error)). 52:- use_module(library(prolog_xref)). 53:- use_module(library(memfile)). 54:- use_module(library(prolog_colour)). 55:- use_module(library(lazy_lists)). 56:- if(exists_source(library(pldoc/man_index))). 57:- use_module(library(pldoc/man_index)). 58:- endif. 59
60http:location(codemirror, swish(cm), []).
61
62:- http_handler(codemirror(.), http_404([]), [id(cm_highlight)]). 63:- http_handler(codemirror(change), codemirror_change, []). 64:- http_handler(codemirror(tokens), codemirror_tokens, []). 65:- http_handler(codemirror(leave), codemirror_leave, []). 66:- http_handler(codemirror(info), token_info, []). 67
68:- setting(swish:editor_max_idle_time, nonneg, 3600,
69 "Maximum time we keep a mirror editor around"). 70
80
81 84
102
103codemirror_change(Request) :-
104 memberchk(method(options), Request),
105 !,
106 cors_enable(Request,
107 [ methods([post])
108 ]),
109 format('~n').
110codemirror_change(Request) :-
111 cors_enable,
112 call_cleanup(codemirror_change_(Request),
113 check_unlocked).
114
115codemirror_change_(Request) :-
116 http_read_json_dict(Request, Change, []),
117 debug(cm(change), 'Change ~p', [Change]),
118 atom_string(UUID, Change.uuid),
119 catch(shadow_editor(Change, TB),
120 cm(Reason), true),
121 ( var(Reason)
122 -> ( catch(apply_change(TB, Changed, Change.change),
123 cm(outofsync), fail)
124 -> mark_changed(TB, Changed),
125 release_editor(UUID),
126 reply_json_dict(true)
127 ; destroy_editor(UUID),
128 change_failed(UUID, outofsync)
129 )
130 ; change_failed(UUID, Reason)
131 ).
132
133change_failed(UUID, Reason) :-
134 reply_json_dict(json{ type:Reason,
135 object:UUID
136 },
137 [status(409)]).
138
139
148
149apply_change(_, _Changed, []) :- !.
150apply_change(TB, Changed, Change) :-
151 _{from:From} :< Change,
152 Line is From.line+1,
153 memory_file_line_position(TB, Line, From.ch, ChPos),
154 remove(Change.removed, TB, ChPos, Changed),
155 insert(Change.text, TB, ChPos, _End, Changed),
156 ( Next = Change.get(next)
157 -> apply_change(TB, Changed, Next)
158 ; true
159 ).
160
161remove([], _, _, _) :- !.
162remove([H|T], TB, ChPos, Changed) :-
163 string_length(H, Len),
164 ( T == []
165 -> DLen is Len
166 ; DLen is Len+1
167 ),
168 ( DLen == 0
169 -> true
170 ; Changed = true,
171 memory_file_substring(TB, ChPos, Len, _, Text),
172 ( Text == H
173 -> true
174 ; throw(cm(outofsync))
175 ),
176 delete_memory_file(TB, ChPos, DLen)
177 ),
178 remove(T, TB, ChPos, Changed).
179
180insert([], _, ChPos, ChPos, _) :- !.
181insert([H|T], TB, ChPos0, ChPos, Changed) :-
182 ( H == ""
183 -> Len = 0
184 ; Changed = true,
185 string_length(H, Len),
186 debug(cm(change_text), 'Insert ~q at ~d', [H, ChPos0]),
187 insert_memory_file(TB, ChPos0, H)
188 ),
189 ChPos1 is ChPos0+Len,
190 ( T == []
191 -> ChPos2 = ChPos1
192 ; debug(cm(change_text), 'Adding newline at ~d', [ChPos1]),
193 Changed = true,
194 insert_memory_file(TB, ChPos1, '\n'),
195 ChPos2 is ChPos1+1
196 ),
197 insert(T, TB, ChPos2, ChPos, Changed).
198
199:- dynamic
200 current_editor/5, 201 editor_last_access/2, 202 xref_upto_data/1. 203
209
210create_editor(UUID, Editor, Change) :-
211 must_be(atom, UUID),
212 uuid_like(UUID),
213 new_memory_file(Editor),
214 ( RoleString = Change.get(role)
215 -> atom_string(Role, RoleString)
216 ; Role = source
217 ),
218 get_time(Now),
219 mutex_create(Lock),
220 with_mutex(swish_create_editor,
221 register_editor(UUID, Editor, Role, Lock, Now)), !.
222create_editor(UUID, Editor, _Change) :-
223 fetch_editor(UUID, Editor).
224
226register_editor(UUID, Editor, Role, Lock, Now) :-
227 \+ current_editor(UUID, _, _, _, _),
228 mutex_lock(Lock),
229 asserta(current_editor(UUID, Editor, Role, Lock, Now)).
230
234
235current_highlight_state(UUID,
236 highlight{data:Editor,
237 role:Role,
238 created:Created,
239 lock:Lock,
240 access:Access
241 }) :-
242 current_editor(UUID, Editor, Role, Lock, Created),
243 ( editor_last_access(Editor, Access)
244 -> true
245 ; Access = Created
246 ).
247
248
254
255uuid_like(UUID) :-
256 split_string(UUID, "-", "", Parts),
257 maplist(string_length, Parts, [8,4,4,4,12]),
258 \+ current_editor(UUID, _, _, _, _).
259
266
267destroy_editor(UUID) :-
268 must_be(atom, UUID),
269 current_editor(UUID, Editor, _, Lock, _), !,
270 mutex_unlock(Lock),
271 retractall(xref_upto_data(UUID)),
272 retractall(editor_last_access(UUID, _)),
273 ( xref_source_id(UUID, SourceID)
274 -> xref_clean(SourceID),
275 destroy_state_module(UUID)
276 ; true
277 ),
278 279 retractall(current_editor(UUID, Editor, _, _, _)),
280 free_memory_file(Editor).
281destroy_editor(_).
282
295
296:- dynamic
297 gced_editors/1. 298
299editor_max_idle_time(Time) :-
300 setting(swish:editor_max_idle_time, Time).
301
302gc_editors :-
303 get_time(Now),
304 ( gced_editors(Then),
305 editor_max_idle_time(MaxIdle),
306 Now - Then < MaxIdle/3
307 -> true
308 ; retractall(gced_editors(_)),
309 asserta(gced_editors(Now)),
310 fail
311 ).
312gc_editors :-
313 editor_max_idle_time(MaxIdle),
314 forall(garbage_editor(UUID, MaxIdle),
315 destroy_garbage_editor(UUID)).
316
317garbage_editor(UUID, TimeOut) :-
318 get_time(Now),
319 current_editor(UUID, _TB, _Role, _Lock, Created),
320 Now - Created > TimeOut,
321 ( editor_last_access(UUID, Access)
322 -> Now - Access > TimeOut
323 ; true
324 ).
325
326destroy_garbage_editor(UUID) :-
327 fetch_editor(UUID, _TB), !,
328 destroy_editor(UUID).
329destroy_garbage_editor(_).
330
336
337fetch_editor(UUID, TB) :-
338 current_editor(UUID, TB, Role, Lock, _),
339 catch(mutex_lock(Lock), error(existence_error(mutex,_),_), fail),
340 debug(cm(lock), 'Locked ~p', [UUID]),
341 ( current_editor(UUID, TB, Role, Lock, _)
342 -> update_access(UUID)
343 ; mutex_unlock(Lock)
344 ).
345
346release_editor(UUID) :-
347 current_editor(UUID, _TB, _Role, Lock, _),
348 debug(cm(lock), 'Unlocked ~p', [UUID]),
349 mutex_unlock(Lock).
350
351check_unlocked :-
352 check_unlocked(unknown).
353
358
359check_unlocked(Reason) :-
360 thread_self(Me),
361 current_editor(_UUID, _TB, _Role, Lock, _),
362 mutex_property(Lock, status(locked(Me, _Count))), !,
363 unlock(Me, Lock),
364 print_message(error, locked(Reason, Me)),
365 assertion(fail).
366check_unlocked(_).
367
368unlock(Me, Lock) :-
369 mutex_property(Lock, status(locked(Me, _Count))), !,
370 mutex_unlock(Lock),
371 unlock(Me, Lock).
372unlock(_, _).
373
378
379update_access(UUID) :-
380 get_time(Now),
381 ( editor_last_access(UUID, Last),
382 Now-Last < 60
383 -> true
384 ; retractall(editor_last_access(UUID, _)),
385 asserta(editor_last_access(UUID, Now))
386 ).
387
388:- multifile
389 prolog:xref_source_identifier/2,
390 prolog:xref_open_source/2,
391 prolog:xref_close_source/2. 392
393prolog:xref_source_identifier(UUID, UUID) :-
394 current_editor(UUID, _, _, _, _).
395
402
403:- if(current_predicate(prolog_source:close_source/3)). 404prolog:xref_open_source(UUID, Stream) :-
405 fetch_editor(UUID, TB),
406 open_memory_file(TB, read, Stream).
407
408prolog:xref_close_source(UUID, Stream) :-
409 release_editor(UUID),
410 close(Stream).
411:- else. 412prolog:xref_open_source(UUID, Stream) :-
413 fetch_editor(UUID, TB),
414 open_memory_file(TB, read, Stream),
415 release_editor(UUID).
416:- endif. 417
423
424codemirror_leave(Request) :-
425 memberchk(method(options), Request),
426 !,
427 cors_enable(Request,
428 [ methods([post])
429 ]),
430 format('~n').
431codemirror_leave(Request) :-
432 cors_enable,
433 call_cleanup(codemirror_leave_(Request),
434 check_unlocked).
435
436codemirror_leave_(Request) :-
437 http_read_json_dict(Request, Data, []),
438 ( atom_string(UUID, Data.get(uuid))
439 -> debug(cm(leave), 'Leaving editor ~p', [UUID]),
440 ( fetch_editor(UUID, _TB)
441 -> destroy_editor(UUID)
442 ; debug(cm(leave), 'No editor for ~p', [UUID])
443 )
444 ; debug(cm(leave), 'No editor?? (data=~p)', [Data])
445 ),
446 reply_json_dict(true).
447
451
452mark_changed(MemFile, Changed) :-
453 ( Changed == true,
454 current_editor(UUID, MemFile, _Role, _, _)
455 -> retractall(xref_upto_data(UUID))
456 ; true
457 ).
458
460
461xref(UUID) :-
462 xref_upto_data(UUID), !.
463xref(UUID) :-
464 setup_call_cleanup(
465 fetch_editor(UUID, _TB),
466 ( xref_source_id(UUID, SourceId),
467 xref_state_module(UUID, Module),
468 xref_source(SourceId,
469 [ silent(true),
470 module(Module)
471 ]),
472 asserta(xref_upto_data(UUID))
473 ),
474 release_editor(UUID)).
475
480
481xref_source_id(UUID, UUID).
482
487
488xref_state_module(UUID, UUID) :-
489 ( module_property(UUID, class(temporary))
490 -> true
491 ; set_module(UUID:class(temporary)),
492 add_import_module(UUID, swish, start),
493 maplist(copy_flag(UUID, swish), [var_prefix])
494 ).
495
496copy_flag(Module, Application, Flag) :-
497 current_prolog_flag(Application:Flag, Value), !,
498 set_prolog_flag(Module:Flag, Value).
499copy_flag(_, _, _).
500
501destroy_state_module(UUID) :-
502 module_property(UUID, class(temporary)), !,
503 '$destroy_module'(UUID).
504destroy_state_module(_).
505
506
507 510
515
516codemirror_tokens(Request) :-
517 memberchk(method(options), Request),
518 !,
519 cors_enable(Request,
520 [ methods([post])
521 ]),
522 format('~n').
523codemirror_tokens(Request) :-
524 cors_enable,
525 setup_call_catcher_cleanup(
526 true,
527 codemirror_tokens_(Request),
528 Reason,
529 check_unlocked(Reason)).
530
531codemirror_tokens_(Request) :-
532 http_read_json_dict(Request, Data, []),
533 atom_string(UUID, Data.get(uuid)),
534 debug(cm(tokens), 'Asking for tokens: ~p', [Data]),
535 ( catch(shadow_editor(Data, TB), cm(Reason), true)
536 -> ( var(Reason)
537 -> call_cleanup(enriched_tokens(TB, Data, Tokens),
538 release_editor(UUID)),
539 reply_json_dict(json{tokens:Tokens}, [width(0)])
540 ; check_unlocked(Reason),
541 change_failed(UUID, Reason)
542 )
543 ; reply_json_dict(json{tokens:[[]]})
544 ),
545 gc_editors.
546
547
548enriched_tokens(TB, _Data, Tokens) :- 549 current_editor(UUID, TB, source, _Lock, _), !,
550 xref(UUID),
551 server_tokens(TB, Tokens).
552enriched_tokens(TB, Data, Tokens) :- 553 json_source_id(Data.get(sourceID), SourceID), !,
554 memory_file_to_string(TB, Query),
555 with_mutex(swish_highlight_query,
556 prolog_colourise_query(Query, SourceID, colour_item(TB))),
557 collect_tokens(TB, Tokens).
558enriched_tokens(TB, _Data, Tokens) :-
559 memory_file_to_string(TB, Query),
560 prolog_colourise_query(Query, module(swish), colour_item(TB)),
561 collect_tokens(TB, Tokens).
562
568
569:- if(current_predicate(prolog_colour:to_list/2)). 570json_source_id(StringList, SourceIDList) :-
571 is_list(StringList),
572 StringList \== [], !,
573 maplist(string_source_id, StringList, SourceIDList).
574:- else. 575json_source_id([String|_], SourceID) :-
576 maplist(string_source_id, String, SourceID).
577:- endif. 578json_source_id(String, SourceID) :-
579 string(String),
580 string_source_id(String, SourceID).
581
582string_source_id(String, SourceID) :-
583 atom_string(SourceID, String),
584 ( fetch_editor(SourceID, _TB)
585 -> release_editor(SourceID)
586 ; true
587 ).
588
589
606
607shadow_editor(Data, TB) :-
608 atom_string(UUID, Data.get(uuid)),
609 setup_call_catcher_cleanup(
610 fetch_editor(UUID, TB),
611 once(update_editor(Data, UUID, TB)),
612 Catcher,
613 cleanup_update(Catcher, UUID)), !.
614shadow_editor(Data, TB) :-
615 Text = Data.get(text), !,
616 atom_string(UUID, Data.uuid),
617 create_editor(UUID, TB, Data),
618 debug(cm(change), 'Create editor for ~p', [UUID]),
619 debug(cm(change_text), 'Initialising editor to ~q', [Text]),
620 insert_memory_file(TB, 0, Text).
621shadow_editor(Data, TB) :-
622 _{role:_} :< Data, !,
623 atom_string(UUID, Data.uuid),
624 create_editor(UUID, TB, Data).
625shadow_editor(_Data, _TB) :-
626 throw(cm(existence_error)).
627
628update_editor(Data, _UUID, TB) :-
629 Text = Data.get(text), !,
630 size_memory_file(TB, Size),
631 delete_memory_file(TB, 0, Size),
632 insert_memory_file(TB, 0, Text),
633 mark_changed(TB, true).
634update_editor(Data, UUID, TB) :-
635 Changes = Data.get(changes), !,
636 ( debug(cm(change), 'Patch editor for ~p', [UUID]),
637 maplist(apply_change(TB, Changed), Changes)
638 -> true
639 ; throw(cm(out_of_sync))
640 ),
641 mark_changed(TB, Changed).
642
643cleanup_update(exit, _) :- !.
644cleanup_update(_, UUID) :-
645 release_editor(UUID).
646
647:- thread_local
648 token/3. 649
659
660:- public
661 show_mirror/1,
662 server_tokens/1. 663
664show_mirror(Role) :-
665 current_editor(_UUID, TB, Role, _Lock, _), !,
666 memory_file_to_string(TB, String),
667 write(user_error, String).
668
669server_tokens(Role) :-
670 current_editor(_UUID, TB, Role, _Lock, _), !,
671 enriched_tokens(TB, _{}, Tokens),
672 print_term(Tokens, [output(user_error)]).
673
678
679server_tokens(TB, GroupedTokens) :-
680 current_editor(UUID, TB, _Role, _Lock, _),
681 Ignore = error(syntax_error(swi_backslash_newline),_),
682 setup_call_cleanup(
683 asserta(user:thread_message_hook(Ignore, _, _), Ref),
684 setup_call_cleanup(
685 open_memory_file(TB, read, Stream),
686 ( set_stream_file(TB, Stream),
687 prolog_colourise_stream(Stream, UUID, colour_item(TB))
688 ),
689 close(Stream)),
690 erase(Ref)),
691 collect_tokens(TB, GroupedTokens).
692
693collect_tokens(TB, GroupedTokens) :-
694 findall(Start-Token, json_token(TB, Start, Token), Pairs),
695 keysort(Pairs, Sorted),
696 pairs_values(Sorted, Tokens),
697 group_by_term(Tokens, GroupedTokens).
698
699set_stream_file(_,_). 700
707
708group_by_term([], []) :- !.
709group_by_term(Flat, [Term|Grouped]) :-
710 take_term(Flat, Term, Rest),
711 group_by_term(Rest, Grouped).
712
713take_term([], [], []).
714take_term([H|T0], [H|T], R) :-
715 ( ends_term(H.get(type))
716 -> T = [],
717 R = T0
718 ; take_term(T0, T, R)
719 ).
720
721ends_term(fullstop).
722ends_term(syntax_error).
723
732
733json_token(TB, Start, Token) :-
734 retract(token(Style, Start0, Len)),
735 debug(color, 'Trapped ~q.', [token(Style, Start0, Len)]),
736 ( atomic_special(Style, Start0, Len, TB, Type, Attrs)
737 -> Start = Start0
738 ; style(Style, Type0, Attrs0)
739 -> ( Type0 = StartType-EndType
740 -> ( Start = Start0,
741 Type = StartType
742 ; Start is Start0+Len-1,
743 Type = EndType
744 )
745 ; Type = Type0,
746 Start = Start0
747 ),
748 json_attributes(Attrs0, Attrs, TB, Start0, Len)
749 ),
750 dict_create(Token, json, [type(Type)|Attrs]).
751
752atomic_special(atom, Start, Len, TB, Type, Attrs) :-
753 memory_file_substring(TB, Start, 1, _, FirstChar),
754 ( FirstChar == "'"
755 -> Type = qatom,
756 Attrs = []
757 ; char_type(FirstChar, upper)
758 -> Type = uatom, 759 Attrs = []
760 ; Type = atom,
761 ( Len =< 5 762 -> memory_file_substring(TB, Start, Len, _, Text),
763 Attrs = [text(Text)]
764 ; Attrs = []
765 )
766 ).
767
768json_attributes([], [], _, _, _).
769json_attributes([H0|T0], Attrs, TB, Start, Len) :-
770 json_attribute(H0, Attrs, T, TB, Start, Len), !,
771 json_attributes(T0, T, TB, Start, Len).
772json_attributes([_|T0], T, TB, Start, Len) :-
773 json_attributes(T0, T, TB, Start, Len).
774
775json_attribute(text, [text(Text)|T], T, TB, Start, Len) :- !,
776 memory_file_substring(TB, Start, Len, _, Text).
777json_attribute(line(File:Line), [line(Line),file(File)|T], T, _, _, _) :- !.
778json_attribute(Term, [Term|T], T, _, _, _).
779
780colour_item(_TB, Style, Start, Len) :-
781 ( style(Style)
782 -> assertz(token(Style, Start, Len))
783 ; debug(color, 'Ignored ~q.', [token(Style, Start, Len)])
784 ).
785
812
813:- multifile
814 style/3. 815
816style(Style) :-
817 style(Style, _, _).
818
819style(neck(Neck), neck, [ text(Text) ]) :-
820 neck_text(Neck, Text).
821style(head(Class, Head), Type, [ text, arity(Arity) ]) :-
822 goal_arity(Head, Arity),
823 head_type(Class, Type).
824style(goal_term(_Class, Goal), var, []) :-
825 var(Goal), !.
826style(goal_term(Class, {_}), brace_term_open-brace_term_close,
827 [ name({}), arity(1) | More ]) :-
828 goal_type(Class, _Type, More).
829style(goal(Class, Goal), Type, [ text, arity(Arity) | More ]) :-
830 Goal \= {_},
831 goal_arity(Goal, Arity),
832 goal_type(Class, Type, More).
833style(file_no_depend(Path), file_no_depends, [text, path(Path)]).
834style(file(Path), file, [text, path(Path)]).
835style(nofile, nofile, [text]).
836style(option_name, option_name, [text]).
837style(no_option_name, no_option_name, [text]).
838style(flag_name(_Flag), flag_name, [text]).
839style(no_flag_name(_Flag), no_flag_name, [text]).
840style(fullstop, fullstop, []).
841style(var, var, [text]).
842style(singleton, singleton, [text]).
843style(string, string, []).
844style(codes, codes, []).
845style(chars, chars, []).
846style(atom, atom, []).
847style(rational(_Value), rational, [text]).
848style(format_string, format_string, []).
849style(meta(_Spec), meta, []).
850style(op_type(_Type), op_type, [text]).
851style(decl_option(_Name),decl_option, [text]).
852style(functor, functor, [text]).
853style(function, function, [text]).
854style(no_function, no_function, [text]).
855style(control, control, [text]).
856style(delimiter, delimiter, [text]).
857style(identifier, identifier, [text]).
858style(module(_Module), module, [text]).
859style(error, error, [text]).
860style(constraint(Set), constraint, [text, set(Set)]).
861style(type_error(Expect), error, [text,expected(Msg)]) :-
862 type_error_msg(Expect, Msg).
863style(syntax_error(_Msg,_Pos), syntax_error, []).
864style(instantiation_error, instantiation_error, [text]).
865style(predicate_indicator, atom, [text]).
866style(predicate_indicator, atom, [text]).
867style(arity, int, []).
868style(int, int, []).
869style(float, float, []).
870style(keyword(_), keyword, [text]).
871style(qq(open), qq_open, []).
872style(qq(sep), qq_sep, []).
873style(qq(close), qq_close, []).
874style(qq_type, qq_type, [text]).
875style(dict_tag, tag, [text]).
876style(dict_key, key, [text]).
877style(dict_sep, sep, []).
878style(func_dot, atom, [text(.)]).
879style(dict_return_op, atom, [text(:=)]).
880style(dict_function(F), dict_function, [text(F)]).
881style(empty_list, list_open-list_close, []).
882style(list, list_open-list_close, []).
883style(dcg(terminal), list_open-list_close, []).
884style(dcg(string), string_terminal, []).
885style(dcg(plain), brace_term_open-brace_term_close, []).
886style(brace_term, brace_term_open-brace_term_close, []).
887style(dict_content, dict_open-dict_close, []).
888style(expanded, expanded, [text]).
889style(comment_string, comment_string, []). 890style(comment(string), comment_string, []). 891style(ext_quant, ext_quant, []).
892style(unused_import, unused_import, [text]).
893style(undefined_import, undefined_import, [text]).
894 895style(html(_Element), html, []).
896style(entity(_Element), entity, []).
897style(html_attribute(_), html_attribute, []).
898style(sgml_attr_function,sgml_attr_function, []).
899style(html_call, html_call, [text]). 900style(html_raw, html_raw, [text]). 901style(http_location_for_id(_), http_location_for_id, []).
902style(http_no_location_for_id(_), http_no_location_for_id, []).
903 904style(method(send), xpce_method, [text]).
905style(method(get), xpce_method, [text]).
906style(class(built_in,_Name), xpce_class_built_in, [text]).
907style(class(library(File),_Name), xpce_class_lib, [text, file(File)]).
908style(class(user(File),_Name), xpce_class_user, [text, file(File)]).
909style(class(user,_Name), xpce_class_user, [text]).
910style(class(undefined,_Name), xpce_class_undef, [text]).
911
912style(table_mode(_Mode), table_mode, [text]).
913style(table_option(_Mode), table_option, [text]).
914
915
916type_error_msg(declaration(Context), Msg) =>
917 format(string(Msg), '~w declaration', [Context]).
918type_error_msg(Atomic, Msg), atomic(Atomic) =>
919 Msg = Atomic.
920type_error_msg(Term, Msg) =>
921 term_string(Term, Msg).
922
923neck_text(clause, (:-)) :- !.
924neck_text(grammar_rule, (-->)) :- !.
925neck_text(method(send), (:->)) :- !.
926neck_text(method(get), (:<-)) :- !.
927neck_text(directive, (:-)) :- !.
928neck_text(Text, Text). 929
930head_type(exported, head_exported).
931head_type(public(_), head_public).
932head_type(extern(_), head_extern).
933head_type(extern(_,_), head_extern).
934head_type(dynamic, head_dynamic).
935head_type(multifile, head_multifile).
936head_type(unreferenced, head_unreferenced).
937head_type(hook, head_hook).
938head_type(meta, head_meta).
939head_type(constraint(_), head_constraint).
940head_type(imported, head_imported).
941head_type(built_in, head_built_in).
942head_type(iso, head_iso).
943head_type(def_iso, head_def_iso).
944head_type(def_swi, head_def_swi).
945head_type(_, head).
946
947goal_type(built_in, goal_built_in, []).
948goal_type(imported(File), goal_imported, [file(File)]).
949goal_type(autoload(File), goal_autoload, [file(File)]).
950goal_type(global, goal_global, []).
951goal_type(undefined, goal_undefined, []).
952goal_type(thread_local(Line), goal_thread_local, [line(Line)]).
953goal_type(dynamic(Line), goal_dynamic, [line(Line)]).
954goal_type(multifile(Line), goal_multifile, [line(Line)]).
955goal_type(expanded, goal_expanded, []).
956goal_type(extern(_), goal_extern, []).
957goal_type(extern(_,_), goal_extern, []).
958goal_type(recursion, goal_recursion, []).
959goal_type(meta, goal_meta, []).
960goal_type(foreign(_), goal_foreign, []).
961goal_type(local(Line), goal_local, [line(Line)]).
962goal_type(constraint(Line), goal_constraint, [line(Line)]).
963goal_type(not_callable, goal_not_callable, []).
964goal_type(global(Type,_Loc), Class, []) :-
965 global_class(Type, Class).
966
967global_class(dynamic, goal_dynamic) :- !.
968global_class(multifile, goal_multifile) :- !.
969global_class(_, goal_global).
970
974
975goal_arity(Goal, Arity) :-
976 ( compound(Goal)
977 -> compound_name_arity(Goal, _, Arity)
978 ; Arity = 0
979 ).
980
981 984
985:- multifile
986 swish_config:config/2,
987 css/3. 988
997
998swish_config:config(cm_style, Styles) :-
999 findall(Name-Style, highlight_style(Name, Style), Pairs),
1000 keysort(Pairs, Sorted),
1001 remove_duplicate_styles(Sorted, Unique),
1002 dict_pairs(Styles, json, Unique).
1003swish_config:config(cm_hover_style, Styles) :-
1004 findall(Sel-Attrs, css_dict(hover, Sel, Attrs), Pairs),
1005 dict_pairs(Styles, json, Pairs).
1006
1007remove_duplicate_styles([], []).
1008remove_duplicate_styles([H|T0], [H|T]) :-
1009 H = K-_,
1010 remove_same(K, T0, T1),
1011 remove_duplicate_styles(T1, T).
1012
1013remove_same(K, [K-_|T0], T) :- !,
1014 remove_same(K, T0, T).
1015remove_same(_, Rest, Rest).
1016
1017highlight_style(StyleName, Style) :-
1018 style(Term, StyleName, _),
1019 atom(StyleName),
1020 ( prolog_colour:style(Term, Attrs0)
1021 -> maplist(css_style, Attrs0, Attrs),
1022 dict_create(Style, json, Attrs)
1023 ).
1024
1025css_style(bold(true), 'font-weight'(bold)) :- !.
1026css_style(underline(true), 'text-decoration'(underline)) :- !.
1027css_style(colour(Name), color(RGB)) :-
1028 x11_color(Name, R, G, B),
1029 format(atom(RGB), '#~|~`0t~16r~2+~`0t~16r~2+~`0t~16r~2+', [R,G,B]).
1030css_style(Style, Style).
1031
1035
1036x11_color(Name, R, G, B) :-
1037 ( x11_colors_done
1038 -> true
1039 ; with_mutex(swish_highlight, load_x11_colours)
1040 ),
1041 x11_color_cache(Name, R, G, B).
1042
1043:- dynamic
1044 x11_color_cache/4,
1045 x11_colors_done/0. 1046
1047load_x11_colours :-
1048 x11_colors_done, !.
1049load_x11_colours :-
1050 source_file(load_x11_colours, File),
1051 file_directory_name(File, Dir),
1052 directory_file_path(Dir, 'rgb.txt', RgbFile),
1053 setup_call_cleanup(
1054 open(RgbFile, read, In),
1055 ( lazy_list(lazy_read_lines(In, [as(string)]), List),
1056 maplist(assert_colour, List)
1057 ),
1058 close(In)),
1059 asserta(x11_colors_done).
1060
1061assert_colour(String) :-
1062 split_string(String, "\s\t\r", "\s\t\r", [RS,GS,BS|NameParts]),
1063 number_string(R, RS),
1064 number_string(G, GS),
1065 number_string(B, BS),
1066 atomic_list_concat(NameParts, '_', Name0),
1067 downcase_atom(Name0, Name),
1068 assertz(x11_color_cache(Name, R, G, B)).
1069
1070:- catch(initialization(load_x11_colours, prepare_state), _, true). 1071
1082
1083css_dict(Context, Selector, Style) :-
1084 css(Context, Selector, Attrs0),
1085 maplist(css_style, Attrs0, Attrs),
1086 dict_create(Style, json, Attrs).
1087
1088
1089 1092
1093:- multifile
1094 prolog:predicate_summary/2. 1095
1099
1100token_info(Request) :-
1101 memberchk(method(options), Request),
1102 !,
1103 cors_enable(Request,
1104 [ methods([get])
1105 ]),
1106 format('~n').
1107token_info(Request) :-
1108 cors_enable,
1109 http_parameters(Request, [], [form_data(Form)]),
1110 maplist(type_convert, Form, Values),
1111 dict_create(Token, token, Values),
1112 reply_html_page(plain,
1113 title('token info'),
1114 \token_info_or_none(Token)).
1115
1116type_convert(Name=Atom, Name=Number) :-
1117 atom_number(Atom, Number), !.
1118type_convert(NameValue, NameValue).
1119
1120
1121token_info_or_none(Token) -->
1122 token_info(Token), !.
1123token_info_or_none(_) -->
1124 html(span(class('token-noinfo'), 'No info available')).
1125
1132
1133:- multifile token_info//1. 1134
1135token_info(Token) -->
1136 { _{type:Type, text:Name, arity:Arity} :< Token,
1137 goal_type(_, Type, _), !,
1138 ignore(token_predicate_module(Token, Module)),
1139 text_arity_pi(Name, Arity, PI),
1140 predicate_info(Module:PI, Info)
1141 },
1142 pred_info(Info).
1143
1144pred_info([]) -->
1145 html(span(class('pred-nosummary'), 'No help available')).
1146pred_info([Info|_]) --> 1147 (pred_tags(Info) -> [];[]),
1148 (pred_summary(Info) -> [];[]).
1149
1150pred_tags(Info) -->
1151 { Info.get(iso) == true },
1152 html(span(class('pred-tag'), 'ISO')).
1153
1154pred_summary(Info) -->
1155 html(span(class('pred-summary'), Info.get(summary))).
1156
1160
1161token_predicate_module(Token, Module) :-
1162 source_file_property(Token.get(file), module(Module)), !.
1163
1164text_arity_pi('[', 2, consult/1) :- !.
1165text_arity_pi(']', 2, consult/1) :- !.
1166text_arity_pi(Name, Arity, Name/Arity).
1167
1168
1184
1185predicate_info(PI, Info) :-
1186 PI = Module:Name/Arity,
1187 findall(Dict,
1188 ( setof(Key-Value,
1189 predicate_info(PI, Key, Value),
1190 Pairs),
1191 dict_pairs(Dict, json,
1192 [ module - Module,
1193 name - Name,
1194 arity - Arity
1195 | Pairs
1196 ])
1197 ),
1198 Info).
1199
1210
1211 1212predicate_info(Module:Name/Arity, Key, Value) :-
1213 functor(Head, Name, Arity),
1214 predicate_property(system:Head, iso), !,
1215 ignore(Module = system),
1216 ( man_predicate_summary(Name/Arity, Summary),
1217 Key = summary,
1218 Value = Summary
1219 ; Key = iso,
1220 Value = true
1221 ).
1222predicate_info(PI, summary, Summary) :-
1223 PI = Module:Name/Arity,
1224
1225 ( man_predicate_summary(Name/Arity, Summary)
1226 -> true
1227 ; Arity >= 2,
1228 DCGArity is Arity - 2,
1229 man_predicate_summary(Name//DCGArity, Summary)
1230 -> true
1231 ; prolog:predicate_summary(PI, Summary)
1232 -> true
1233 ; Arity >= 2,
1234 DCGArity is Arity - 2,
1235 prolog:predicate_summary(Module:Name/DCGArity, Summary)
1236 ).
1237
1238:- if(current_predicate(man_object_property/2)). 1239man_predicate_summary(PI, Summary) :-
1240 man_object_property(PI, summary(Summary)).
1241:- else. 1242man_predicate_summary(_, _) :-
1243 fail.
1244:- endif.