swish/commit
New upstream files
author | Jan Wielemaker |
---|---|
Tue Jul 24 14:13:26 2018 +0200 | |
committer | Jan Wielemaker |
Tue Jul 24 14:13:26 2018 +0200 | |
commit | 3223dc1f108cbb51fd018b7b9498cb28784bfb30 |
tree | 336524a938cfae112891b4fa0aeaa13e656df0c1 |
parent | cee4be58b96cd6b5c5cf37c9703074b1bb54678b |
Diff style: patch stat
diff --git a/examples/htmlcell.swinb b/examples/htmlcell.swinb index 77178d4..6fc397e 100644 --- a/examples/htmlcell.swinb +++ b/examples/htmlcell.swinb @@ -1,6 +1,6 @@ <div class="notebook"> -<div class="nb-cell html"> +<div class="nb-cell html" name="htm1"> <h2>Using HTML cells in SWISH notebooks</h2> <p> @@ -17,12 +17,24 @@ properties: </p> +<style> + dl.htmlcell-doc dt { text-align: left; } + dt.htmlcell-doc-title { color: blue; margin-top: 5px;} +</style> <div class="list-group"> - <dl class="dl-horizontal"> - <dt>.cell()</dt><dd>Returns a jQuery object pointing to the HTML cell + <dl class="dl-horizontal htmlcell-doc"> + <dt class="htmlcell-doc-title">Finding objects</dt><dd> + </dd><dt>.cell([name])</dt><dd>Returns a jQuery object pointing to the named + or current HTML cell. </dd><dt>.notebook()</dt><dd>Returns a jQuery object of the entire notebook </dd><dt>.$(selector)</dt><dd>Returns a jQuery object holding all DOM elements matching <var>selector</var> in the current HTML cell. + </dd><dt class="htmlcell-doc-title">Running queries</dt><dd> + </dd><dt>.bindQuery([query,] f)</dt><dd>Bind the play button of the query named <var>query</var> to run the + function <var>f</var>. If <var>query</var> is omitted, bind to the first query below this cell. + The function is called with a single argument that provides a method + <code>run(bindings)</code>, where <var>bindings</var> is an object where the keys are the names + of Prolog variables and the values are the values for these variables. </dd><dt>.run(query, parameters)</dt><dd>Run the named query cell. <var>Parameters</var> is an object binding Prolog variables in the query to specified values. </dd><dt>.swish(options)</dt><dd>Wrapper around <code>new Pengine()</code> that fetches the sources @@ -32,6 +44,11 @@ jQuery selector), calls <code>options.predicate</code> with a single argument that is a dict that contains the fields of the form. On success, <code>options.onsuccess</code> is called. If an error occurs, this is displayed. + </dd><dt class="htmlcell-doc-title">Miscelleneous</dt><dd> + </dd><dt>.hideQuery(q[, on])</dt><dd>Hide the query editor and buttons for the query named <var>q</var> if + <var>on</var> is <code>true</code> (default). + </dd><dt>.loadCSS(url)</dt><dd>Load a CSS style sheet from <code>url</code> if this was not + already loaded. </dd></dl> </div> @@ -47,7 +64,9 @@ dynamically from the example sentences defined in the Prolog program at the end of the page. </p> +</div> +<div class="nb-cell html" name="htm2"> <div class="panel panel-default"> <div class="panel-body"> <div class="form-group"> @@ -60,9 +79,6 @@ </ul> </div> <input class="form-control"> - <div class="input-group-btn"> - <button type="button" class="btn btn-primary">Parse</button> - </div> </div> </div> </div> @@ -95,13 +111,12 @@ notebook.$("input").val($(this).text()); }); - // If the "Parse" button is clicked, run the query named "parse" + // If the play button of the "parse" query is clicked, run the query, // binding Sentence to the input string. The function - // notebook.run() takes the name of a query and an object - // holding bindings. This is translated to run the query - // Sentence = (String), (parse(Sentence, Tree)). - notebook.$(".btn-primary").on("click", function() { - notebook.run("parse", {Sentence: notebook.$("input").val()}); + // notebook.bindQuery() takes the name of a query and a + // function that collects the arguments and calls the query. + notebook.bindQuery("parse", function(q) { + q.run({Sentence: notebook.$("input").val()}); }); </script> </div> @@ -110,7 +125,7 @@ parse(Sentence, Tree). </div> -<div class="nb-cell markdown"> +<div class="nb-cell markdown" name="md1"> ### The programs Below are three program fragments. All three are declared as _background_ programs, so they are available to all queries posted from this notebook. They specify @@ -122,7 +137,7 @@ Below are three program fragments. All three are declared as _background_ progr You can change the grammar as well as the example sentences and see the immediate effect. </div> -<div class="nb-cell program" data-background="true"> +<div class="nb-cell program" data-background="true" name="p1"> % A simple English DCG grammar % ============================ @@ -153,13 +168,13 @@ v(v(saw), _) --> [saw]. p(p(with)) --> [with]. </div> -<div class="nb-cell program" data-background="true" data-singleline="true"> +<div class="nb-cell program" data-background="true" data-singleline="true" name="p2"> example("john sees a man"). example("a man sees john"). example("john sees a man with a telescope"). </div> -<div class="nb-cell program" data-background="true" data-singleline="true"> +<div class="nb-cell program" data-background="true" data-singleline="true" name="p3"> :- use_rendering(svgtree, [list(false)]). parse(Sentence, Tree) :- diff --git a/examples/stats.swinb b/examples/stats.swinb index cf35ce3..b018049 100644 --- a/examples/stats.swinb +++ b/examples/stats.swinb @@ -1,6 +1,6 @@ <div class="notebook"> -<div class="nb-cell markdown"> +<div class="nb-cell markdown" name="md1"> # Display SWISH server statistics This page examines the performance and health of the SWISH server. Most of the statistics are gathered by `lib/swish_debug`, which is by default loaded into http://swish.swi-prolog.org but must be explicitly loaded into your own SWISH server. Part of the statistics are based on reading the Linux =|/proc|= file system and thus only function on Linux. @@ -8,18 +8,18 @@ This page examines the performance and health of the SWISH server. Most of the The first step is easy, showing the overall statistics of the server. </div> -<div class="nb-cell query"> +<div class="nb-cell query" name="q3"> statistics. </div> -<div class="nb-cell markdown"> +<div class="nb-cell markdown" name="md2"> ## Historical performance statistics The charts below render historical performance characteristics of the server. Please open the program below for a description of chart/3. </div> -<div class="nb-cell program" data-singleline="true"> +<div class="nb-cell program" data-singleline="true" name="p1"> :- use_rendering(c3). %% chart(+Period, +Keys, -Chart) is det. @@ -44,7 +44,8 @@ program below for a description of chart/3. % high if user applications use the dynamic database. % - `rss_mb`, `stack_mb`, `heap_mb` are the above divided by 1024^2. -chart(Period, Keys, Chart) :- +chart(PeriodS, Keys, Chart) :- + atom_string(Period, PeriodS), swish_stats(Period, Dicts0), maplist(add_heap_mb, Dicts0, Dicts1), maplist(rss_mb, Dicts1, Dicts2), @@ -99,38 +100,110 @@ fix_date(Stat0, Stat) :- put_dict(time, Stat0, Time, Stat). </div> -<div class="nb-cell markdown"> -### Number of Pengines and CPU load over the past hour - -The number of Pegines denotes the number of actively executing queries. These queries may be sleeping while waiting for input, a debugger command or the user asking for more answers. Note that the number of Pengines is sampled and short-lived Pengines does not appear in this chart. -</div> - -<div class="nb-cell query"> -chart(hour, [pengines,d_cpu], Chart). -</div> - -<div class="nb-cell markdown"> -### Number of threads and visitors - -Threads are used as HTTP workers, pengines and some adminstrative tasks. Visitors is the number of open websockets, which reflects the number of browser windows watching this page. -</div> - -<div class="nb-cell query"> -chart(hour, [pengines,threads,visitors], Chart). -</div> - -<div class="nb-cell markdown"> -### Memory usage over the past hour - -*rss* is the total (_resident_) memory usage as reported by Linux. *stack* is the memory occupied by all Prolog stacks. -*heap* is an approximation of the memory used for the Prolog program space, computed as _rss_ - _stack_ - _free_. This is incorrect for two reasons. It ignores the C-stacks and the not-yet-committed memory of the Prolog stacks is not part of *rss*. *free* is memory that is freed but not yet reused as reported by GNU =|malinfo()|= as `fordblks`. -</div> - -<div class="nb-cell query"> -chart(hour, [rss_mb,heap_mb,stack_mb,free_mb], Chart). -</div> - -<div class="nb-cell markdown"> +<div class="nb-cell html" name="htm1"> +<div class="panel panel-default"> + <div class="panel-heading"> + <label>Number of Pengines and CPU load over the past period</label> + </div> + <p>The number of Pegines denotes the number of actively executing queries. + These queries may be sleeping while waiting for input, a debugger command + or the user asking for more answers. Note that the number of Pengines is + sampled and short-lived Pengines does not appear in this chart. + </p><div class="panel-body"> + <div class="form-group row" style="margin-bottom:0px"> + <label class="col-sm-2">Period:</label> + <div class="col-sm-10"> + <label class="radio-inline"><input name="period1" value="week" type="radio">Week</label> + <label class="radio-inline"><input name="period1" value="day" type="radio">Day</label> + <label class="radio-inline"><input name="period1" value="hour" checked="" type="radio">Hour</label> + <label class="radio-inline"><input name="period1" value="minute" type="radio">Minute</label> + </div> + </div> + </div> +</div> + +<script> + notebook.bindQuery(function(q) { + q.run({ Period: notebook.$('input[type=radio]:checked').val() }); + }); +</script> +</div> + +<div class="nb-cell query" name="cpu"> +projection([Chart]), +chart(Period, [pengines,d_cpu], Chart). +</div> + +<div class="nb-cell html" name="htm2"> +<div class="panel panel-default"> + <div class="panel-heading"> + <label>Number of threads and visitors</label> + </div> + <p>Threads are used as HTTP workers, pengines and some adminstrative tasks. + Visitors is the number of open websockets, which reflects the number of browser + windows watching this page. + </p><div class="panel-body"> + <div class="form-group row" style="margin-bottom:0px"> + <label class="col-sm-2">Period:</label> + <div class="col-sm-10"> + <label class="radio-inline"><input name="period2" value="week" type="radio">Week</label> + <label class="radio-inline"><input name="period2" value="day" type="radio">Day</label> + <label class="radio-inline"><input name="period2" value="hour" checked="" type="radio">Hour</label> + <label class="radio-inline"><input name="period2" value="minute" type="radio">Minute</label> + </div> + </div> + </div> +</div> + +<script> + notebook.bindQuery(function(q) { + q.run({ Period: notebook.$('input[type=radio]:checked').val() }); + }); +</script> +</div> + +<div class="nb-cell query" name="visitors"> +projection([Chart]), +chart(Period, [pengines,threads,visitors], Chart). +</div> + +<div class="nb-cell html" name="htm3"> +<div class="panel panel-default"> + <div class="panel-heading"> + <label>Memory usage over the past hour</label> + </div> + <p><b>rss</b> is the total (resident) memory usage as reported by Linux. <b>stack</b> is the memory + occupied by all Prolog stacks. <b>heap</b> is an approximation of the memory used for the + Prolog program space, computed as <i>rss - stack - free<i>. This is incorrect for two reasons. + It ignores the C-stacks and the not-yet-committed memory of the Prolog stacks + is not part of rss. free is memory that is freed but not yet reused as reported + by GNU <a href="https://www.gnu.org/software/libc/manual/html_node/Statistics-of-Malloc.html">malinfo()</a> as <code>fordblks</code>. + </i></i></p><div class="panel-body"><i><i> + <div class="form-group row" style="margin-bottom:0px"> + <label class="col-sm-2">Period:</label> + <div class="col-sm-10"> + <label class="radio-inline"><input name="period3" value="week" type="radio">Week</label> + <label class="radio-inline"><input name="period3" value="day" type="radio">Day</label> + <label class="radio-inline"><input name="period3" value="hour" checked="" type="radio">Hour</label> + <label class="radio-inline"><input name="period3" value="minute" type="radio">Minute</label> + </div> + </div> + </i></i></div><i><i> +</i></i></div><i><i> + +<script> + notebook.bindQuery(function(q) { + q.run({ Period: notebook.$('input[type=radio]:checked').val() }); + }); +</script></i></i> +</div> + +<div class="nb-cell query" name="q6"> +projection([Chart]), +chart(Period, [rss_mb,heap_mb,stack_mb,free_mb], Chart). +</div> + +<div class="nb-cell markdown" name="md6"> ## Health statistics The statistics below assesses the number of *Pengines* (actively executing queries from users) and the *highlight states*, the number of server-side mirrors we have from client's source code used to compute the semantically enriched tokens. If such states are not explicitly invalidated by the client, they are removed after having not been accessed for one hour. The *stale modules* count refers to temporary modules that are not associated to a Pengine, nor to a highlight state and probably indicate a leak. @@ -138,7 +211,7 @@ The statistics below assesses the number of *Pengines* (actively executing queri The two queries below extract information about stale modules and threads that have died. These are used to help debugging related leaks. </div> -<div class="nb-cell program" data-singleline="true"> +<div class="nb-cell program" data-singleline="true" name="p2"> :- use_rendering(table). stats([stale_modules-Stale|Pairs]) :- @@ -148,7 +221,7 @@ stats([stale_modules-Stale|Pairs]) :- Pairs). </div> -<div class="nb-cell query"> +<div class="nb-cell query" name="q7"> stats(Stats). </div> diff --git a/examples/swish_tutorials.swinb b/examples/swish_tutorials.swinb index 185a4d1..ab59aaf 100644 --- a/examples/swish_tutorials.swinb +++ b/examples/swish_tutorials.swinb @@ -6,7 +6,10 @@ This notebook provides an overview of tutorials about using SWISH. - [Rendering answers graphically](example/rendering.swinb) - - [Using HTML cells in notebooks](example/htmlcell.swinb) + - Using HTML cells in notebooks + - [The basics](example/htmlcell.swinb) + - [Including user widgets (slider)](example/slider.swinb) + - [Hiding all SWISH elements](example/chat80.swinb) - [Accessing external data](example/data_source.swinb) - [Access the SWISH interface from Prolog](example/jquery.swinb) </div> diff --git a/lib/swish/chat.pl b/lib/swish/chat.pl index 1a54fe7..16d6e8c 100644 --- a/lib/swish/chat.pl +++ b/lib/swish/chat.pl @@ -152,7 +152,7 @@ extend_options([_|T0], Options, T) :- % See whether the client associated with a session is flooding us % and if so, return a resource error. -check_flooding(_0Session) :- +check_flooding(Session) :- get_time(Now), ( http_session_retract(websocket(Score, Last)) -> Passed is Now-Last, @@ -161,11 +161,13 @@ check_flooding(_0Session) :- Passed = 0 ), debug(chat(flooding), 'Flooding score: ~2f (session ~p)', - [NewScore, _0Session]), + [NewScore, Session]), http_session_assert(websocket(NewScore, Now)), ( NewScore > 50 -> throw(http_reply(resource_error( - websocket(reconnect(Passed, NewScore))))) + error(permission_error(reconnect, websocket, + Session), + websocket(reconnect(Passed, NewScore)))))) ; true ). @@ -387,10 +389,19 @@ do_gc_visitors :- reclaim_visitor(WSID) :- debug(chat(gc), 'Reclaiming idle ~p', [WSID]), - retractall(visitor_session(WSID, _Session, _Token)), + reclaim_visitor_session(WSID), retractall(visitor_status(WSID, _Status)), unsubscribe(WSID, _). +reclaim_visitor_session(WSID) :- + forall(retract(visitor_session(WSID, Session, _Token)), + http_session_retractall(websocket(_, _), Session)). + +:- if(\+current_predicate(http_session_retractall/2)). +http_session_retractall(Data, Session) :- + retractall(http_session:session_data(Session, Data)). +:- endif. + %% create_session_user(+Session, -User, -UserData, +Options) % @@ -747,6 +758,11 @@ avatar_property(_Avatar, Source, avatar_source, Source). % HTTP handler for Noble Avatar images. Using create_avatar/2 % re-creates avatars from the file name, so we can safely discard % the avatar file store. +% +% Not really. A new user gets a new avatar and this is based on +% whether or not the file exists. Probably we should maintain a db +% of handed out avatars and their last-use time stamp. How to do +% that? Current swish stats: 400K avatars, 3.2Gb data. reply_avatar(Request) :- option(path_info(Local), Request), @@ -1316,8 +1332,8 @@ broadcast_bell(_Options) --> *******************************/ :- multifile - prolog:message//1. + prolog:message_context//1. -prolog:message(websocket(reconnect(Passed, Score))) --> +prolog:message_context(websocket(reconnect(Passed, Score))) --> [ 'WebSocket: too frequent reconnect requests (~1f sec; score = ~1f)'- [Passed, Score] ]. diff --git a/lib/swish/config.pl b/lib/swish/config.pl index 4799d63..39f402f 100644 --- a/lib/swish/config.pl +++ b/lib/swish/config.pl @@ -3,7 +3,8 @@ Author: Jan Wielemaker E-mail: J.Wielemaker@vu.nl WWW: http://www.swi-prolog.org - Copyright (c) 2014-2016, VU University Amsterdam + Copyright (c) 2014-2018, VU University Amsterdam + CWI, Amsterdam All rights reserved. Redistribution and use in source and binary forms, with or without @@ -38,12 +39,15 @@ swish_config_hash/2 % -HASH, +Options ]). :- use_module(library(http/http_dispatch)). +:- use_module(library(http/http_path)). :- use_module(library(http/http_json)). :- use_module(library(option)). +:- use_module(library(apply)). :- multifile config/2, % ?Key, ?Value config/3, % ?Key, ?Value, +Options + web_plugin/1, % ?Dict source_alias/2, % ?Alias, ?Options authenticate/2, % +Request, -User login_item/2, % -Server, -HTML_DOM @@ -78,10 +82,12 @@ swish_config_hash(Hash, Options) :- json_config(json{ http: json{ locations:JSON }, - swish: SWISHConfig + swish: SWISHConfig, + plugins : Plugins }, Options) :- http_locations(JSON), - swish_config_dict(SWISHConfig, Options). + swish_config_dict(SWISHConfig, Options), + web_plugins(Plugins, Options). http_locations(JSON) :- findall(ID-Path, @@ -116,7 +122,53 @@ same_ids(T, _, T, []). swish_config_dict(Config, Options) :- findall(Key-Value, swish_config(Key, Value, Options), Pairs), - dict_pairs(Config, json, Pairs). + keysort(Pairs, Sorted), + warn_duplicate_config(Sorted, Unique), + dict_pairs(Config, json, Unique). + +:- dynamic warned_duplicate/1. +:- volatile warned_duplicate/1. + +warn_duplicate_config([], []). +warn_duplicate_config([K-V1,K-V2|T0], [K-V1|T]) :- !, + collect_same(K, T0, VL, T1), + ( warned_duplicate(K) + -> true + ; print_message(warning, swish(duplicate_config(K, [V1,V2|VL]))), + assertz(warned_duplicate(K)) + ), + warn_duplicate_config(T1, T). +warn_duplicate_config([KV|T0], [KV|T]) :- !, + warn_duplicate_config(T0, T). + +collect_same(K, [K-V|T0], [V|VT], T) :- !, + collect_same(K, T0, VT, T). +collect_same(_, List, [], List). + +%! web_plugins(-Plugins, +Options) is det. +% +% Obtain a list of JSON dicts for additional web plugins. + +web_plugins(Plugins, _Options) :- + findall(Plugin, web_plugin_ex(Plugin), Plugins). + +web_plugin_ex(Plugin) :- + web_plugin(Plugin0), + dict_pairs(Plugin0, Tag, Pairs0), + maplist(expand_paths, Pairs0, Pairs), + dict_pairs(Plugin, Tag, Pairs). + +:- multifile http:location/3. +:- dynamic http:location/3. + +expand_paths(Name-Spec, Name-Path) :- + compound(Spec), + compound_name_arity(Spec, Alias, 1), + http:location(Alias, _, _), + !, + http_absolute_location(Spec, Path, []). +expand_paths(Pair, Pair). + %% config(-Key, -Value) is nondet. %% swish_config(-Key, -Value) is nondet. @@ -236,6 +288,10 @@ config(residuals_var, '_residuals'). prolog:message(http(duplicate_handlers(Id, Paths))) --> [ 'Duplicate HTTP handler IDs: "~w"'-[Id] ], paths(Paths). +prolog:message(swish(duplicate_config(K, [V0|List]))) --> + [ 'Duplicate SWISH config values for "~w": ~p. Using ~q'- + [K, [V0|List], V0] + ]. paths([]) --> []. paths([H|T]) --> [ '\t~q'-[H], nl ], paths(T). diff --git a/lib/swish/pack/wordnet/pack.pl b/lib/swish/pack/wordnet/pack.pl index abe8bf2..da01308 100644 --- a/lib/swish/pack/wordnet/pack.pl +++ b/lib/swish/pack/wordnet/pack.pl @@ -1,5 +1,5 @@ name(wordnet). -version('0.9.1'). +version('0.9.3'). title('Access to WordNet database'). keywords([wordnet, lexical, nlp]). author( 'Jan Wielemaker', 'jan@swi-prolog.org' ). diff --git a/lib/swish/pack/wordnet/prolog/wn.pl b/lib/swish/pack/wordnet/prolog/wn.pl index 4c3b88c..eb71597 100644 --- a/lib/swish/pack/wordnet/prolog/wn.pl +++ b/lib/swish/pack/wordnet/prolog/wn.pl @@ -84,11 +84,11 @@ Some more remarks: both adjective and adjective_satellite are represented as 3XXXXXXXX -@author Originally by Jan Wielemaker. Partly documented by an -unknown author. Current commens copied from prologdb.5WN.html -file from the sources. -@see Wordnet is a lexical database for the English language. See -http://www.cogsci.princeton.edu/~wn/ +@author Originally by Jan Wielemaker. Partly documented by Samer +Abdallah. Current comments copied from prologdb.5WN.html file from the +sources. +@see Wordnet is a lexical database for the English language. +See http://www.cogsci.princeton.edu/~wn/ */ @@ -418,22 +418,9 @@ load_wordnet :- load_op(Name) :- atom_concat('wn_', Name, File), - absolute_file_name(wndb(File), - [ access(read), - file_type(prolog) - ], - PlFile), - file_name_extension(Base, _Ext, PlFile), - file_name_extension(Base, qlf, QlfFile), - ( exists_file(QlfFile), - time_file(QlfFile, QlfTime), - time_file(PlFile, PlTime), - QlfTime >= PlTime - -> load_files(QlfFile) - ; access_file(QlfFile, write) - -> qcompile(PlFile) - ; load_files(PlFile) - ). + load_files(wndb(File), + [ qcompile(auto) + ]). /******************************* diff --git a/lib/swish/page.pl b/lib/swish/page.pl index f25ad8a..c8a21bd 100644 --- a/lib/swish/page.pl +++ b/lib/swish/page.pl @@ -3,7 +3,8 @@ Author: Jan Wielemaker E-mail: J.Wielemaker@vu.nl WWW: http://www.swi-prolog.org - Copyright (c) 2014-2017, VU University Amsterdam + Copyright (c) 2014-2018, VU University Amsterdam + CWI, Amsterdam All rights reserved. Redistribution and use in source and binary forms, with or without @@ -87,6 +88,7 @@ http:location(pldoc, swish(pldoc), [priority(100)]). :- multifile swish_config:logo//1, + swish_config:title//1, swish_config:source_alias/2, swish_config:reply_page/1, swish_config:li_login_button//1. @@ -158,14 +160,7 @@ swish_reply3(_, Options) :- swish_reply3(_, Options) :- reply_html_page( swish(main), - [ 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_title(Options), \swish_page(Options)). params_options([], []). @@ -389,6 +384,33 @@ collapsed_button --> span(class('icon-bar'), []) ])). + + /******************************* + * BRANDING * + *******************************/ + +%! swish_title(+Options)// is det. +% +% Emit the HTML header options dealing with the title and shortcut +% icons. This can be hooked using swish_config:title//1. + +swish_title(Options) --> + swish_config:title(Options), !. +swish_title(_Options) --> + html([ 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_logos(+Options)// is det. +% +% Emit the navbar branding logos at the top-left. Can be hooked +% using swish_config:swish_logos//1. + swish_logos(Options) --> swish_config:logo(Options), !. swish_logos(Options) --> @@ -399,7 +421,8 @@ swish_logos(Options) --> % % Hook to include the top-left logos. The default calls % pengine_logo//1 and swish_logo//1. The implementation should -% emit zero or more <a> elements. +% emit zero or more <a> elements. See +% `config_available/branding.pl` for an example. %! pengine_logo(+Options)// is det. %! swish_logo(+Options)// is det. @@ -418,6 +441,10 @@ swish_logo(_Options) --> html(a([href(HREF), class('swish-logo')], &(nbsp))). + /******************************* + * CONTENT * + *******************************/ + %% swish_content(+Options)// % % Generate the SWISH editor, Prolog output area and query editor. diff --git a/lib/swish/paths.pl b/lib/swish/paths.pl index 2d3364b..e7559c9 100644 --- a/lib/swish/paths.pl +++ b/lib/swish/paths.pl @@ -52,9 +52,12 @@ user:file_search_path(config, config_enabled(.)). user:file_search_path(config, swish('config-available')). user:file_search_path(swish_web, swish(web)). user:file_search_path(swish_pack, swish(pack)). +user:file_search_path(js, config('web/js')). user:file_search_path(js, swish_web(js)). user:file_search_path(css, swish_web(css)). +user:file_search_path(icons, config('web/icons')). user:file_search_path(icons, swish_web(icons)). +user:file_search_path(plugin, config('web/plugin')). %! set_swish_path % diff --git a/lib/swish/plugin/notify.pl b/lib/swish/plugin/notify.pl index e8ab9ee..10aa14e 100644 --- a/lib/swish/plugin/notify.pl +++ b/lib/swish/plugin/notify.pl @@ -300,11 +300,11 @@ notify_event(follow(DocID, ProfileID, Options)) :- follow(DocID, ProfileID, Options). % events on gitty files notify_event(updated(File, Commit)) :- - ( storage_meta_data(Commit.get(previous), OldCommit), - atom_concat('gitty:', OldCommit.name, DocID) - -> notify(DocID, forked(OldCommit, Commit)) + storage_meta_data(Commit.get(previous), OldCommit), + ( atom_concat('gitty:', OldCommit.name, DocID) + -> notify(DocID, updated(Commit)) ; atom_concat('gitty:', File, DocID), - notify(DocID, updated(Commit)) + notify(DocID, forked(OldCommit, Commit)) ). notify_event(deleted(File, Commit)) :- atom_concat('gitty:', File, DocID), diff --git a/lib/swish/swish_csv.pl b/lib/swish/swish_csv.pl index 89ca341..a2db2c9 100644 --- a/lib/swish/swish_csv.pl +++ b/lib/swish/swish_csv.pl @@ -113,6 +113,7 @@ success(Answers, VarTerm, Options) :- csv_write_stream(current_output, Page, [])). projection_row(-) :- !. +projection_row(row) :- !. projection_row(VarTerm) :- csv_write_stream(current_output, [VarTerm], []). diff --git a/web/bower_components/codemirror/mode/javascript/javascript.js b/web/bower_components/codemirror/mode/javascript/javascript.js index c4a709c..ba27c5d 100644 --- a/web/bower_components/codemirror/mode/javascript/javascript.js +++ b/web/bower_components/codemirror/mode/javascript/javascript.js @@ -75,17 +75,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return ret(ch); } else if (ch == "=" && stream.eat(">")) { return ret("=>", "operator"); - } else if (ch == "0" && stream.eat(/x/i)) { - stream.eatWhile(/[\da-f]/i); - return ret("number", "number"); - } else if (ch == "0" && stream.eat(/o/i)) { - stream.eatWhile(/[0-7]/i); - return ret("number", "number"); - } else if (ch == "0" && stream.eat(/b/i)) { - stream.eatWhile(/[01]/i); + } else if (ch == "0" && stream.match(/^(?:x[\da-f]+|o[0-7]+|b[01]+)n?/i)) { return ret("number", "number"); } else if (/\d/.test(ch)) { - stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); + stream.match(/^\d*(?:n|(?:\.\d*)?(?:[eE][+\-]?\d+)?)?/); return ret("number", "number"); } else if (ch == "/") { if (stream.eat("*")) { @@ -96,7 +89,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return ret("comment", "comment"); } else if (expressionAllowed(stream, state, 1)) { readRegexp(stream); - stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); + stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/); return ret("regexp", "string-2"); } else { stream.eat("="); @@ -265,21 +258,42 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { pass.apply(null, arguments); return true; } + function inList(name, list) { + for (var v = list; v; v = v.next) if (v.name == name) return true + return false; + } function register(varname) { - function inList(list) { - for (var v = list; v; v = v.next) - if (v.name == varname) return true; - return false; - } var state = cx.state; cx.marked = "def"; if (state.context) { - if (inList(state.localVars)) return; - state.localVars = {name: varname, next: state.localVars}; + if (state.lexical.info == "var" && state.context && state.context.block) { + // FIXME function decls are also not block scoped + var newContext = registerVarScoped(varname, state.context) + if (newContext != null) { + state.context = newContext + return + } + } else if (!inList(varname, state.localVars)) { + state.localVars = new Var(varname, state.localVars) + return + } + } + // Fall through means this is global + if (parserConfig.globalVars && !inList(varname, state.globalVars)) + state.globalVars = new Var(varname, state.globalVars) + } + function registerVarScoped(varname, context) { + if (!context) { + return null + } else if (context.block) { + var inner = registerVarScoped(varname, context.prev) + if (!inner) return null + if (inner == context.prev) return context + return new Context(inner, context.vars, true) + } else if (inList(varname, context.vars)) { + return context } else { - if (inList(state.globalVars)) return; - if (parserConfig.globalVars) - state.globalVars = {name: varname, next: state.globalVars}; + return new Context(context.prev, new Var(varname, context.vars), false) } } @@ -289,15 +303,23 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { // Combinators - var defaultVars = {name: "this", next: {name: "arguments"}}; + function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block } + function Var(name, next) { this.name = name; this.next = next } + + var defaultVars = new Var("this", new Var("arguments", null)) function pushcontext() { - cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; - cx.state.localVars = defaultVars; + cx.state.context = new Context(cx.state.context, cx.state.localVars, false) + cx.state.localVars = defaultVars + } + function pushblockcontext() { + cx.state.context = new Context(cx.state.context, cx.state.localVars, true) + cx.state.localVars = null } function popcontext() { - cx.state.localVars = cx.state.context.vars; - cx.state.context = cx.state.context.prev; + cx.state.localVars = cx.state.context.vars + cx.state.context = cx.state.context.prev } + popcontext.lex = true function pushlex(type, info) { var result = function() { var state = cx.state, indent = state.indented; @@ -322,19 +344,19 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function expect(wanted) { function exp(type) { if (type == wanted) return cont(); - else if (wanted == ";") return pass(); + else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass(); else return cont(exp); }; return exp; } function statement(type, value) { - if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); + if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex); if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex); if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex); if (type == "debugger") return cont(expect(";")); - if (type == "{") return cont(pushlex("}"), block, poplex); + if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext); if (type == ";") return cont(); if (type == "if") { if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) @@ -363,18 +385,20 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return cont(pushlex("stat"), maybelabel); } } - if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), - block, poplex, poplex); + if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext, + block, poplex, poplex, popcontext); if (type == "case") return cont(expression, expect(":")); if (type == "default") return cont(expect(":")); - if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), - statement, poplex, popcontext); + if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext); if (type == "export") return cont(pushlex("stat"), afterExport, poplex); if (type == "import") return cont(pushlex("stat"), afterImport, poplex); if (type == "async") return cont(statement) if (value == "@") return cont(expression, statement) return pass(pushlex("stat"), expression, expect(";"), poplex); } + function maybeCatchBinding(type) { + if (type == "(") return cont(funarg, expect(")")) + } function expression(type, value) { return expressionInner(type, value, false); } @@ -783,7 +807,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { cc: [], lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), localVars: parserConfig.localVars, - context: parserConfig.localVars && {vars: parserConfig.localVars}, + context: parserConfig.localVars && new Context(null, null, false), indented: basecolumn || 0 }; if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") @@ -824,7 +848,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { lexical = lexical.prev; var type = lexical.type, closing = firstChar == type; - if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); + if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0); else if (type == "form" && firstChar == "{") return lexical.indented; else if (type == "form") return lexical.indented + indentUnit; else if (type == "stat")