swish/commit

New upstream versions

authorJan Wielemaker
Wed Sep 23 14:46:50 2015 +0200
committerJan Wielemaker
Wed Sep 23 14:46:50 2015 +0200
commit89c2ffa40d8013b961d66a456fd76105c6bceff3
tree9dfb376fa317b0ab36eb1ec0c23e978ded56960f
parentc161b04591ffe11def51b0315af6171346d26bdd
Diff style: patch stat
diff --git a/lib/swish/page.pl b/lib/swish/page.pl
index f67d04d..75a3a19 100644
--- a/lib/swish/page.pl
+++ b/lib/swish/page.pl
@@ -112,22 +112,26 @@ swish_reply(SwishOptions, Request) :-
 		   background(_, [optional(true)]),
 		   examples(_,   [optional(true)]),
 		   q(_,          [optional(true)]),
-		   format(_,     [oneof([swish,raw]), default(swish)])
+		   format(_,     [oneof([swish,raw,json]), default(swish)])
 		 ],
 	http_parameters(Request, Params),
 	params_options(Params, Options0),
 	merge_options(Options0, SwishOptions, Options1),
 	source_option(Request, Options1, Options2),
-	swish_reply1(Options2).
+	option(format(Format), Options2),
+	swish_reply2(Format, Options2).
 
-swish_reply1(Options) :-
-	option(code(Code), Options),
-	option(format(raw), Options), !,
+swish_reply2(raw, Options) :-
+	option(code(Code), Options), !,
 	format('Content-type: text/x-prolog~n~n'),
 	format('~s', [Code]).
-swish_reply1(Options) :-
+swish_reply2(json, Options) :-
+	option(code(Code), Options), !,
+	option(meta(Meta), Options, _{}),
+	reply_json_dict(json{data:Code, meta:Meta}).
+swish_reply2(_, Options) :-
 	swish_config:reply_page(Options), !.
