swish/commit

New Prolog files from upstream

authorJan Wielemaker
Wed Oct 15 14:27:59 2014 +0200
committerJan Wielemaker
Wed Oct 15 14:27:59 2014 +0200
commit27da6118daab1330f249cdb9593a5aa10b15724d
treeccfe02a939d6282c3fb086a0249d7a675d62b7ef
parent4110f9f6381e6ec5db2a186e2d8ffc86daac6e87
Diff style: patch stat
diff --git a/lib/swish/help.pl b/lib/swish/help.pl
index 1a9888f..3b7b904 100644
--- a/lib/swish/help.pl
+++ b/lib/swish/help.pl
@@ -33,9 +33,13 @@
 
 /** <module> SWISH help system
 
+This module serves help information for SWISH.
+
 @tbd	Server SWI-Prolog Markdown files.
 */
 
-:- http_handler(swish(help), serve_files_in_directory(swish_help), [id(help),prefix]).
+:- http_handler(swish(help), serve_files_in_directory(swish_help),
+		[id(help),prefix]).
 
 user:file_search_path(swish_help, swish(web/help)).
+
diff --git a/lib/swish/highlight.pl b/lib/swish/highlight.pl
new file mode 100644
index 0000000..806eeb0
--- /dev/null
+++ b/lib/swish/highlight.pl
@@ -0,0 +1,851 @@
+/*  Part of SWI-Prolog
+
+    Author:        Jan Wielemaker
+    E-mail:        J.Wielemaker@cs.vu.nl
+    WWW:           http://www.swi-prolog.org
+    Copyright (C): 2014, VU University Amsterdam
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License
+    as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+    As a special exception, if you link this library with other files,
+    compiled with a Free Software compiler, to produce an executable, this
+    library does not by itself cause the resulting executable to be covered
+    by the GNU General Public License. This exception does not however
+    invalidate any other reasons why the executable file might be covered by
+    the GNU General Public License.
+*/
+
+:- module(swish_highlight,
+	  [
+	  ]).
+:- use_module(library(pce)).
+:- use_module(library(debug)).
+:- use_module(library(http/http_dispatch)).
+:- use_module(library(http/html_write)).
+:- use_module(library(http/http_json)).
+:- use_module(library(http/http_path), []).
+:- use_module(library(http/http_parameters)).
+:- use_module(library(pairs)).
+:- use_module(library(apply)).
+:- use_module(library(prolog_colour)).
+:- if(exists_source(library(helpidx))).
+:- use_module(library(helpidx), [predicate/5]).
+:- endif.
+
+http:location(codemirror, swish(cm), []).
+
+:- http_handler(codemirror(.),      http_404([]),      [id(cm_highlight)]).
+:- http_handler(codemirror(change), codemirror_change, []).
+:- http_handler(codemirror(tokens), codemirror_tokens, []).
+:- http_handler(codemirror(leave),  codemirror_leave,  []).
+:- http_handler(codemirror(info),   token_info,        []).
+
+/** <module> Highlight token server
+
+This module provides the Prolog part of server-assisted highlighting for
+SWISH. It is implemented by managing a  shadow copy of the client editor
+on the server. On request,  the  server   computes  a  list of _semantic
+tokens_.
+
+@tbd	Use websockets
+*/
+
+		 /*******************************
+		 *	  SHADOW EDITOR		*
+		 *******************************/
+
+%%	codemirror_change(+Request)
+%
+%	Handle changes to the codemirror instances. These are sent to us
+%	using  a  POST  request.  The  request   a  POSTed  JSON  object
+%	containing:
+%
+%	  - uuid: string holding the editor's UUID
+%	  - change: the change object, which holds:
+%	    - from: Start position as {line:Line, ch:Ch}
+%	    - to: End position
+%	    - removed: list(atom) of removed text
+%	    - text: list(atom) of inserted text
+%	    - origin: what caused this change event
+%	    - next: optional next change event.
+
+codemirror_change(Request) :-
+	http_read_json_dict(Request, Change, []),
+	debug(cm(change), 'Change ~p', [Change]),
+	shadow_editor(Change, TB),
+	apply_change(TB, Change.change),
+	reply_json_dict(true).
+
+
+apply_change(_, []) :- !.
+apply_change(TB, Change) :-
+	_{from:From} :< Change,
+	get(TB, scan, 0, line, From.line, start, SOL),
+	ChPos is SOL+From.ch,
+	remove(Change.removed, TB, ChPos),
+	insert(Change.text, TB, ChPos, End),
+	send(TB, caret, End),
+	(   Next = Change.get(next)
+	->  apply_change(TB, Next)
+	;   true
+	).
+
+remove([], _, _) :- !.
+remove([H|T], TB, ChPos) :-
+	atom_length(H, Len),
+	(   T == []
+	->  DLen is Len
+	;   DLen is Len+1
+	),
+	(   DLen == 0
+	->  true
+	;   send(TB, delete, ChPos, DLen)
+	),
+	remove(T, TB, ChPos).
+
+insert([], _, ChPos, ChPos) :- !.
+insert([H|T], TB, ChPos0, ChPos) :-
+	atom_length(H, Len),
+	(   T == []
+	->  Fmt = '%s',
+	    ChPos1 = ChPos0+Len
+	;   Fmt = '%s\n',
+	    ChPos1 is ChPos0+Len+1
+	),
+	send(TB, insert, ChPos0, string(Fmt, H), 1),
+	insert(T, TB, ChPos1, ChPos).
+
+:- dynamic
+	current_editor/2.
+
+create_editor(UUID, Editor, Change) :-
+	must_be(atom, UUID),
+	uuid_like(UUID),
+	(   Role = Change.get(role)
+	->  new(Editor, source_buffer(UUID, Role))
+	;   new(Editor, source_buffer(UUID))
+	),
+	(   debugging(text_buffer)
+	->  send(Editor, open)
+	;   true
+	),
+	asserta(current_editor(UUID, Editor)).
+
+%%	uuid_like(+UUID) is semidet.
+%
+%	Do some sanity checking on  the  UUID   because  we  use it as a
+%	temporary module name and thus we must be quite sure it will not
+%	conflict with anything.
+
+uuid_like(UUID) :-
+	split_string(UUID, "-", "", Parts),
+	maplist(string_length, Parts, [8,4,4,4,12]),
+	\+ current_editor(UUID, _).
+
+destroy_editor(UUID, Editor) :-
+	must_be(atom, UUID),
+	(   xref_source_id(Editor, SourceID)
+	->  xref_clean(SourceID),
+	    '$destroy_module'(UUID)	% temp xref module
+	;   true
+	),
+	retractall(current_editor(UUID, Editor)),
+	free(Editor).
+
+
+:- multifile
+	prolog:xref_source_identifier/2,
+	prolog:xref_open_source/2.
+
+prolog:xref_source_identifier(UUID, UUID) :-
+	current_editor(UUID, _).
+
+prolog:xref_open_source(UUID, Stream) :-
+	current_editor(UUID, TB), !,
+	pce_open(TB, read, Stream).
+
+
+%%	codemirror_leave(+Request)
+%
+%	POST  handler  that  deals   with    destruction   of  the  XPCE
+%	source_buffer  associated  with  an  editor,   as  well  as  the
+%	associated cross-reference information.
+
+codemirror_leave(Request) :-
+	http_read_json_dict(Request, Data, []),
+	debug(cm(leave), 'Leaving editor ~p', [Data]),
+	(   atom_string(UUID, Data.get(uuid))
+	->  forall(current_editor(UUID, TB),
+		   destroy_editor(UUID, TB))
+	;   true
+	),
+	reply_json_dict(true).
+
+
+		 /*******************************
+		 *	CLASS SOURCE BUFFER	*
+		 *******************************/
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+This XPCE class reuses XPCE's editor   infrastructructure  to maintain a
+mirror of the user's editor. This is   not  ideal because XPCE's objects
+are much more heavy weight that what is  needed for this purpos and XPCE
+is  not  multi-threaded.  Eventually,  we'll  make   a  snappy  small  C
+datastructure to deal with this. An alternative   might be to add insert
+and delete behaviour to Prolog's memory files.
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+:- pce_begin_class(source_buffer, text_buffer,
+		   "Server side buffer for web editors").
+
+variable(uuid,		  name,		  get,	"Associated source id").
+variable(role,		  {source,query}, both,	"Associated source id").
+variable(file,		  name*,	  get,	"Associated file").
+variable(xref_generation, int*,		  get,	"Generation of last xref").
+
+% do not maintain undo information.
+class_variable(undo_buffer_size, int, 0).
+
+initialise(TB, UUID:uuid=name, Role:role=[{source,query}]) :->
+	"Create from UUID"::
+	send_super(TB, initialise),
+	send(TB, slot, uuid, UUID),
+	default(Role, source, TheRole),
+	send(TB, slot, role, TheRole).
+
+open(TB) :->
+	"Open graphical window (for debugging)"::
+	in_pce_thread(show_text_buffer(TB)).
+
+caret(TB, Pos:int) :->
+	"Provide feedback on caret location"::
+	get(TB, editors, Editors),
+	(   send(Editors, empty)
+	->  true
+	;   in_pce_thread(caret_editors(Editors, Pos))
+	).
+
+caret_editors(Editors, Caret) :-
+	send(Editors, for_all, message(@arg1, caret, Caret)).
+
+show_text_buffer(TextBuffer) :-
+	new(E, editor(TextBuffer)),
+	send(view(editor:=E), open).
+
+xref_source(TB, Always:[bool]) :->
+	"Run the cross-referencer on buffer"::
+	get(TB, generation, G),
+	(   (   Always == @on
+	    ->  true
+	    ;   get(TB, xref_generation, GRef),
+		GRef \== G
+	    )
+	->  xref_source_id(TB, SourceId),
+	    (	TB == SourceId
+	    ->	true
+	    ;	send(TB, attribute, xref_source_id, SourceId)
+	    ),
+	    (	xref_module(TB, Module)
+	    ->  xref_source(SourceId,
+			    [ silent(true),
+			      module(Module)
+			    ])
+	    ;	xref_source(SourceId, [silent(true)])
+	    ),
+	    send(TB, slot, xref_generation, G)
+	;   true
+	).
+
+xref_source_id(M, SourceId:any) :<-
+	"Xref source identifier"::
+	get(M, text_buffer, TB),
+	(   get(TB, attribute, xref_source_id, SourceId)
+	->  true
+	;   SourceId = TB
+	).
+
+%%	xref_source_id(+TextBuffer, -SourceID) is det.
+%
+%	Find the object we need  to   examine  for cross-referencing. If
+%	this is an included file, this is the corresponding main file.
+
+xref_source_id(TB, SourceId) :-
+	get(TB, file, File), File \== @nil, !,
+	get(File, absolute_path, Path0),
+	absolute_file_name(Path0, Path),
+	master_load_file(Path, [], Master),
+	(   Master == Path
+	->  SourceId = TB
+	;   SourceId = Master
+	).
+xref_source_id(TB, SourceId) :-
+	get(TB, uuid, SourceId).
+
+%%	xref_module(+TB, -Module) is semidet.
+%
+%	True if we must run the cross-referencing in Module. Now, we use
+%	a single module. Eventually, we should use multiple modules from
+%	a pool.
+
+xref_module(TB, Module) :-
+	get(TB, uuid, Module),
+	(   module_property(foobar, class(temporary))
+	->  true
+	;   set_module(Module:class(temporary)),
+	    add_import_module(Module, swish, start)
+	).
+
+%%	master_load_file(+File, +Seen, -MasterFile) is det.
+%
+%	If file is included into another  file, find the outermost file.
+%	This is the file that needs to  be reloaded instead of reloading
+%	File.
+
+master_load_file(File0, Seen, File) :-
+	source_file_property(File0, included_in(File1, _Line)),
+	\+ memberchk(File1, Seen), !,
+	master_load_file(File1, [File0|Seen], File).
+master_load_file(File, _, File).
+
+:- pce_end_class.
+
+
+		 /*******************************
+		 *	  SERVER TOKENS		*
+		 *******************************/
+
+%%	codemirror_tokens(+Request)
+%
+%	HTTP POST handler that returns an array of tokens for the given
+%	editor.
+
+codemirror_tokens(Request) :-
+	http_read_json_dict(Request, Data, []),
+	debug(cm(tokens), 'Asking for tokens: ~p', [Data]),
+	(   shadow_editor(Data, TB)
+	->  enriched_tokens(TB, Data, Tokens)
+	;   Tokens = [[]]
+	),
+	reply_json_dict(json{tokens:Tokens}, [width(0)]).
+
+enriched_tokens(TB, _Data, Tokens) :-		% source window
+	get(TB, role, source), !,
+	send(TB, xref_source),
+	server_tokens(TB, Tokens).
+enriched_tokens(TB, Data, Tokens) :-		% query window
+	atom_string(SourceID, Data.get(sourceID)),
+	current_editor(SourceID, SourceTB),
+	xref_source_id(SourceTB, XRefID), !,
+	get(TB, contents, string(Query)),
+	prolog_colourise_query(Query, XRefID, colour_item(TB)),
+	collect_tokens(TB, Tokens).
+enriched_tokens(TB, _Data, Tokens) :-
+	get(TB, contents, string(Query)),
+	prolog_colourise_query(Query, swish, colour_item(TB)),
+	collect_tokens(TB, Tokens).
+
+shadow_editor(Data, TB) :-
+	Text = Data.get(text), !,
+	atom_string(UUID, Data.uuid),
+	create_editor(UUID, TB, Data),
+	send(TB, contents, string(Text)).
+shadow_editor(Data, TB) :-
+	_{role:_} :< Data, !,
+	atom_string(UUID, Data.uuid),
+	create_editor(UUID, TB, Data).
+shadow_editor(Data, TB) :-
+	atom_string(UUID, Data.get(uuid)), !,
+	current_editor(UUID, TB).
+
+
+:- thread_local
+	token/3.
+
+%%	show_mirror(+Role) is det.
+%%	server_tokens(+Role) is det.
+%
+%	These predicates help debugging the   server side. show_mirror/0
+%	opens the XPCE editor,  which   simplifies  validation  that the
+%	server  copy  is  in  sync  with    the  client.  The  predicate
+%	server_tokens/1 dumps the token list.
+%
+%	@arg	Role is one of =source= or =query=, expressing the role of
+%		the editor in the SWISH UI.
+
+show_mirror(Role) :-
+	current_editor(_UUID, TB),
+	get(TB, role, Role), !,
+	send(TB, open).
+
+server_tokens(Role) :-
+	current_editor(_UUID, TB),
+	get(TB, role, Role), !,
+	server_tokens(TB, Tokens),
+	print_term(Tokens, [output(user_error)]).
+
+%%	server_tokens(+TextBuffer, -Tokens) is det.
+%
+%	@arg	Tokens is a nested list of Prolog JSON terms.  Each group
+%		represents the tokens found in a single toplevel term.
+
+server_tokens(TB, GroupedTokens) :-
+	get(TB, uuid, UUID),
+	setup_call_cleanup(
+	    pce_open(TB, read, Stream),
+	    ( set_stream_file(TB, Stream),
+	      prolog_colourise_stream(Stream, UUID, colour_item(TB))
+	    ),
+	    close(Stream)),
+	collect_tokens(TB, GroupedTokens).
+
+collect_tokens(TB, GroupedTokens) :-
+	findall(Start-Token, json_token(TB, Start, Token), Pairs),
+	keysort(Pairs, Sorted),
+	pairs_values(Sorted, Tokens),
+	group_by_term(Tokens, GroupedTokens).
+
+set_stream_file(_,_).			% TBD
+
+%%	group_by_term(+Tokens, -Nested) is det.
+%
+%	Group the tokens by  input   term.  This  simplifies incremental
+%	updates of the token  list  at  the   client  sides  as  well as
+%	re-syncronizing. This predicate relies on   the `fullstop` token
+%	that is emitted at the end of each input term.
+
+group_by_term([], []) :- !.
+group_by_term(Flat, [Term|Grouped]) :-
+	take_term(Flat, Term, Rest),
+	group_by_term(Rest, Grouped).
+
+take_term([], [], []).
+take_term([H|T0], [H|T], R) :-
+	(   ends_term(H.get(type))
+	->  T = [],
+	    R = T0
+	;   take_term(T0, T, R)
+	).
+
+ends_term(fullstop).
+ends_term(syntax_error).
+
+%%	json_token(+TB, -Start, -JSON) is nondet.
+%
+%	Extract the stored terms.
+%
+%	@tbd	We could consider to collect the attributes in the
+%		colour_item/4 callback and maintain a global variable
+%		instead of using assert/retract.  Most likely that would
+%		be faster.  Need to profile to check the bottleneck.
+
+json_token(TB, Start, Token) :-
+	retract(token(Style, Start0, Len)),
+	debug(color, 'Trapped ~q.', [token(Style, Start0, Len)]),
+	(   atomic_special(Style, Start0, Len, TB, Type, Attrs)
+	->  Start = Start0
+	;   style(Style, Type0, Attrs0)
+	->  (   Type0 = StartType-EndType
+	    ->	(   Start = Start0,
+		    Type  = StartType
+		;   Start is Start0+Len-1,
+		    Type  = EndType
+		)
+	    ;	Type = Type0,
+		Start = Start0
+	    ),
+	    json_attributes(Attrs0, Attrs, TB, Start0, Len)
+	),
+	dict_create(Token, json, [type(Type)|Attrs]).
+
+atomic_special(atom, Start, Len, TB, Type, Attrs) :-
+	(   get(TB, character, Start, 0'\')
+	->  Type = qatom,
+	    Attrs = []
+	;   Type = atom,
+	    (   Len =< 5			% solo characters, neck, etc.
+	    ->  get(TB, contents, Start, Len, string(Text)),
+	        Attrs = [text(#(Text))]
+	    ;   Attrs = []
+	    )
+	).
+
+json_attributes([], [], _, _, _).
+json_attributes([H0|T0], [H|T], TB, Start, Len) :-
+	json_attribute(H0, H, TB, Start, Len), !,
+	json_attributes(T0, T, TB, Start, Len).
+json_attributes([_|T0], T, TB, Start, Len) :-
+	json_attributes(T0, T, TB, Start, Len).
+
+
+json_attribute(text, text(#(Text)), TB, Start, Len) :- !,
+	get(TB, contents, Start, Len, string(Text)).
+json_attribute(Term, Term, _, _, _).
+
+colour_item(_TB, Style, Start, Len) :-
+	(   style(Style)
+	->  assertz(token(Style, Start, Len))
+	;   debug(color, 'Ignored ~q.', [token(Style, Start, Len)])
+	).
+
+%%	style(+StyleIn) is semidet.
+%%	style(+StyleIn, -SWISHType:atomOrPair, -Attributes:list)
+%
+%	Declare    that    we    map    StyleIn    as    generated    by
+%	library(prolog_colour) into a token of type SWISHType, providing
+%	additional context information based on  Attributes. Elements of
+%	Attributes are terms of the form Name(Value) or the atom =text=.
+%	The latter is mapped to text(String),  where String contains the
+%	text that matches the token character range.
+%
+%	The  resulting  JSON  token  object    has  a  property  =type=,
+%	containing  the  SWISHType  and  the    properties   defined  by
+%	Attributes.
+%
+%	Additional translations can be defined by   adding rules for the
+%	multifile predicate swish:style/3. The base   type, which refers
+%	to the type generated by the   SWISH tokenizer must be specified
+%	by adding an  attribute  base(BaseType).   For  example,  if the
+%	colour system classifies an  atom  as   refering  to  a database
+%	column, library(prolog_colour) may emit  db_column(Name) and the
+%	following rule should ensure consistent mapping:
+%
+%	  ==
+%	  swish_highlight:style(db_column(Name),
+%				db_column, [text, base(atom)]).
+%	  ==
+
+:- multifile
+	style/3.
+
+style(Style) :-
+	style(Style, _, _).
+
+style(neck(Neck),     neck, [ text(Text) ]) :-
+	neck_text(Neck, Text).
+style(head(Class, Head), Type, [ text, arity(Arity) ]) :-
+	goal_arity(Head, Arity),
+	head_type(Class, Type).
+style(goal(Class, Goal), Type, [ text, arity(Arity) | More ]) :-
+	goal_arity(Goal, Arity),
+	goal_type(Class, Type, More).
+style(file_no_depend(Path), file_no_depends,		   [text, path(Path)]).
+style(file(Path),	 file,				   [text, path(Path)]).
+style(nofile,		 nofile,			   [text]).
+style(option_name,	 option_name,			   [text]).
+style(no_option_name,	 no_option_name,		   [text]).
+style(flag_name(_Flag),	 flag_name,			   [text]).
+style(no_flag_name(_Flag), no_flag_name,		   [text]).
+style(fullstop,		 fullstop,			   []).
+style(var,		 var,				   [text]).
+style(singleton,	 singleton,			   [text]).
+style(string,		 string,			   []).
+style(codes,		 codes,				   []).
+style(chars,		 chars,				   []).
+style(atom,		 atom,				   []).
+style(meta(_Spec),	 meta,				   []).
+style(op_type(_Type),	 op_type,			   [text]).
+style(functor,		 functor,			   [text]).
+style(control,		 control,			   [text]).
+style(identifier,	 identifier,			   [text]).
+style(module(_Module),   module,			   [text]).
+style(error,		 error,				   [text]).
+style(type_error(_Expect), error,			   [text]).
+style(syntax_error(_Msg,_Pos), syntax_error,		   []).
+style(predicate_indicator, atom,			   [text]).
+style(predicate_indicator, atom,			   [text]).
+style(arity,		 int,				   []).
+style(int,		 int,				   []).
+style(float,		 float,				   []).
+style(qq(open),		 qq_open,			   []).
+style(qq(sep),		 qq_sep,			   []).
+style(qq(close),	 qq_close,			   []).
+style(qq_type,		 qq_type,			   [text]).
+style(dict_tag,		 tag,				   [text]).
+style(dict_key,		 key,				   [text]).
+style(dict_sep,		 sep,				   []).
+style(func_dot,		 atom,				   [text(.)]).
+style(dict_return_op,	 atom,				   [text(:=)]).
+style(dict_function(F),  dict_function,			   [text(F)]).
+style(empty_list,	 list_open-list_close,		   []).
+style(list,		 list_open-list_close,		   []).
+style(dcg(terminal),	 list_open-list_close,		   []).
+style(dcg(plain),	 brace_term_open-brace_term_close, []).
+style(brace_term,	 brace_term_open-brace_term_close, []).
+style(dict_content,	 dict_open-dict_close,             []).
+style(expanded,		 expanded,			   [text]).
+style(comment_string,	 comment_string,		   []).
+					% from library(http/html_write)
+style(html(_Element),	 html,				   []).
+style(entity(_Element),	 entity,			   []).
+style(html_attribute(_), html_attribute,		   []).
+style(sgml_attr_function,sgml_attr_function,		   []).
+style(http_location_for_id(_), http_location_for_id,       []).
+style(http_no_location_for_id(_), http_no_location_for_id, []).
+					% XPCE support
+style(method(send),	 xpce_method,			   [text]).
+style(method(get),	 xpce_method,			   [text]).
+style(class(built_in,_Name),	  xpce_class_built_in,	   [text]).
+style(class(library(File),_Name), xpce_class_lib,	   [text, file(File)]).
+style(class(user(File),_Name),	  xpce_class_user,	   [text, file(File)]).
+style(class(user,_Name),	  xpce_class_user,	   [text]).
+style(class(undefined,_Name),	  xpce_class_undef,	   [text]).
+
+neck_text(clause,       (:-)).
+neck_text(grammar_rule, (-->)).
+neck_text(method(send), (:->)).
+neck_text(method(get),  (:<-)).
+neck_text(directive,    (:-)).
+
+head_type(exported,	head_exported).
+head_type(public(_),	head_public).
+head_type(extern(_),	head_extern).
+head_type(dynamic,	head_dynamic).
+head_type(multifile,	head_multifile).
+head_type(unreferenced,	head_unreferenced).
+head_type(hook,		head_hook).
+head_type(meta,		head_meta).
+head_type(constraint,	head_constraint).
+head_type(imported,	head_imported).
+head_type(built_in,	head_built_in).
+head_type(iso,		head_iso).
+head_type(def_iso,	head_def_iso).
+head_type(def_swi,	head_def_swi).
+head_type(_,		head).
+
+goal_type(built_in,	      goal_built_in,	 []).
+goal_type(imported(File),     goal_imported,	 [file(File)]).
+goal_type(autoload(File),     goal_autoload,	 [file(File)]).
+goal_type(global,	      goal_global,	 []).
+goal_type(undefined,	      goal_undefined,	 []).
+goal_type(thread_local(Line), goal_thread_local, [line(Line)]).
+goal_type(dynamic(Line),      goal_dynamic,	 [line(Line)]).
+goal_type(multifile(Line),    goal_multifile,	 [line(Line)]).
+goal_type(expanded,	      goal_expanded,	 []).
+goal_type(extern(_),	      goal_extern,	 []).
+goal_type(recursion,	      goal_recursion,	 []).
+goal_type(meta,		      goal_meta,	 []).
+goal_type(foreign(_),	      goal_foreign,	 []).
+goal_type(local(Line),	      goal_local,	 [line(Line)]).
+goal_type(constraint(Line),   goal_constraint,	 [line(Line)]).
+goal_type(not_callable,	      goal_not_callable, []).
+
+%%	goal_arity(+Goal, -Arity) is det.
+%
+%	Get the arity of a goal safely in SWI7
+
+goal_arity(Goal, Arity) :-
+	(   compound(Goal)
+	->  compound_name_arity(Goal, _, Arity)
+	;   Arity = 0
+	).
+
+		 /*******************************
+		 *	 HIGHLIGHT CONFIG	*
+		 *******************************/
+
+:- multifile
+	swish_config:config/2,
+	css/3.				% ?Context, ?Selector, -Attributes
+
+%%	swish_config:config(-Name, -Styles) is det.
+%
+%	Provides the object `config.swish.style`,  a   JSON  object that
+%	maps   style   properties   of    user-defined   extensions   of
+%	library(prolog_colour). This info is  used   by  the server-side
+%	colour engine to populate the CodeMirror styles.
+%
+%	@tbd	Provide summary information
+
+swish_config:config(cm_style, Styles) :-
+	findall(Name-Style, highlight_style(Name, Style), Pairs),
+	keysort(Pairs, Sorted),
+	remove_duplicate_styles(Sorted, Unique),
+	dict_pairs(Styles, json, Unique).
+swish_config:config(cm_hover_style, Styles) :-
+	findall(Sel-Attrs, css_dict(hover, Sel, Attrs), Pairs),
+	dict_pairs(Styles, json, Pairs).
+
+remove_duplicate_styles([], []).
+remove_duplicate_styles([H|T0], [H|T]) :-
+	H = K-_,
+	remove_same(K, T0, T1),
+	remove_duplicate_styles(T1, T).
+
+remove_same(K, [K-_|T0], T) :- !,
+	remove_same(K, T0, T).
+remove_same(_, Rest, Rest).
+
+highlight_style(StyleName, Style) :-
+	style(Term, StyleName, _),
+	atom(StyleName),
+	(   prolog_colour:style(Term, Attrs0)
+        ->  maplist(css_style, Attrs0, Attrs),
+	    dict_create(Style, json, Attrs)
+	).
+
+css_style(bold(true),      'font-weight'(bold)) :- !.
+css_style(underline(true), 'text-decoration'(underline)) :- !.
+css_style(colour(Name), color(RGB)) :-
+	current_prolog_flag(gui, true), !,
+	get(colour(Name), red,   R0),
+	get(colour(Name), green, G0),
+	get(colour(Name), blue,  B0),
+	R is R0//256,
+	G is G0//256,
+	B is B0//256,
+	format(atom(RGB), '#~|~`0t~16r~2+~`0t~16r~2+~`0t~16r~2+', [R,G,B]).
+css_style(Style, Style).
+
+%%	css(?Context, ?Selector, -Style) is nondet.
+%
+%	Multifile hook to define additional style to apply in a specific
+%	context.  Currently defined contexts are:
+%
+%	  - hover
+%	  Used for CodeMirror hover extension.
+%
+%	@arg Selector is a CSS selector, which is refined by Context
+%	@arg Style is a list of Name(Value) terms.
+
+css_dict(Context, Selector, Style) :-
+	css(Context, Selector, Attrs0),
+	maplist(css_style, Attrs0, Attrs),
+	dict_create(Style, json, Attrs).
+
+
+		 /*******************************
+		 *	       INFO		*
+		 *******************************/
+
+:- multifile
+	prolog:predicate_summary/2.
+
+%%	token_info(+Request)
+%
+%	HTTP handler that provides information  about a token.
+
+token_info(Request) :-
+	http_parameters(Request, [], [form_data(Form)]),
+	maplist(type_convert, Form, Values),
+	dict_create(Token, token, Values),
+	reply_html_page(plain,
+			title('token info'),
+			\token_info_or_none(Token)).
+
+type_convert(Name=Atom, Name=Number) :-
+	atom_number(Atom, Number), !.
+type_convert(NameValue, NameValue).
+
+
+token_info_or_none(Token) -->
+	token_info(Token), !.
+token_info_or_none(_) -->
+	html(span(class('token-noinfo'), 'No info available')).
+
+%%	token_info(+Token:dict)// is det.
+%
+%	Generate HTML, providing details about Token.   Token is a dict,
+%	providing  the  enriched  token  as  defined  by  style/3.  This
+%	multifile non-terminal can be hooked to provide details for user
+%	defined style extensions.
+
+:- multifile token_info//1.
+
+token_info(Token) -->
+	{ _{type:Type, text:Name, arity:Arity} :< Token,
+	  goal_type(_, Type, _), !,
+	  ignore(token_predicate_module(Token, Module)),
+	  predicate_info(Module:Name/Arity, Info)
+	},
+	pred_info(Info).
+
+pred_info([]) -->
+	html(span(class('pred-nosummary'), 'No help available')).
+pred_info([Info|_]) -->			% TBD: Ambiguous
+	(pred_tags(Info)     -> [];[]),
+	(pred_summary(Info)  -> [];[]).
+
+pred_tags(Info) -->
+	{ Info.get(iso) == true },
+	html(span(class('pred-tag'), 'ISO')).
+
+pred_summary(Info) -->
+	html(span(class('pred-summary'), Info.get(summary))).
+
+
+%%	token_predicate_module(+Token, -Module) is semidet.
+%
+%	Try to extract the module from the token.
+
+token_predicate_module(Token, Module) :-
+	source_file_property(Token.get(file), module(Module)), !.
+
+%%	predicate_info(+PI, -Info:list(dict)) is det.
+%
+%	Info is a list of dicts providing details about predicates that
+%	match PI.  Fields in dict are:
+%
+%	  - module:Atom
+%	  Module of the predicate
+%	  - name:Atom
+%	  Name of the predicate
+%	  - arity:Integer
+%	  Arity of the predicate
+%	  - summary:Text
+%	  Summary text extracted from the system manual or PlDoc
+%	  - iso:Boolean
+%	  Presend and =true= if the predicate is an ISO predicate
+
+predicate_info(PI, Info) :-
+	PI = Module:Name/Arity,
+	findall(Dict,
+		( setof(Key-Value,
+			predicate_info(PI, Key, Value),
+			Pairs),
+		  dict_pairs(Dict, json,
+			     [ module - Module,
+			       name   - Name,
+			       arity  - Arity
+			     | Pairs
+			     ])
+		),
+		Info).
+
+%%	predicate_info(?PI, -Key, -Value) is nondet.
+%
+%	Find information about predicates from   the  system, manual and
+%	PlDoc. First, we  deal  with  ISO   predicates  that  cannot  be
+%	redefined and are documented in the   manual. Next, we deal with
+%	predicates that are documented in  the   manual.
+%
+%	@bug: Handling predicates documented  in   the  manual  is buggy
+%	because their definition may  be  overruled   by  the  user.  We
+%	probably must include the file into the equation.
+
+					% ISO predicates
+predicate_info(Module:Name/Arity, Key, Value) :-
+	compound_name_arity(Head, Name, Arity),
+	predicate_property(system:Head, iso), !,
+	ignore(Module = system),
+	(   catch(predicate(Name, Arity, Summary, _, _), _, fail),
+	    Key = summary,
+	    Value = Summary
+	;   Key = iso,
+	    Value = true
+	).
+predicate_info(_Module:Name/Arity, summary, Summary) :-
+	catch(predicate(Name, Arity, Summary, _, _), _, fail), !.
+predicate_info(PI, summary, Summary) :-	% PlDoc
+	prolog:predicate_summary(PI, Summary).
diff --git a/lib/swish/page.pl b/lib/swish/page.pl
index f77589e..f64a5dc 100644
--- a/lib/swish/page.pl
+++ b/lib/swish/page.pl
@@ -98,7 +98,14 @@ swish_reply(SwishOptions, Request) :-
 	->  true
 	;   reply_html_page(
 		swish(main),
-		title('SWISH -- SWI-Prolog for SHaring'),
+		[ title('SWISH -- SWI-Prolog for SHaring'),
+		  link([ rel('shortcut icon'),
+			 href('/icons/favicon.ico')
+		       ]),
+		  link([ rel('apple-touch-icon'),
+			 href('/icons/swish-touch-icon.png')
+		       ])
+		],
 		\swish_page(Options))
 	).
 
diff --git a/lib/swish/render.pl b/lib/swish/render.pl
new file mode 100644
index 0000000..a55fea8
--- /dev/null
+++ b/lib/swish/render.pl
@@ -0,0 +1,205 @@
+/*  Part of SWI-Prolog
+
+    Author:        Jan Wielemaker
+    E-mail:        J.Wielemaker@cs.vu.nl
+    WWW:           http://www.swi-prolog.org
+    Copyright (C): 2014, VU University Amsterdam
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License
+    as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+    As a special exception, if you link this library with other files,
+    compiled with a Free Software compiler, to produce an executable, this
+    library does not by itself cause the resulting executable to be covered
+    by the GNU General Public License. This exception does not however
+    invalidate any other reasons why the executable file might be covered by
+    the GNU General Public License.
+*/
+
+:- module(swish_render,
+	  [ use_rendering/1,		% +Renderer
+	    use_rendering/2,		% +Renderer, +Options
+
+	    register_renderer/2		% Declare a rendering module
+	  ]).
+:- use_module(library(pengines_io), []).
+:- use_module(library(http/html_write)).
+:- use_module(library(http/term_html)).
+:- use_module(library(option)).
+:- use_module(library(error)).
+
+:- meta_predicate
+	register_renderer(:, +),
+	use_rendering(:),
+	use_rendering(:, +).
+
+/** <module> SWISH term-rendering support
+
+This module manages rendering answers  using alternative vizualizations.
+The idea is that a  specific  context   _uses_  zero  or  more rendering
+modules.  These  rendering  modules   provide    an   alternative   HTML
+representation for the target term. If  multiple possible renderings are
+found,  a  =|<div  class="render-multi">|=  element  is  generated  that
+contains the alternative renderings. The   jQuery  plugin =renderMulti=,
+defined in =answer.js= adds the  behaviour   to  change rendering to the
+generated div.
+
+The user can import rendering schemes into the current context using the
+directive below. `Spec` is either an atom   or string, making the system
+look for render(Spec), or it is a   (single) file specification that can
+be used for use_module/1.
+
+  ==
+  :- use_rendering(Spec).
+  ==
+
+A rendering module is a  Prolog   module  that  defines the non-terminal
+term_rendering//3,  which  will  be  called  as  below.  `Term`  is  the
+(non-var) term that must be rendered, `Vars` is a list of variable names
+bound to this term and `Options` is a   list of write options that would
+normally  be  passed  to  write_term/3.  The   grammar  is  executed  by
+library(http/html_write) and must  generate   compatible  tokens  (which
+means it must call html//1 to generate HTML tokens).
+
+  ==
+  phrase(Renderer:term_rendering(Term, Vars, Options), Tokens)
+  ==
+*/
+
+:- multifile user:file_search_path/2.
+
+user:file_search_path(render, swish('lib/render')).
+
+
+%%	use_rendering(+FileOrID)
+%
+%	Register an answer  renderer.   Same  as use_rendering(FileOrID,
+%	[]).
+%
+%	@see use_rendering/2.
+
+:- multifile user:term_expansion/2.
+
+use_rendering(Rendering) :-
+	use_rendering(Rendering, []).
+
+%%	use_rendering(:ID, +Options)
+%
+%	Register an answer renderer  with   options.  Options are merged
+%	with   write-options   and   passed     to    the   non-terminal
+%	term_rendering//3 defined in the rendering module.
+
+use_rendering(Rendering, Options) :-
+	Rendering = Into:Renderer,
+	must_be(atom, Renderer),
+	(   renderer(Renderer, _, _)
+	->  true
+	;   existence_error(renderer, Renderer)
+	),
+	retractall(Into:'swish renderer'(Renderer, _)),
+	assertz(Into:'swish renderer'(Renderer, Options)).
+
+user:term_expansion((:- use_rendering(Renderer)), Expanded) :-
+	expand_rendering(Renderer, [], Expanded).
+user:term_expansion((:- use_rendering(Renderer, Options)), Expanded) :-
+	expand_rendering(Renderer, Options, Expanded).
+
+expand_rendering(Module:Renderer, Options,
+		 Module:'swish renderer'(Renderer, Options)) :- !,
+	must_be(atom, Module),
+	must_be(atom, Renderer).
+expand_rendering(Renderer, Options,
+		 'swish renderer'(Renderer, Options)) :-
+	must_be(atom, Renderer).
+
+%%	pengines_io:binding_term(+Term, +Vars, +Options) is semidet.
+%
+%	Produce alternative renderings for Term, which  is a binding for
+%	Vars.
+
+:- multifile pengines_io:binding_term//3.
+
+pengines_io:binding_term(Term, Vars, Options) -->
+	{ option(module(Module), Options),
+	  findall(Tokens,
+		  call_term_rendering(Module, Term, Vars, Options, Tokens),
+		  NestedTokens),
+	  NestedTokens \== [], !
+	},
+	alt_renderer(NestedTokens, Term, Options).
+
+%%	call_term_rendering(+Module, +Term, +Vars, +Options, -Tokens) is nondet.
+%
+%	Call  term_rendering//3  in  all  modules    from  which  Module
+%	inherits.
+
+call_term_rendering(Module, Term, Vars, Options, Tokens) :-
+	State = state([]),
+	default_module(Module, Target),
+	current_predicate(Target:'swish renderer'/2),
+	Target:'swish renderer'(Name, RenderOptions),
+	atom(Name),
+	is_new(State, Name),
+	renderer(Name, RenderModule, _Comment),
+	merge_options(RenderOptions, Options, AllOptions),
+	phrase(RenderModule:term_rendering(Term, Vars, AllOptions), Tokens).
+
+%%	is_new(!State, +M) is semidet.
+%
+%	Only succeeds once for each new ground value M.
+
+is_new(State, M) :-
+	arg(1, State, Seen),
+	(   memberchk(M, Seen)
+	->  fail
+	;   nb_linkarg(1, State, [M|Seen])
+	).
+
+%%	alt_renderer(+Specialised, +Term, +Options)//
+%
+%	Create a rendering selection object after we have found at least
+%	one alternative rendering for Term.
+
+alt_renderer(Specialised, Term, Options) -->
+	html(div(class('render-multi'),
+		 \specialised(Specialised, Term, Options))).
+
+specialised([], Term, Options) -->
+	html(span('data-render'('Prolog term'), \term(Term, Options))).
+specialised([H|T], Term, Options) -->
+	tokens(H),
+	specialised(T, Term, Options).
+
+tokens([]) --> [].
+tokens([H|T]) --> [H], tokens(T).
+
+
+		 /*******************************
+		 *	   REGISTRATION		*
+		 *******************************/
+
+:- multifile
+	renderer/3.
+
+%%	register_renderer(:Name, +Comment)
+%
+%	Register a module as SWISH rendering component.
+
+register_renderer(Name, Comment) :-
+	throw(error(context_error(nodirective, register_renderer(Name, Comment)),
+		    _)).
+
+user:term_expansion((:- register_renderer(Name, Comment)),
+		    swish_render:renderer(Name, Module, Comment)) :-
+	prolog_load_context(module, Module).
diff --git a/lib/swish/render/rdf.pl b/lib/swish/render/rdf.pl
new file mode 100644
index 0000000..3aca11a
--- /dev/null
+++ b/lib/swish/render/rdf.pl
@@ -0,0 +1,72 @@
+/*  Part of SWI-Prolog
+
+    Author:        Jan Wielemaker
+    E-mail:        J.Wielemaker@cs.vu.nl
+    WWW:           http://www.swi-prolog.org
+    Copyright (C): 2014, VU University Amsterdam
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License
+    as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+    As a special exception, if you link this library with other files,
+    compiled with a Free Software compiler, to produce an executable, this
+    library does not by itself cause the resulting executable to be covered
+    by the GNU General Public License. This exception does not however
+    invalidate any other reasons why the executable file might be covered by
+    the GNU General Public License.
+*/
+
+:- module(cliopatria_render_rdf,
+	  [ term_rendering//3			% +Term, +Vars, +Options
+	  ]).
+:- use_module(library(semweb/rdf_db)).
+:- use_module(components(label)).
+:- use_module(library(uri)).
+:- use_module(library(swish/render)).
+
+:- register_renderer(rdf, "Render RDF terms").
+
+/** <module> SWISH RDF renderer
+
+Render RDF data.
+*/
+
+%%	term_rendering(+Term, +Vars, +Options)//
+%
+%	Renders Term as a uri.  Furt
+
+term_rendering(Term, _Vars, Options) -->
+	{ is_rdf(Term)
+	}, !,
+	rdf_link(Term, Options).
+
+is_rdf(Term) :-
+	is_uri(Term), !.
+is_rdf(literal(Value)) :-
+	ground(Value),
+	is_literal(Value).
+
+is_uri(Term) :-
+	atom(Term),
+	(   uri_is_global(Term)
+	->  true
+	;   rdf_is_bnode(Term)
+	).
+
+is_literal(Atomic) :- is_plain_literal(Atomic).
+is_literal(type(Type, Literal)) :- is_uri(Type), is_plain_literal(Literal).
+is_literal(lang(Lang, Literal)) :- atom(Lang),   is_plain_literal(Literal).
+
+is_plain_literal(Value) :-
+	atomic(Value).
diff --git a/lib/swish/render/table.pl b/lib/swish/render/table.pl
new file mode 100644
index 0000000..d2348fb
--- /dev/null
+++ b/lib/swish/render/table.pl
@@ -0,0 +1,143 @@
+/*  Part of SWI-Prolog
+
+    Author:        Jan Wielemaker
+    E-mail:        J.Wielemaker@cs.vu.nl
+    WWW:           http://www.swi-prolog.org
+    Copyright (C): 2014, VU University Amsterdam
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License
+    as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+    As a special exception, if you link this library with other files,
+    compiled with a Free Software compiler, to produce an executable, this
+    library does not by itself cause the resulting executable to be covered
+    by the GNU General Public License. This exception does not however
+    invalidate any other reasons why the executable file might be covered by
+    the GNU General Public License.
+*/
+
+:- module(swish_render_table,
+	  [ term_rendering//3			% +Term, +Vars, +Options
+	  ]).
+:- use_module(library(apply)).
+:- use_module(library(http/html_write)).
+:- use_module(library(http/term_html)).
+:- use_module('../render').
+
+:- register_renderer(table, "Render data as tables").
+
+/** <module> SWISH table renderer
+
+Render table-like data.
+*/
+
+%%	term_rendering(+Term, +Vars, +Options)//
+%
+%	Renders Term as  a  table.   This  renderer  recognises  several
+%	representations of table-like data:
+%
+%	  $ A list of terms of equal arity :
+%	  $ A list of lists of equal length :
+%
+%	@tbd: recogniser more formats, provide options to specify the
+%	header, etc.
+
+term_rendering(Term, _Vars, Options) -->
+	{ is_list_of_terms(Term, _Rows, Cols)
+	}, !,
+	html(div([ style('display:inline-block'),
+		   'data-render'('List of terms as a table')
+		 ],
+		 [ table(class('render-table'),
+			 [ \header(Cols, Options),
+			   \rows(Term)
+			 ])
+		 ])).
+term_rendering(Term, _Vars, Options) -->
+	{ is_list_of_lists(Term, _Rows, Cols)
+	}, !,
+	html(div([ style('display:inline-block'),
+		   'data-render'('List of lists as a table')
+		 ],
+		 [ table(class('render-table'),
+			 [ \header(Cols, Options),
+			   \rows(Term)
+			 ])
+		 ])).
+
+rows([]) --> [].
+rows([H|T]) -->
+	{ cells(H, Cells) },
+	html(tr(\row(Cells))),
+	rows(T).
+
+row([]) --> [].
+row([H|T]) -->
+	html(td(\term(H, []))),
+	row(T).
+
+cells(Row, Cells) :-
+	is_list(Row), !,
+	Cells = Row.
+cells(Row, Cells) :-
+	compound(Row),
+	compound_name_arguments(Row, _, Cells).
+
+%%	header(+NCols, +Options)// is det.
+%
+%	Include a header row  if   an  option header(+ListOfColNames) is
+%	present, whose length matches NCols.
+
+header(Cols, Options) -->
+	{ option(header(ColNames), Options),
+	  length(ColNames, Cols)
+	},
+	html(tr(class(hrow), \header_row(ColNames))).
+header(_, _) --> [].
+
+header_row([]) --> [].
+header_row([H|T]) -->
+	html(th(\term(H, []))),
+	header_row(T).
+
+
+%%	is_list_of_terms(@Term, -Rows, -Cols) is semidet.
+%
+%	Recognises a list of terms with   the  same functor and non-zero
+%	ariry.
+
+is_list_of_terms(Term, Rows, Cols) :-
+	is_list(Term), Term \== [],
+	length(Term, Rows),
+	maplist(is_term_row(_Name, Cols), Term),
+	Cols > 0.
+
+is_term_row(Name, Arity, Term) :-
+	compound(Term),
+	compound_name_arity(Term, Name, Arity).
+
+%%	is_list_of_lists(@Term, -Rows, -Cols) is semidet.
+%
+%	Recognise a list of lists of equal length.
+
+is_list_of_lists(Term, Rows, Cols) :-
+	is_list(Term), Term \== [],
+	length(Term, Rows),
+	maplist(is_list_row(Cols), Term),
+	Cols > 0.
+
+is_list_row(Length, Term) :-
+	is_list(Term),
+	length(Term, Length).
+