34
35:- module(swish_highlight,
36 [ current_highlight_state/2
37 ]). 38:- use_module(library(debug)). 39:- use_module(library(settings)). 40:- use_module(library(http/http_dispatch)). 41:- use_module(library(http/html_write)). 42:- use_module(library(http/http_json)). 43:- use_module(library(http/http_path), []). 44:- use_module(library(http/http_parameters)). 45:- use_module(library(pairs)). 46:- use_module(library(apply)). 47:- use_module(library(error)). 48:- use_module(library(prolog_xref)). 49:- use_module(library(memfile)). 50:- use_module(library(prolog_colour)). 51:- use_module(library(lazy_lists)). 52:- if(exists_source(library(helpidx))). 53:- use_module(library(helpidx), [predicate/5]). 54:- endif. 55
56http:location(codemirror, swish(cm), []).
57
58:- http_handler(codemirror(.), http_404([]), [id(cm_highlight)]). 59:- http_handler(codemirror(change), codemirror_change, []). 60:- http_handler(codemirror(tokens), codemirror_tokens, []). 61:- http_handler(codemirror(leave), codemirror_leave, []). 62:- http_handler(codemirror(info), token_info, []). 63
64:- setting(swish:editor_max_idle_time, nonneg, 3600,
65 "Maximum time we keep a mirror editor around"). 66
76
77 80
98
99codemirror_change(Request) :-
100 call_cleanup(codemirror_change_(Request),
101 check_unlocked).
102
103codemirror_change_(Request) :-
104 http_read_json_dict(Request, Change, []),
105 debug(cm(change), 'Change ~p', [Change]),
106 atom_string(UUID, Change.uuid),
107 catch(shadow_editor(Change, TB),
108 cm(Reason), true),
109 ( var(Reason)
110 -> ( catch(apply_change(TB, Changed, Change.change),
111 cm(outofsync), fail)
112 -> mark_changed(TB, Changed),
113 release_editor(UUID),
114 reply_json_dict(true)
115 ; destroy_editor(UUID),
116 change_failed(UUID, outofsync)
117 )
118 ; change_failed(UUID, Reason)
119 ).
120
121change_failed(UUID, Reason) :-
122 reply_json_dict(json{ type:Reason,
123 object:UUID
124 },
125 [status(409)]).
126
127
136
137apply_change(_, _Changed, []) :- !.
138apply_change(TB, Changed, Change) :-
139 _{from:From} :< Change,
140 Line is From.line+1,
141 memory_file_line_position(TB, Line, From.ch, ChPos),
142 remove(Change.removed, TB, ChPos, Changed),
143 insert(Change.text, TB, ChPos, _End, Changed),
144 ( Next = Change.get(next)
145 -> apply_change(TB, Changed, Next)
146 ; true
147 ).
148
149remove([], _, _, _) :- !.
150remove([H|T], TB, ChPos, Changed) :-
151 string_length(H, Len),
152 ( T == []
153 -> DLen is Len
154 ; DLen is Len+1
155 ),
156 ( DLen == 0
157 -> true
158 ; Changed = true,
159 memory_file_substring(TB, ChPos, Len, _, Text),
160 ( Text == H
161 -> true
162 ; throw(cm(outofsync))
163 ),
164 delete_memory_file(TB, ChPos, DLen)
165 ),
166 remove(T, TB, ChPos, Changed).
167
168insert([], _, ChPos, ChPos, _) :- !.
169insert([H|T], TB, ChPos0, ChPos, Changed) :-
170 ( H == ""
171 -> Len = 0
172 ; Changed = true,
173 string_length(H, Len),
174 debug(cm(change_text), 'Insert ~q at ~d', [H, ChPos0]),
175 insert_memory_file(TB, ChPos0, H)
176 ),
177 ChPos1 is ChPos0+Len,
178 ( T == []
179 -> ChPos2 = ChPos1
180 ; debug(cm(change_text), 'Adding newline at ~d', [ChPos1]),
181 Changed = true,
182 insert_memory_file(TB, ChPos1, '\n'),
183 ChPos2 is ChPos1+1
184 ),
185 insert(T, TB, ChPos2, ChPos, Changed).
186
187:- dynamic
188 current_editor/5, 189 editor_last_access/2, 190 xref_upto_data/1. 191
197
198create_editor(UUID, Editor, Change) :-
199 must_be(atom, UUID),
200 uuid_like(UUID),
201 new_memory_file(Editor),
202 ( RoleString = Change.get(role)
203 -> atom_string(Role, RoleString)
204 ; Role = source
205 ),
206 get_time(Now),
207 mutex_create(Lock),
208 with_mutex(swish_create_editor,
209 register_editor(UUID, Editor, Role, Lock, Now)), !.
210create_editor(UUID, Editor, _Change) :-
211 fetch_editor(UUID, Editor).
212
214register_editor(UUID, Editor, Role, Lock, Now) :-
215 \+ current_editor(UUID, _, _, _, _),
216 mutex_lock(Lock),
217 asserta(current_editor(UUID, Editor, Role, Lock, Now)).
218
222
223current_highlight_state(UUID,
224 highlight{data:Editor,
225 role:Role,
226 created:Created,
227 lock:Lock,
228 access:Access
229 }) :-
230 current_editor(UUID, Editor, Role, Lock, Created),
231 ( editor_last_access(Editor, Access)
232 -> true
233 ; Access = Created
234 ).
235
236
242
243uuid_like(UUID) :-
244 split_string(UUID, "-", "", Parts),
245 maplist(string_length, Parts, [8,4,4,4,12]),
246 \+ current_editor(UUID, _, _, _, _).
247
254
255destroy_editor(UUID) :-
256 must_be(atom, UUID),
257 current_editor(UUID, Editor, _, Lock, _), !,
258 mutex_unlock(Lock),
259 retractall(xref_upto_data(UUID)),
260 retractall(editor_last_access(UUID, _)),
261 ( xref_source_id(UUID, SourceID)
262 -> xref_clean(SourceID),
263 destroy_state_module(UUID)
264 ; true
265 ),
266 267 retractall(current_editor(UUID, Editor, _, _, _)),
268 free_memory_file(Editor).
269destroy_editor(_).
270
283
284:- dynamic
285 gced_editors/1. 286
287editor_max_idle_time(Time) :-
288 setting(swish:editor_max_idle_time, Time).
289
290gc_editors :-
291 get_time(Now),
292 ( gced_editors(Then),
293 editor_max_idle_time(MaxIdle),
294 Now - Then < MaxIdle/3
295 -> true
296 ; retractall(gced_editors(_)),
297 asserta(gced_editors(Now)),
298 fail
299 ).
300gc_editors :-
301 editor_max_idle_time(MaxIdle),
302 forall(garbage_editor(UUID, MaxIdle),
303 destroy_garbage_editor(UUID)).
304
305garbage_editor(UUID, TimeOut) :-
306 get_time(Now),
307 current_editor(UUID, _TB, _Role, _Lock, Created),
308 Now - Created > TimeOut,
309 ( editor_last_access(UUID, Access)
310 -> Now - Access > TimeOut
311 ; true
312 ).
313
314destroy_garbage_editor(UUID) :-
315 fetch_editor(UUID, _TB), !,
316 destroy_editor(UUID).
317destroy_garbage_editor(_).
318
324
325fetch_editor(UUID, TB) :-
326 current_editor(UUID, TB, Role, Lock, _),
327 catch(mutex_lock(Lock), error(existence_error(mutex,_),_), fail),
328 debug(cm(lock), 'Locked ~p', [UUID]),
329 ( current_editor(UUID, TB, Role, Lock, _)
330 -> update_access(UUID)
331 ; mutex_unlock(Lock)
332 ).
333
334release_editor(UUID) :-
335 current_editor(UUID, _TB, _Role, Lock, _),
336 debug(cm(lock), 'Unlocked ~p', [UUID]),
337 mutex_unlock(Lock).
338
339check_unlocked :-
340 check_unlocked(unknown).
341
346
347check_unlocked(Reason) :-
348 thread_self(Me),
349 current_editor(_UUID, _TB, _Role, Lock, _),
350 mutex_property(Lock, status(locked(Me, _Count))), !,
351 unlock(Me, Lock),
352 print_message(error, locked(Reason, Me)),
353 assertion(fail).
354check_unlocked(_).
355
356unlock(Me, Lock) :-
357 mutex_property(Lock, status(locked(Me, _Count))), !,
358 mutex_unlock(Lock),
359 unlock(Me, Lock).
360unlock(_, _).
361
366
367update_access(UUID) :-
368 get_time(Now),
369 ( editor_last_access(UUID, Last),
370 Now-Last < 60
371 -> true
372 ; retractall(editor_last_access(UUID, _)),
373 asserta(editor_last_access(UUID, Now))
374 ).
375
376:- multifile
377 prolog:xref_source_identifier/2,
378 prolog:xref_open_source/2,
379 prolog:xref_close_source/2. 380
381prolog:xref_source_identifier(UUID, UUID) :-
382 current_editor(UUID, _, _, _, _).
383
390
391:- if(current_predicate(prolog_source:close_source/3)). 392prolog:xref_open_source(UUID, Stream) :-
393 fetch_editor(UUID, TB),
394 open_memory_file(TB, read, Stream).
395
396prolog:xref_close_source(UUID, Stream) :-
397 release_editor(UUID),
398 close(Stream).
399:- else. 400prolog:xref_open_source(UUID, Stream) :-
401 fetch_editor(UUID, TB),
402 open_memory_file(TB, read, Stream),
403 release_editor(UUID).
404:- endif. 405
411
412codemirror_leave(Request) :-
413 call_cleanup(codemirror_leave_(Request),
414 check_unlocked).
415
416codemirror_leave_(Request) :-
417 http_read_json_dict(Request, Data, []),
418 ( atom_string(UUID, Data.get(uuid))
419 -> debug(cm(leave), 'Leaving editor ~p', [UUID]),
420 ( fetch_editor(UUID, _TB)
421 -> destroy_editor(UUID)
422 ; debug(cm(leave), 'No editor for ~p', [UUID])
423 )
424 ; debug(cm(leave), 'No editor?? (data=~p)', [Data])
425 ),
426 reply_json_dict(true).
427
431
432mark_changed(MemFile, Changed) :-
433 ( Changed == true,
434 current_editor(UUID, MemFile, _Role, _, _)
435 -> retractall(xref_upto_data(UUID))
436 ; true
437 ).
438
440
441xref(UUID) :-
442 xref_upto_data(UUID), !.
443xref(UUID) :-
444 setup_call_cleanup(
445 fetch_editor(UUID, _TB),
446 ( xref_source_id(UUID, SourceId),
447 xref_state_module(UUID, Module),
448 xref_source(SourceId,
449 [ silent(true),
450 module(Module)
451 ]),
452 asserta(xref_upto_data(UUID))
453 ),
454 release_editor(UUID)).
455
460
461xref_source_id(UUID, UUID).
462
467
468xref_state_module(UUID, UUID) :-
469 ( module_property(UUID, class(temporary))
470 -> true
471 ; set_module(UUID:class(temporary)),
472 add_import_module(UUID, swish, start),
473 maplist(copy_flag(UUID, swish), [var_prefix])
474 ).
475
476copy_flag(Module, Application, Flag) :-
477 current_prolog_flag(Application:Flag, Value), !,
478 set_prolog_flag(Module:Flag, Value).
479copy_flag(_, _, _).
480
481destroy_state_module(UUID) :-
482 module_property(UUID, class(temporary)), !,
483 '$destroy_module'(UUID).
484destroy_state_module(_).
485
486
487 490
495
496codemirror_tokens(Request) :-
497 setup_call_catcher_cleanup(
498 true,
499 codemirror_tokens_(Request),
500 Reason,
501 check_unlocked(Reason)).
502
503codemirror_tokens_(Request) :-
504 http_read_json_dict(Request, Data, []),
505 atom_string(UUID, Data.get(uuid)),
506 debug(cm(tokens), 'Asking for tokens: ~p', [Data]),
507 ( catch(shadow_editor(Data, TB), cm(Reason), true)
508 -> ( var(Reason)
509 -> call_cleanup(enriched_tokens(TB, Data, Tokens),
510 release_editor(UUID)),
511 reply_json_dict(json{tokens:Tokens}, [width(0)])
512 ; check_unlocked(Reason),
513 change_failed(UUID, Reason)
514 )
515 ; reply_json_dict(json{tokens:[[]]})
516 ),
517 gc_editors.
518
519
520enriched_tokens(TB, _Data, Tokens) :- 521 current_editor(UUID, TB, source, _Lock, _), !,
522 xref(UUID),
523 server_tokens(TB, Tokens).
524enriched_tokens(TB, Data, Tokens) :- 525 json_source_id(Data.get(sourceID), SourceID), !,
526 memory_file_to_string(TB, Query),
527 with_mutex(swish_highlight_query,
528 prolog_colourise_query(Query, SourceID, colour_item(TB))),
529 collect_tokens(TB, Tokens).
530enriched_tokens(TB, _Data, Tokens) :-
531 memory_file_to_string(TB, Query),
532 prolog_colourise_query(Query, module(swish), colour_item(TB)),
533 collect_tokens(TB, Tokens).
534
540
541:- if(current_predicate(prolog_colour:to_list/2)). 542json_source_id(StringList, SourceIDList) :-
543 is_list(StringList),
544 StringList \== [], !,
545 maplist(string_source_id, StringList, SourceIDList).
546:- else. 547json_source_id([String|_], SourceID) :-
548 maplist(string_source_id, String, SourceID).
549:- endif. 550json_source_id(String, SourceID) :-
551 string(String),
552 string_source_id(String, SourceID).
553
554string_source_id(String, SourceID) :-
555 atom_string(SourceID, String),
556 ( fetch_editor(SourceID, _TB)
557 -> release_editor(SourceID)
558 ; true
559 ).
560
561
578
579shadow_editor(Data, TB) :-
580 atom_string(UUID, Data.get(uuid)),
581 setup_call_catcher_cleanup(
582 fetch_editor(UUID, TB),
583 once(update_editor(Data, UUID, TB)),
584 Catcher,
585 cleanup_update(Catcher, UUID)), !.
586shadow_editor(Data, TB) :-
587 Text = Data.get(text), !,
588 atom_string(UUID, Data.uuid),
589 create_editor(UUID, TB, Data),
590 debug(cm(change), 'Create editor for ~p', [UUID]),
591 debug(cm(change_text), 'Initialising editor to ~q', [Text]),
592 insert_memory_file(TB, 0, Text).
593shadow_editor(Data, TB) :-
594 _{role:_} :< Data, !,
595 atom_string(UUID, Data.uuid),
596 create_editor(UUID, TB, Data).
597shadow_editor(_Data, _TB) :-
598 throw(cm(existence_error)).
599
600update_editor(Data, _UUID, TB) :-
601 Text = Data.get(text), !,
602 size_memory_file(TB, Size),
603 delete_memory_file(TB, 0, Size),
604 insert_memory_file(TB, 0, Text),
605 mark_changed(TB, true).
606update_editor(Data, UUID, TB) :-
607 Changes = Data.get(changes), !,
608 ( debug(cm(change), 'Patch editor for ~p', [UUID]),
609 maplist(apply_change(TB, Changed), Changes)
610 -> true
611 ; throw(cm(out_of_sync))
612 ),
613 mark_changed(TB, Changed).
614
615cleanup_update(exit, _) :- !.
616cleanup_update(_, UUID) :-
617 release_editor(UUID).
618
619:- thread_local
620 token/3. 621
631
632:- public
633 show_mirror/1,
634 server_tokens/1. 635
636show_mirror(Role) :-
637 current_editor(_UUID, TB, Role, _Lock, _), !,
638 memory_file_to_string(TB, String),
639 write(user_error, String).
640
641server_tokens(Role) :-
642 current_editor(_UUID, TB, Role, _Lock, _), !,
643 enriched_tokens(TB, _{}, Tokens),
644 print_term(Tokens, [output(user_error)]).
645
650
651server_tokens(TB, GroupedTokens) :-
652 current_editor(UUID, TB, _Role, _Lock, _),
653 setup_call_cleanup(
654 open_memory_file(TB, read, Stream),
655 ( set_stream_file(TB, Stream),
656 prolog_colourise_stream(Stream, UUID, colour_item(TB))
657 ),
658 close(Stream)),
659 collect_tokens(TB, GroupedTokens).
660
661collect_tokens(TB, GroupedTokens) :-
662 findall(Start-Token, json_token(TB, Start, Token), Pairs),
663 keysort(Pairs, Sorted),
664 pairs_values(Sorted, Tokens),
665 group_by_term(Tokens, GroupedTokens).
666
667set_stream_file(_,_). 668
675
676group_by_term([], []) :- !.
677group_by_term(Flat, [Term|Grouped]) :-
678 take_term(Flat, Term, Rest),
679 group_by_term(Rest, Grouped).
680
681take_term([], [], []).
682take_term([H|T0], [H|T], R) :-
683 ( ends_term(H.get(type))
684 -> T = [],
685 R = T0
686 ; take_term(T0, T, R)
687 ).
688
689ends_term(fullstop).
690ends_term(syntax_error).
691
700
701json_token(TB, Start, Token) :-
702 retract(token(Style, Start0, Len)),
703 debug(color, 'Trapped ~q.', [token(Style, Start0, Len)]),
704 ( atomic_special(Style, Start0, Len, TB, Type, Attrs)
705 -> Start = Start0
706 ; style(Style, Type0, Attrs0)
707 -> ( Type0 = StartType-EndType
708 -> ( Start = Start0,
709 Type = StartType
710 ; Start is Start0+Len-1,
711 Type = EndType
712 )
713 ; Type = Type0,
714 Start = Start0
715 ),
716 json_attributes(Attrs0, Attrs, TB, Start0, Len)
717 ),
718 dict_create(Token, json, [type(Type)|Attrs]).
719
720atomic_special(atom, Start, Len, TB, Type, Attrs) :-
721 memory_file_substring(TB, Start, 1, _, FirstChar),
722 ( FirstChar == "'"
723 -> Type = qatom,
724 Attrs = []
725 ; char_type(FirstChar, upper)
726 -> Type = uatom, 727 Attrs = []
728 ; Type = atom,
729 ( Len =< 5 730 -> memory_file_substring(TB, Start, Len, _, Text),
731 Attrs = [text(Text)]
732 ; Attrs = []
733 )
734 ).
735
736json_attributes([], [], _, _, _).
737json_attributes([H0|T0], Attrs, TB, Start, Len) :-
738 json_attribute(H0, Attrs, T, TB, Start, Len), !,
739 json_attributes(T0, T, TB, Start, Len).
740json_attributes([_|T0], T, TB, Start, Len) :-
741 json_attributes(T0, T, TB, Start, Len).
742
743json_attribute(text, [text(Text)|T], T, TB, Start, Len) :- !,
744 memory_file_substring(TB, Start, Len, _, Text).
745json_attribute(line(File:Line), [line(Line),file(File)|T], T, _, _, _) :- !.
746json_attribute(Term, [Term|T], T, _, _, _).
747
748colour_item(_TB, Style, Start, Len) :-
749 ( style(Style)
750 -> assertz(token(Style, Start, Len))
751 ; debug(color, 'Ignored ~q.', [token(Style, Start, Len)])
752 ).
753
780
781:- multifile
782 style/3. 783
784style(Style) :-
785 style(Style, _, _).
786
787style(neck(Neck), neck, [ text(Text) ]) :-
788 neck_text(Neck, Text).
789style(head(Class, Head), Type, [ text, arity(Arity) ]) :-
790 goal_arity(Head, Arity),
791 head_type(Class, Type).
792style(goal(Class, Goal), Type, [ text, arity(Arity) | More ]) :-
793 goal_arity(Goal, Arity),
794 goal_type(Class, Type, More).
795style(file_no_depend(Path), file_no_depends, [text, path(Path)]).
796style(file(Path), file, [text, path(Path)]).
797style(nofile, nofile, [text]).
798style(option_name, option_name, [text]).
799style(no_option_name, no_option_name, [text]).
800style(flag_name(_Flag), flag_name, [text]).
801style(no_flag_name(_Flag), no_flag_name, [text]).
802style(fullstop, fullstop, []).
803style(var, var, [text]).
804style(singleton, singleton, [text]).
805style(string, string, []).
806style(codes, codes, []).
807style(chars, chars, []).
808style(atom, atom, []).
809style(meta(_Spec), meta, []).
810style(op_type(_Type), op_type, [text]).
811style(functor, functor, [text]).
812style(control, control, [text]).
813style(delimiter, delimiter, [text]).
814style(identifier, identifier, [text]).
815style(module(_Module), module, [text]).
816style(error, error, [text]).
817style(type_error(Expect), error, [text,expected(Expect)]).
818style(syntax_error(_Msg,_Pos), syntax_error, []).
819style(instantiation_error, instantiation_error, [text]).
820style(predicate_indicator, atom, [text]).
821style(predicate_indicator, atom, [text]).
822style(arity, int, []).
823style(int, int, []).
824style(float, float, []).
825style(qq(open), qq_open, []).
826style(qq(sep), qq_sep, []).
827style(qq(close), qq_close, []).
828style(qq_type, qq_type, [text]).
829style(dict_tag, tag, [text]).
830style(dict_key, key, [text]).
831style(dict_sep, sep, []).
832style(func_dot, atom, [text(.)]).
833style(dict_return_op, atom, [text(:=)]).
834style(dict_function(F), dict_function, [text(F)]).
835style(empty_list, list_open-list_close, []).
836style(list, list_open-list_close, []).
837style(dcg(terminal), list_open-list_close, []).
838style(dcg(string), string_terminal, []).
839style(dcg(plain), brace_term_open-brace_term_close, []).
840style(brace_term, brace_term_open-brace_term_close, []).
841style(dict_content, dict_open-dict_close, []).
842style(expanded, expanded, [text]).
843style(comment_string, comment_string, []). 844style(comment(string), comment_string, []). 845style(ext_quant, ext_quant, []).
846style(unused_import, unused_import, [text]).
847style(undefined_import, undefined_import, [text]).
848 849style(html(_Element), html, []).
850style(entity(_Element), entity, []).
851style(html_attribute(_), html_attribute, []).
852style(sgml_attr_function,sgml_attr_function, []).
853style(http_location_for_id(_), http_location_for_id, []).
854style(http_no_location_for_id(_), http_no_location_for_id, []).
855 856style(method(send), xpce_method, [text]).
857style(method(get), xpce_method, [text]).
858style(class(built_in,_Name), xpce_class_built_in, [text]).
859style(class(library(File),_Name), xpce_class_lib, [text, file(File)]).
860style(class(user(File),_Name), xpce_class_user, [text, file(File)]).
861style(class(user,_Name), xpce_class_user, [text]).
862style(class(undefined,_Name), xpce_class_undef, [text]).
863
864neck_text(clause, (:-)).
865neck_text(grammar_rule, (-->)).
866neck_text(method(send), (:->)).
867neck_text(method(get), (:<-)).
868neck_text(directive, (:-)).
869
870head_type(exported, head_exported).
871head_type(public(_), head_public).
872head_type(extern(_), head_extern).
873head_type(dynamic, head_dynamic).
874head_type(multifile, head_multifile).
875head_type(unreferenced, head_unreferenced).
876head_type(hook, head_hook).
877head_type(meta, head_meta).
878head_type(constraint(_), head_constraint).
879head_type(imported, head_imported).
880head_type(built_in, head_built_in).
881head_type(iso, head_iso).
882head_type(def_iso, head_def_iso).
883head_type(def_swi, head_def_swi).
884head_type(_, head).
885
886goal_type(built_in, goal_built_in, []).
887goal_type(imported(File), goal_imported, [file(File)]).
888goal_type(autoload(File), goal_autoload, [file(File)]).
889goal_type(global, goal_global, []).
890goal_type(undefined, goal_undefined, []).
891goal_type(thread_local(Line), goal_thread_local, [line(Line)]).
892goal_type(dynamic(Line), goal_dynamic, [line(Line)]).
893goal_type(multifile(Line), goal_multifile, [line(Line)]).
894goal_type(expanded, goal_expanded, []).
895goal_type(extern(_), goal_extern, []).
896goal_type(recursion, goal_recursion, []).
897goal_type(meta, goal_meta, []).
898goal_type(foreign(_), goal_foreign, []).
899goal_type(local(Line), goal_local, [line(Line)]).
900goal_type(constraint(Line), goal_constraint, [line(Line)]).
901goal_type(not_callable, goal_not_callable, []).
902
906
907goal_arity(Goal, Arity) :-
908 ( compound(Goal)
909 -> compound_name_arity(Goal, _, Arity)
910 ; Arity = 0
911 ).
912
913 916
917:- multifile
918 swish_config:config/2,
919 css/3. 920
929
930swish_config:config(cm_style, Styles) :-
931 findall(Name-Style, highlight_style(Name, Style), Pairs),
932 keysort(Pairs, Sorted),
933 remove_duplicate_styles(Sorted, Unique),
934 dict_pairs(Styles, json, Unique).
935swish_config:config(cm_hover_style, Styles) :-
936 findall(Sel-Attrs, css_dict(hover, Sel, Attrs), Pairs),
937 dict_pairs(Styles, json, Pairs).
938
939remove_duplicate_styles([], []).
940remove_duplicate_styles([H|T0], [H|T]) :-
941 H = K-_,
942 remove_same(K, T0, T1),
943 remove_duplicate_styles(T1, T).
944
945remove_same(K, [K-_|T0], T) :- !,
946 remove_same(K, T0, T).
947remove_same(_, Rest, Rest).
948
949highlight_style(StyleName, Style) :-
950 style(Term, StyleName, _),
951 atom(StyleName),
952 ( prolog_colour:style(Term, Attrs0)
953 -> maplist(css_style, Attrs0, Attrs),
954 dict_create(Style, json, Attrs)
955 ).
956
957css_style(bold(true), 'font-weight'(bold)) :- !.
958css_style(underline(true), 'text-decoration'(underline)) :- !.
959css_style(colour(Name), color(RGB)) :-
960 x11_color(Name, R, G, B),
961 format(atom(RGB), '#~|~`0t~16r~2+~`0t~16r~2+~`0t~16r~2+', [R,G,B]).
962css_style(Style, Style).
963
967
968x11_color(Name, R, G, B) :-
969 ( x11_color_cache(_,_,_,_)
970 -> true
971 ; load_x11_colours
972 ),
973 x11_color_cache(Name, R, G, B).
974
975:- dynamic
976 x11_color_cache/4. 977
978load_x11_colours :-
979 source_file(load_x11_colours, File),
980 file_directory_name(File, Dir),
981 directory_file_path(Dir, 'rgb.txt', RgbFile),
982 setup_call_cleanup(
983 open(RgbFile, read, In),
984 ( lazy_list(lazy_read_lines(In, [as(string)]), List),
985 maplist(assert_colour, List)
986 ),
987 close(In)).
988
989assert_colour(String) :-
990 split_string(String, "\s\t\r", "\s\t\r", [RS,GS,BS|NameParts]),
991 number_string(R, RS),
992 number_string(G, GS),
993 number_string(B, BS),
994 atomic_list_concat(NameParts, '_', Name0),
995 downcase_atom(Name0, Name),
996 assertz(x11_color_cache(Name, R, G, B)).
997
1008
1009css_dict(Context, Selector, Style) :-
1010 css(Context, Selector, Attrs0),
1011 maplist(css_style, Attrs0, Attrs),
1012 dict_create(Style, json, Attrs).
1013
1014
1015 1018
1019:- multifile
1020 prolog:predicate_summary/2. 1021
1025
1026token_info(Request) :-
1027 http_parameters(Request, [], [form_data(Form)]),
1028 maplist(type_convert, Form, Values),
1029 dict_create(Token, token, Values),
1030 reply_html_page(plain,
1031 title('token info'),
1032 \token_info_or_none(Token)).
1033
1034type_convert(Name=Atom, Name=Number) :-
1035 atom_number(Atom, Number), !.
1036type_convert(NameValue, NameValue).
1037
1038
1039token_info_or_none(Token) -->
1040 token_info(Token), !.
1041token_info_or_none(_) -->
1042 html(span(class('token-noinfo'), 'No info available')).
1043
1050
1051:- multifile token_info//1. 1052
1053token_info(Token) -->
1054 { _{type:Type, text:Name, arity:Arity} :< Token,
1055 goal_type(_, Type, _), !,
1056 ignore(token_predicate_module(Token, Module)),
1057 text_arity_pi(Name, Arity, PI),
1058 predicate_info(Module:PI, Info)
1059 },
1060 pred_info(Info).
1061
1062pred_info([]) -->
1063 html(span(class('pred-nosummary'), 'No help available')).
1064pred_info([Info|_]) --> 1065 (pred_tags(Info) -> [];[]),
1066 (pred_summary(Info) -> [];[]).
1067
1068pred_tags(Info) -->
1069 { Info.get(iso) == true },
1070 html(span(class('pred-tag'), 'ISO')).
1071
1072pred_summary(Info) -->
1073 html(span(class('pred-summary'), Info.get(summary))).
1074
1078
1079token_predicate_module(Token, Module) :-
1080 source_file_property(Token.get(file), module(Module)), !.
1081
1082text_arity_pi('[', 2, consult/1) :- !.
1083text_arity_pi(']', 2, consult/1) :- !.
1084text_arity_pi(Name, Arity, Name/Arity).
1085
1086
1102
1103predicate_info(PI, Info) :-
1104 PI = Module:Name/Arity,
1105 findall(Dict,
1106 ( setof(Key-Value,
1107 predicate_info(PI, Key, Value),
1108 Pairs),
1109 dict_pairs(Dict, json,
1110 [ module - Module,
1111 name - Name,
1112 arity - Arity
1113 | Pairs
1114 ])
1115 ),
1116 Info).
1117
1128
1129 1130predicate_info(Module:Name/Arity, Key, Value) :-
1131 functor(Head, Name, Arity),
1132 predicate_property(system:Head, iso), !,
1133 ignore(Module = system),
1134 ( catch(once(predicate(Name, Arity, Summary, _, _)), _, fail),
1135 Key = summary,
1136 Value = Summary
1137 ; Key = iso,
1138 Value = true
1139 ).
1140predicate_info(_Module:Name/Arity, summary, Summary) :-
1141 catch(once(predicate(Name, Arity, Summary, _, _)), _, fail), !.
1142predicate_info(PI, summary, Summary) :- 1143 once(prolog:predicate_summary(PI, Summary))