-swish_reply1(Options) :-
+swish_reply2(_, Options) :-
 	reply_html_page(
 	    swish(main),
 	    [ title('SWISH -- SWI-Prolog for SHaring'),
@@ -207,14 +211,46 @@ path_info_file(PathInfo, Path, Options) :-
 	confirm_access(Path, AliasOptions), !,
 	option(alias(Alias), Options, _).
 
-source_data(Path, Code, [title(Title), type(Ext)]) :-
+source_data(Path, Code, [title(Title), type(Ext), meta(Meta)]) :-
 	setup_call_cleanup(
 	    open(Path, read, In, [encoding(utf8)]),
 	    read_string(In, _, Code),
 	    close(In)),
+	source_metadata(Path, Code, Meta),
 	file_base_name(Path, File),
 	file_name_extension(Title, Ext, File).
 
+%%	source_metadata(+Path, +Code, -Meta:dict) is det.
+%
+%	Obtain meta information about a local  source file. Defined meta
+%	info is:
+%
+%	  - last_modified:Time
+%	  Last modified stamp of the file.  Always present.
+%	  - loaded:true
+%	  Present of the file is a loaded source file
+%	  - modified_since_loaded:true
+%	  Present if the file loaded, has been edited, but not
+%	  yet reloaded.
+
+source_metadata(Path, Code, Meta) :-
+	findall(Name-Value, source_metadata(Path, Code, Name, Value), Pairs),
+	dict_pairs(Meta, meta, Pairs).
+
+source_metadata(Path, _Code, path, Path).
+source_metadata(Path, _Code, last_modified, Modified) :-
+	time_file(Path, Modified).
+source_metadata(Path, _Code, loaded, true) :-
+	source_file(Path).
+source_metadata(Path, _Code, modified_since_loaded, true) :-
+	source_file_property(Path, modified(ModifiedWhenLoaded)),
+	time_file(Path, Modified),
+	ModifiedWhenLoaded \== Modified.
+source_metadata(Path, _Code, module, Module) :-
+	file_name_extension(_, Ext, Path),
+	prolog_file_type(Ext, prolog),
+	xref_public_list(Path, _, [module(Module)]).
+
 confirm_access(Path, Options) :-
 	option(if(Condition), Options), !,
 	must_be(oneof([loaded]), Condition),
@@ -359,16 +395,19 @@ swish_config_hash -->
 %	  - file(+File)
 %	  If present and code(String) is present, also associate the
 %	  editor with the given file.  See storage.pl.
+%	  - url(+URL)
+%	  as file(File), but used if the data is loaded from an
+%	  alias/file path.
+%	  - title(+Title)
+%	  Defines the title used for the tab.
 
 source(pl, Options) -->
 	{ option(code(Spec), Options), !,
 	  download_source(Spec, Source, Options),
-	  phrase(source_data_attrs(Options), Extra),
-	  source_meta_data(MetaAttrs, Options)
+	  phrase(source_data_attrs(Options), Extra)
 	},
 	html(div([ class(['prolog-editor']),
 		   'data-label'('Program')
-		 | MetaAttrs
 		 ],
 		 [ textarea([ class([source,prolog]),
 			      style('display:none')
@@ -381,7 +420,8 @@ source(_, _) --> [].
 source_data_attrs(Options) -->
 	(source_file_data(Options) -> [] ; []),
 	(source_url_data(Options) -> [] ; []),
-	(source_title_data(Options) -> [] ; []).
+	(source_title_data(Options) -> [] ; []),
+	(source_meta_data(Options) -> [] ; []).
 
 source_file_data(Options) -->
 	{ option(file(File), Options) },
@@ -392,18 +432,17 @@ source_url_data(Options) -->
 source_title_data(Options) -->
 	{ option(title(File), Options) },
 	['data-title'(File)].
+source_meta_data(Options) -->
+	{ option(meta(Meta), Options), !,
+	  atom_json_dict(Text, Meta, [])
+	},
+	['data-meta'(Text)].
 
-
-%%	source_meta_data(-Extra, +Options)
+%%	background(+Options)//
 %
-%	Dump the meta-data of the provided file into swish.meta_data.
-%	@tbd: serialize and add
-
-source_meta_data(['data-meta'(Text)], Options) :-
-	option(file(_), Options),
-	option(meta(Meta), Options), !,
-	atom_json_dict(Text, Meta, []).
-source_meta_data([], _).
+%	Associate  the  background  program  (if  any).  The  background
+%	program is not displayed in  the  editor,   but  is  sent to the
+%	pengine for execution.
 
 background(Options) -->
 	{ option(background(Spec), Options), !,
@@ -443,12 +482,10 @@ query(_) --> [].
 notebooks(swinb, Options) -->
 	{ option(code(Spec), Options),
 	  download_source(Spec, NoteBookText, Options),
-	  phrase(source_data_attrs(Options), Extra),
-	  source_meta_data(MetaAttrs, Options)
+	  phrase(source_data_attrs(Options), Extra)
 	},
 	html(div([ class('notebook'),
 		   'data-label'('Notebook')		% Use file?
-		 | MetaAttrs
 		 ],
 		 [ pre([ class('notebook-data'),
 			 style('display:none')
diff --git a/lib/swish/render/graphviz.pl b/lib/swish/render/graphviz.pl
index 6d5e5df..4b77e4d 100644
--- a/lib/swish/render/graphviz.pl
+++ b/lib/swish/render/graphviz.pl
@@ -31,6 +31,7 @@
 	  [ term_rendering//3			% +Term, +Vars, +Options
 	  ]).
 :- use_module(library(http/html_write)).
+:- use_module(library(http/js_write)).
 :- use_module(library(http/http_dispatch)).
 :- use_module(library(http/http_parameters)).
 :- use_module(library(http/http_path)).
@@ -142,7 +143,55 @@ render_dot(DOTString, Program, _Options) -->	% <svg> rendering
 		       ))
 	},
 	(   { Error == "" }
-	->  html(\[SVG])
+	->  html(div([ class(['render-graphviz', 'reactive-size']),
+		       'data-render'('As Graphviz graph')
+		     ],
+		     [ \[SVG],
+		       \js_script({|javascript||
+(function() {
+   if ( $.ajaxScript ) {
+     var div  = $.ajaxScript.parent();
+     var svg  = div.find("svg");
+     var data = { w0: svg.width(),
+		  h0: svg.height()
+		};
+     var pan;
+
+     function updateSize() {
+       console.log("updateSize");
+       var w = svg.closest("div.answer").innerWidth();
+
+       function reactive() {
+	 if ( !data.reactive ) {
+	   data.reactive = true;
+	   div.on("reactive-resize", updateSize);
+	 }
+       }
+
+       w = Math.max(w*0.85, 100);
+       if ( w < data.w0 ) {
+	 svg.width(w);
+	 svg.height(w = Math.max(w*data.h0/data.w0, w/4));
+	 reactive();
+	 if ( pan ) {
+	   pan.resize();
+	   pan.fit();
+	   pan.center();
+	 }
+       }
+     }
+
+     require(["svg-pan-zoom"], function(svgPanZoom) {
+       updateSize()
+       pan = svgPanZoom(svg[0], {
+			  // controlIconsEnabled: true
+			  maxZoom: 50
+			});
+    });
+   }
+ })();
+		      |})
+		     ]))
 	;   html(div(style('color:red;'),
 		     [ '~w'-[Program], ': ', Error]))
 	).
diff --git a/lib/swish/search.pl b/lib/swish/search.pl
index f6fc4c7..5e2236a 100644
--- a/lib/swish/search.pl
+++ b/lib/swish/search.pl
@@ -28,7 +28,8 @@
 */
 
 :- module(swish_search,
-	  [ search_box//1		% +Options
+	  [ search_box//1,		% +Options
+	    match/3			% +Line, +Query, +Options
 	  ]).
 :- use_module(library(lists)).
 :- use_module(library(http/html_write)).
@@ -42,7 +43,7 @@
 :- use_module(config).
 
 :- multifile
-	typeahead/3.			% +Set, +Query, -Match
+	typeahead/4.			% +Set, +Query, -Match, +Options
 
 /** <module> SWISH search from the navigation bar
 
@@ -89,13 +90,14 @@ search_box(_Options) -->
 
 typeahead(Request) :-
 	http_parameters(Request,
-			[ q(Query, [default('')]),
-			  set(Set, [default(predicates)])
+			[ q(Query,     [default('')]),
+			  set(Set,     [default(predicates)]),
+			  match(Match, [default(sow)])
 			]),
-	findall(Match, typeahead(Set, Query, Match), Matches),
-	reply_json_dict(Matches).
+	findall(Result, typeahead(Set, Query, Result, _{match:Match}), Results),
+	reply_json_dict(Results).
 
-%%	typeahead(+Type, +Query, -Match) is nondet.
+%%	typeahead(+Type, +Query, -Match, +Options:dict) is nondet.
 %
 %	Find  typeahead  suggestions  for  a  specific  search  category
 %	(Type). This oredicate is a   multifile  predicate, which allows
@@ -112,23 +114,29 @@ typeahead(Request) :-
 :- multifile
 	swish_config:source_alias/2.
 
-typeahead(predicates, Query, Template) :-
+typeahead(predicates, Query, Template, _) :-
 	swish_config(templates, Templates),
 	member(Template, Templates),
 	_{name:Name, arity:_} :< Template,
 	sub_atom(Name, 0, _, _, Query).
-typeahead(sources, Query, hit{alias:Alias, file:File, ext:Ext,
-			      query:Query, line:LineNo, text:Line}) :-
+typeahead(sources, Query, Hit, Options) :-
 	source_file(Path),
-	file_name_on_path(Path, Symbolic),
-	file_name_extension(_, Ext, Path),
-	Symbolic =.. [Alias,File],
-	once(swish_config:source_alias(Alias, _)),
-	limit(5, search_file(Path, Query, LineNo, Line)).
+	(   file_alias_path(Alias, Dir),
+	    once(swish_config:source_alias(Alias, _)),
+	    atom_concat(Dir, File, Path)
+	->  true
+	),
+	file_name_extension(Base, Ext, File),
+	(   sub_atom(File, 0, _, _, Query)
+	->  Hit = hit{alias:Alias, file:Base, ext:Ext, query:Query}
+	;   Hit = hit{alias:Alias, file:Base, ext:Ext,
+		      query:Query, line:LineNo, text:Line},
+	    limit(5, search_file(Path, Query, LineNo, Line, Options))
+	).
 typeahead(sources, Query, hit{alias:Alias, file:Base, ext:Ext,
-			      query:Query, line:LineNo, text:Line}) :-
-	swish_config:source_alias(Alias, Options),
-	option(search(Pattern), Options),
+			      query:Query, line:LineNo, text:Line}, Options) :-
+	swish_config:source_alias(Alias, AliasOptions),
+	option(search(Pattern), AliasOptions),
 	DirSpec =.. [Alias,.],
 	absolute_file_name(DirSpec, Dir,
 			   [ access(read),
@@ -143,16 +151,59 @@ typeahead(sources, Query, hit{alias:Alias, file:Base, ext:Ext,
 	\+ source_file(Path),		% already did this one above
 	atom_concat(DirSlash, File, Path),
 	file_name_extension(Base, Ext, File),
-	limit(5, search_file(Path, Query, LineNo, Line)).
+	limit(5, search_file(Path, Query, LineNo, Line, Options)).
 
-search_file(Path, Query, LineNo, Line) :-
+search_file(Path, Query, LineNo, Line, Options) :-
+	debug(swish(search), 'Searching ~q for ~q (~q)', [Path, Query, Options]),
 	setup_call_cleanup(
 	    open(Path, read, In),
 	    read_string(In, _, String),
 	    close(In)),
 	split_string(String, "\n", "\r", Lines),
 	nth1(LineNo, Lines, Line),
-	once(sub_string(Line, _, _, _, Query)).
+	match(Line, Query, Options).
+
+%%	match(+Line:string, +Query:string, +Options:dict) is semidet.
+%
+%	True if Line matches Query, respecting Options.
+
+match(Text, Query, Options) :-
+	sub_string(Text, Start, _, _, Query),
+	(   Options.get(match) == sow
+	->  sow(Text, Start), !
+	;   Options.get(match) == sol
+	->  !, Start == 0
+	;   !
+	).
+
+sow(_, 0) :- !.
+sow(Text, Offset) :-
+	Pre is Offset-1,
+	sub_atom(Text, Pre, 1, _, Before),
+	sub_atom(Text, Offset, 1, _, Start),
+	char_class(Start, Class),
+	\+ char_class(Before, Class).
+
+char_class(C, Class) :-
+	var(Class), !,
+	(   target_class(Class),
+	    char_type(C, Class)
+	->  true
+	;   Class = other
+	).
+char_class(C, Class) :-
+	(   target_class(Class)
+	->  char_type(C, Class)
+	;   \+ ( target_class(T),
+	         char_type(C, T)
+	       )
+	).
+
+target_class(lower).
+target_class(upper).
+target_class(digit).
+target_class(space).
+target_class(punct).
 
 %%	search(+Request)
 %
diff --git a/lib/swish/storage.pl b/lib/swish/storage.pl
index 1968e43..136dd56 100644
--- a/lib/swish/storage.pl
+++ b/lib/swish/storage.pl
@@ -47,6 +47,7 @@
 :- use_module(page).
 :- use_module(gitty).
 :- use_module(config).
+:- use_module(search).
 
 /** <module> Store files on behalve of web clients
 
@@ -58,7 +59,7 @@ their own version.
 
 :- setting(directory, atom, storage, 'The directory for storing files.').
 
-:- http_handler(swish(p), web_storage, [ id(web_storage), prefix ]).
+:- http_handler(swish('p/'), web_storage, [ id(web_storage), prefix ]).
 
 %%	web_storage(+Request) is det.
 %
@@ -154,8 +155,7 @@ storage(delete, Request) :-
 	reply_json_dict(true).
 
 request_file(Request, Dir, File) :-
-	option(path_info(PathInfo), Request),
-	atom_concat(/, File, PathInfo),
+	option(path_info(File), Request),
 	(   gitty_file(Dir, File, _Hash)
 	->  true
 	;   http_404([], Request)
@@ -256,8 +256,7 @@ storage_get(diff(RelTo), Dir, _, File, _Request) :-
 	reply_json_dict(Diff).
 
 request_file_or_hash(Request, Dir, FileOrHash, Type) :-
-	option(path_info(PathInfo), Request),
-	atom_concat(/, FileOrHash, PathInfo),
+	option(path_info(FileOrHash), Request),
 	(   gitty_file(Dir, FileOrHash, _Hash)
 	->  Type = file
 	;   is_sha1(FileOrHash)
@@ -340,7 +339,7 @@ storage_file(File, Data, Meta) :-
 		 *******************************/
 
 :- multifile
-	swish_search:typeahead/3.	% +Set, +Query, -Match
+	swish_search:typeahead/4.	% +Set, +Query, -Match, +Options
 
 %%	swish_search:typeahead(+Set, +Query, -Match) is nondet.
 %
@@ -355,7 +354,7 @@ storage_file(File, Data, Meta) :-
 %	@tbd caching?
 %	@tbd We should only demand public on public servers.
 
-swish_search:typeahead(file, Query, FileInfo) :-
+swish_search:typeahead(file, Query, FileInfo, _Options) :-
 	setting(directory, Dir),
 	gitty_file(Dir, File, Head),
 	gitty_commit(Dir, Head, Meta),
@@ -382,20 +381,20 @@ meta_match_query(Query, Meta) :-
 	    \+ char_type(C, csym)
 	).
 
-swish_search:typeahead(store_content, Query, FileInfo) :-
-	limit(25, search_store_content(Query, FileInfo)).
+swish_search:typeahead(store_content, Query, FileInfo, Options) :-
+	limit(25, search_store_content(Query, FileInfo, Options)).
 
-search_store_content(Query, FileInfo) :-
+search_store_content(Query, FileInfo, Options) :-
 	setting(directory, Dir),
 	gitty_file(Dir, File, Head),
 	gitty_data(Dir, Head, Data, Meta),
 	Meta.get(public) == true,
-	limit(5, search_file(File, Meta, Data, Query, FileInfo)).
+	limit(5, search_file(File, Meta, Data, Query, FileInfo, Options)).
 
-search_file(File, Meta, Data, Query, FileInfo) :-
+search_file(File, Meta, Data, Query, FileInfo, Options) :-
 	split_string(Data, "\n", "\r", Lines),
 	nth1(LineNo, Lines, Line),
-	once(sub_string(Line, _, _, _, Query)),
+	match(Line, Query, Options),
 	FileInfo = Meta.put(_{type:"store", file:File,
 			      line:LineNo, text:Line, query:Query
 			     }).
diff --git a/lib/swish/template_hint.pl b/lib/swish/template_hint.pl
index 25fdbf9..eba917e 100644
--- a/lib/swish/template_hint.pl
+++ b/lib/swish/template_hint.pl
@@ -251,7 +251,7 @@ man_predicate_info(PI, Name-Value) :-
 	(   atom_string(PName, PString),
 	    Name-Value = name-PString
 	;   Name-Value = arity-Arity
-	;   Name-Value = mode-ModeLine
+	;   Name-Value = (mode)-ModeLine
 	;   once(catch(predicate(PName, Arity, Summary, _, _), _, fail)),
 	    Name-Value = summary-Summary
 	;   predicate_property(system:PHead, iso),
@@ -285,7 +285,7 @@ pldoc_predicate_info(PI, Name-Value) :-
 	(   atom_string(PName, PString),
 	    Name-Value = name-PString
 	;   Name-Value = arity-Arity
-	;   Name-Value = mode-ModeLine
+	;   Name-Value = (mode)-ModeLine
 	;   Name-Value = summary-Summary
 	;   Det \== unknown,
 	    Name-Value = determinism-Det