swish/commit
Copied upstream files
author | Jan Wielemaker |
---|---|
Sun Dec 4 11:22:49 2016 +0100 | |
committer | Jan Wielemaker |
Sun Dec 4 11:22:49 2016 +0100 | |
commit | 56f6f5bb27d80d5febd474c51c83aa799ee8346b |
tree | bd2d48677867ccf96f86e5e81505472913feb42a |
parent | 24b9a02d860741e8d8f7d9eabe8390bd7ddd5de1 |
Diff style: patch stat
diff --git a/lib/swish/examples.pl b/lib/swish/examples.pl index ffbd464..6f3ed90 100644 --- a/lib/swish/examples.pl +++ b/lib/swish/examples.pl @@ -106,7 +106,7 @@ index_json(HREF, Dir, JSON) :- read_file_to_json(File, JSON0), maplist(add_href(HREF), JSON0, JSON). index_json(HREF, Dir, JSON) :- - string_concat(Dir, "/*.pl", Pattern), + string_concat(Dir, "/*.{pl,swinb}", Pattern), expand_file_name(Pattern, Files), maplist(ex_file_json(HREF), Files, JSON). diff --git a/lib/swish/gitty.pl b/lib/swish/gitty.pl index 07dba5d..bf8c29d 100644 --- a/lib/swish/gitty.pl +++ b/lib/swish/gitty.pl @@ -433,7 +433,7 @@ set_head(Store, File, Head) :- * DIFF * *******************************/ -%% gitty_diff(+Store, ?Hash1, +FileOrHash2, -Dict) is det. +%% gitty_diff(+Store, ?Hash1, +FileOrHash2OrData, -Dict) is det. % % True if Dict representeds the changes in Hash1 to FileOrHash2. % If Hash1 is unbound, it is unified with the `previous` of @@ -448,7 +448,19 @@ set_head(Store, File, Head) :- % data. Only present of data has changed % - tags:_{added:AddedTags, deleted:DeletedTags} % If tags have changed, the added and deleted ones. +% +% @arg FileOrHash2OrData is a file name, hash or a term +% data(String) to compare a given string with a +% gitty version. +gitty_diff(Store, C1, data(Data2), Dict) :- !, + must_be(atom, C1), + gitty_data(Store, C1, Data1, _Meta1), + ( Data1 \== Data2 + -> udiff_string(Data1, Data2, UDIFF), + Dict = json{data:UDIFF} + ; Dict = json{} + ). gitty_diff(Store, C1, C2, Dict) :- gitty_data(Store, C2, Data2, Meta2), ( var(C1) diff --git a/lib/swish/highlight.pl b/lib/swish/highlight.pl index 2dffcf9..778a185 100644 --- a/lib/swish/highlight.pl +++ b/lib/swish/highlight.pl @@ -96,18 +96,25 @@ tokens_. % the editor is not known. codemirror_change(Request) :- + call_cleanup(codemirror_change_(Request), + check_unlocked). + +codemirror_change_(Request) :- http_read_json_dict(Request, Change, []), debug(cm(change), 'Change ~p', [Change]), - UUID = Change.uuid, - ( shadow_editor(Change, TB) + atom_string(UUID, Change.uuid), + catch(shadow_editor(Change, TB), + cm(Reason), true), + ( var(Reason) -> ( catch(apply_change(TB, Changed, Change.change), cm(outofsync), fail) -> mark_changed(TB, Changed), + release_editor(UUID), reply_json_dict(true) ; destroy_editor(UUID), change_failed(UUID, outofsync) ) - ; change_failed(UUID, existence_error) + ; change_failed(UUID, Reason) ). change_failed(UUID, Reason) :- @@ -177,9 +184,15 @@ insert([H|T], TB, ChPos0, ChPos, Changed) :- insert(T, TB, ChPos2, ChPos, Changed). :- dynamic - current_editor/4, % UUID, MemFile, Role, Time - editor_last_access/2, % UUID, Time - xref_upto_data/1. % UUID + current_editor/5, % UUID, MemFile, Role, Lock, Time + editor_last_access/2, % UUID, Time + xref_upto_data/1. % UUID + +%% create_editor(+UUID, -Editor, +Change) is det. +% +% Create a new editor for source UUID from Change. The editor is +% created in a locked state and must be released using +% release_editor/1 before it can be publically used. create_editor(UUID, Editor, Change) :- must_be(atom, UUID), @@ -190,7 +203,16 @@ create_editor(UUID, Editor, Change) :- ; Role = source ), get_time(Now), - asserta(current_editor(UUID, Editor, Role, Now)). + mutex_create(Lock), + with_mutex(swish_create_editor, + register_editor(UUID, Editor, Role, Lock, Now)), !. +create_editor(UUID, Editor, _Change) :- + fetch_editor(UUID, Editor). + +register_editor(UUID, Editor, Role, Lock, Now) :- + \+ current_editor(UUID, _, _, _, _), + mutex_lock(Lock), + asserta(current_editor(UUID, Editor, Role, Lock, Now)). %% current_highlight_state(?UUID, -State) is nondet. % @@ -200,9 +222,10 @@ current_highlight_state(UUID, highlight{data:Editor, role:Role, created:Created, + lock:Lock, access:Access }) :- - current_editor(UUID, Editor, Role, Created), + current_editor(UUID, Editor, Role, Lock, Created), ( editor_last_access(Editor, Access) -> true ; Access = Created @@ -218,25 +241,28 @@ current_highlight_state(UUID, uuid_like(UUID) :- split_string(UUID, "-", "", Parts), maplist(string_length, Parts, [8,4,4,4,12]), - \+ current_editor(UUID, _, _, _). + \+ current_editor(UUID, _, _, _, _). %% destroy_editor(+UUID) % % Destroy source admin UUID: the shadow text (a memory file), the -% XREF data and the module used for cross-referencing. +% XREF data and the module used for cross-referencing. The editor +% must be acquired using fetch_editor/2 before it can be +% destroyed. destroy_editor(UUID) :- must_be(atom, UUID), + current_editor(UUID, Editor, _, Lock, _), !, + mutex_unlock(Lock), retractall(xref_upto_data(UUID)), retractall(editor_last_access(UUID, _)), - current_editor(UUID, Editor, _, _), !, - ( xref_source_id(Editor, SourceID) + ( xref_source_id(UUID, SourceID) -> xref_clean(SourceID), destroy_state_module(UUID) ; true ), - % destroy late to make xref_source_identifier/2 work. - retractall(current_editor(UUID, Editor, _, _)), + % destroy after xref_clean/1 to make xref_source_identifier/2 work. + retractall(current_editor(UUID, Editor, _, _, _)), free_memory_file(Editor). destroy_editor(_). @@ -272,69 +298,120 @@ gc_editors :- gc_editors :- editor_max_idle_time(MaxIdle), forall(garbage_editor(UUID, MaxIdle), - destroy_old_editor(UUID)). + destroy_garbage_editor(UUID)). garbage_editor(UUID, TimeOut) :- get_time(Now), - current_editor(UUID, _TB, _Role, Created), + current_editor(UUID, _TB, _Role, _Lock, Created), Now - Created > TimeOut, ( editor_last_access(UUID, Access) -> Now - Access > TimeOut ; true ). -destroy_old_editor(UUID) :- - with_mutex(swish_gc_editor, - destroy_old_editor_sync(UUID)). - -destroy_old_editor_sync(UUID) :- - editor_max_idle_time(MaxIdle), - garbage_editor(UUID, MaxIdle), !, - debug(cm(gc), 'GC highlight state for ~q', [UUID]), +destroy_garbage_editor(UUID) :- + fetch_editor(UUID, _TB), !, destroy_editor(UUID). -destroy_old_editor_sync(_). +destroy_garbage_editor(_). %% fetch_editor(+UUID, -MemFile) is semidet. % -% Fetch existing editor for source UUID. Make sure the last access -% time is updated to avoid concurrent GC of the editor. +% Fetch existing editor for source UUID. Update the last access +% time. After success, the editor is locked and must be released +% using release_editor/1. fetch_editor(UUID, TB) :- - with_mutex(swish_gc_editor, - ( current_editor(UUID, TB, _Role, _), - update_access(UUID) - )). + current_editor(UUID, TB, Role, Lock, _), + catch(mutex_lock(Lock), error(existence_error(mutex,_),_), fail), + debug(cm(lock), 'Locked ~p', [UUID]), + ( current_editor(UUID, TB, Role, Lock, _) + -> update_access(UUID) + ; mutex_unlock(Lock) + ). + +release_editor(UUID) :- + current_editor(UUID, _TB, _Role, Lock, _), + debug(cm(lock), 'Unlocked ~p', [UUID]), + mutex_unlock(Lock). + +check_unlocked :- + check_unlocked(unknown). + +check_unlocked(Reason) :- + thread_self(Me), + current_editor(_UUID, _TB, _Role, Lock, _), + mutex_property(Lock, status(locked(Me, _Count))), !, + print_message(error, locked(Reason, Me)), + assertion(fail). +check_unlocked(_). + +unlocked_editor(UUID) :- + thread_self(Me), + current_editor(UUID, _TB, _Role, Lock, _), + mutex_property(Lock, status(locked(Me, _Count))), !, + fail. +unlocked_editor(_). + +%% update_access(+UUID) +% +% Update the registered last access. We only update if the time is +% behind for more than a minute. update_access(UUID) :- get_time(Now), - retractall(editor_last_access(UUID, _)), - asserta(editor_last_access(UUID, Now)). + ( editor_last_access(UUID, Last), + Now-Last < 60 + -> true + ; retractall(editor_last_access(UUID, _)), + asserta(editor_last_access(UUID, Now)) + ). :- multifile prolog:xref_source_identifier/2, - prolog:xref_open_source/2. + prolog:xref_open_source/2, + prolog:xref_close_source/2. prolog:xref_source_identifier(UUID, UUID) :- - current_editor(UUID, _, _, _). + current_editor(UUID, _, _, _, _). + +%% prolog:xref_open_source(+UUID, -Stream) +% +% Open a source. As we cannot open the same source twice we must +% lock it. As of 7.3.32 this can be done through the +% prolog:xref_close_source/2 hook. In older versions we get no +% callback on the close, so we must leave the editor unlocked. +:- if(current_predicate(prolog_source:close_source/3)). prolog:xref_open_source(UUID, Stream) :- - current_editor(UUID, TB, _Role, _), !, + fetch_editor(UUID, TB), open_memory_file(TB, read, Stream). +prolog:xref_close_source(UUID, Stream) :- + release_editor(UUID), + close(Stream). +:- else. +prolog:xref_open_source(UUID, Stream) :- + fetch_editor(UUID, TB), + open_memory_file(TB, read, Stream), + release_editor(UUID). +:- endif. %% 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. +% POST handler that deals with destruction of our mirror +% associated with an editor, as well as the associated +% cross-reference information. codemirror_leave(Request) :- + call_cleanup(codemirror_leave_(Request), + check_unlocked). + +codemirror_leave_(Request) :- http_read_json_dict(Request, Data, []), ( atom_string(UUID, Data.get(uuid)) -> debug(cm(leave), 'Leaving editor ~p', [UUID]), - ( current_editor(UUID, _, _, _) - -> forall(current_editor(UUID, _TB, _Role, _), - with_mutex(swish_gc_editor, destroy_editor(UUID))) + ( fetch_editor(UUID, _TB) + -> destroy_editor(UUID) ; debug(cm(leave), 'No editor for ~p', [UUID]) ) ; debug(cm(leave), 'No editor?? (data=~p)', [Data]) @@ -347,7 +424,7 @@ codemirror_leave(Request) :- mark_changed(MemFile, Changed) :- ( Changed == true - -> current_editor(UUID, MemFile, _Role, _), + -> current_editor(UUID, MemFile, _Role, _, _), retractall(xref_upto_data(UUID)) ; true ). @@ -357,39 +434,31 @@ mark_changed(MemFile, Changed) :- xref(UUID) :- xref_upto_data(UUID), !. xref(UUID) :- - current_editor(UUID, MF, _Role, _), - xref_source_id(MF, SourceId), - xref_state_module(MF, Module), - xref_source(SourceId, - [ silent(true), - module(Module) - ]), - asserta(xref_upto_data(UUID)). - -%% 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, UUID) :- - current_editor(UUID, TB, _Role, _). - -%% xref_state_module(+TB, -Module) is semidet. + setup_call_cleanup( + fetch_editor(UUID, _TB), + ( xref_source_id(UUID, SourceId), + xref_state_module(UUID, Module), + xref_source(SourceId, + [ silent(true), + module(Module) + ]), + asserta(xref_upto_data(UUID)) + ), + release_editor(UUID)). + +%% xref_source_id(+Editor, -SourceID) is det. +% +% SourceID is the xref source identifier for Editor. As we are +% using UUIDs we just use the editor. + +xref_source_id(UUID, UUID). + +%% xref_state_module(+UUID, -Module) is semidet. % % True if we must run the cross-referencing in Module. We use a % temporary module based on the UUID of the source. -xref_state_module(TB, UUID) :- - current_editor(UUID, TB, _Role, _), +xref_state_module(UUID, UUID) :- ( module_property(UUID, class(temporary)) -> true ; set_module(UUID:class(temporary)), @@ -418,13 +487,23 @@ destroy_state_module(_). % editor. codemirror_tokens(Request) :- + setup_call_catcher_cleanup( + true, + codemirror_tokens_(Request), + Reason, + check_unlocked(Reason)). + +codemirror_tokens_(Request) :- http_read_json_dict(Request, Data, []), + atom_string(UUID, Data.get(uuid)), debug(cm(tokens), 'Asking for tokens: ~p', [Data]), ( catch(shadow_editor(Data, TB), cm(Reason), true) -> ( var(Reason) - -> enriched_tokens(TB, Data, Tokens), + -> call_cleanup(enriched_tokens(TB, Data, Tokens), + release_editor(UUID)), reply_json_dict(json{tokens:Tokens}, [width(0)]) - ; change_failed(Data.uuid, Reason) + ; check_unlocked(Reason), + change_failed(UUID, Reason) ) ; reply_json_dict(json{tokens:[[]]}) ), @@ -432,7 +511,7 @@ codemirror_tokens(Request) :- enriched_tokens(TB, _Data, Tokens) :- % source window - current_editor(UUID, TB, source, _), !, + current_editor(UUID, TB, source, _Lock, _), !, xref(UUID), server_tokens(TB, Tokens). enriched_tokens(TB, Data, Tokens) :- % query window @@ -468,7 +547,7 @@ json_source_id(String, SourceID) :- string_source_id(String, SourceID) :- atom_string(SourceID, String), ( fetch_editor(SourceID, _TB) - -> true + -> release_editor(SourceID) ; true ). @@ -500,9 +579,12 @@ shadow_editor(Data, TB) :- mark_changed(TB, true) ; Changes = Data.get(changes) -> ( debug(cm(change), 'Patch editor for ~p', [UUID]), - maplist(apply_change(TB, Changed), Changes) + catch(maplist(apply_change(TB, Changed), Changes), E, + (release_editor(UUID), throw(E))) -> true - ; throw(cm(out_of_sync)) + ; release_editor(UUID), + assertion(unlocked_editor(UUID)), + throw(cm(out_of_sync)) ), mark_changed(TB, Changed) ). @@ -527,9 +609,8 @@ shadow_editor(_Data, _TB) :- %% 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. +% displays the text the server thinks is in the client editor. 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. @@ -539,12 +620,12 @@ shadow_editor(_Data, _TB) :- server_tokens/1. show_mirror(Role) :- - current_editor(_UUID, TB, Role, _), !, + current_editor(_UUID, TB, Role, _Lock, _), !, memory_file_to_string(TB, String), write(user_error, String). server_tokens(Role) :- - current_editor(_UUID, TB, Role, _), !, + current_editor(_UUID, TB, Role, _Lock, _), !, enriched_tokens(TB, _{}, Tokens), print_term(Tokens, [output(user_error)]). @@ -554,7 +635,7 @@ server_tokens(Role) :- % represents the tokens found in a single toplevel term. server_tokens(TB, GroupedTokens) :- - current_editor(UUID, TB, _Role, _), + current_editor(UUID, TB, _Role, _Lock, _), setup_call_cleanup( open_memory_file(TB, read, Stream), ( set_stream_file(TB, Stream), @@ -1002,13 +1083,13 @@ predicate_info(Module:Name/Arity, Key, Value) :- functor(Head, Name, Arity), predicate_property(system:Head, iso), !, ignore(Module = system), - ( catch(predicate(Name, Arity, Summary, _, _), _, fail), + ( catch(once(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), !. + catch(once(predicate(Name, Arity, Summary, _, _)), _, fail), !. predicate_info(PI, summary, Summary) :- % PlDoc - prolog:predicate_summary(PI, Summary). + once(prolog:predicate_summary(PI, Summary)). diff --git a/lib/swish/page.pl b/lib/swish/page.pl index 72b481c..8900aab 100644 --- a/lib/swish/page.pl +++ b/lib/swish/page.pl @@ -343,7 +343,7 @@ swish_logo(_Options) --> % Add search box to the navigation bar search_form(Options) --> - html(div(class(['col-sm-3', 'col-md-3', 'pull-right']), + html(div(class(['pull-right']), \search_box(Options))). diff --git a/lib/swish/render/graphviz.pl b/lib/swish/render/graphviz.pl index c7da3f5..31dcdf1 100644 --- a/lib/swish/render/graphviz.pl +++ b/lib/swish/render/graphviz.pl @@ -478,7 +478,8 @@ attribute(html(Value), O) --> !, attribute(Name=html(Value), _, List, Tail) :- atomic(Value), !, format(codes(List,Tail), '~w=<~w>', [Name, Value]). -attribute(Name=html(Term), _, List, Tail) :- !, +attribute(Name=html(Term), _, List, Tail) :- + nonvar(Term), !, phrase(html(Term), Tokens0), delete(Tokens0, nl(_), Tokens), with_output_to(string(HTML), print_html(Tokens)), diff --git a/lib/swish/storage.pl b/lib/swish/storage.pl index 6400c94..549ede2 100644 --- a/lib/swish/storage.pl +++ b/lib/swish/storage.pl @@ -51,6 +51,7 @@ :- use_module(page). :- use_module(gitty). +:- use_module(patch). :- use_module(config). :- use_module(search). @@ -163,14 +164,19 @@ storage(put, Request) :- -> gitty_data(Dir, File, Data, _OldMeta) ; option(data(Data), Dict, "") ), - meta_data(Request, Dict, Meta), + meta_data(Request, Dir, Dict, Meta), storage_url(File, URL), - gitty_update(Dir, File, Data, Meta, Commit), - debug(storage, 'Updated: ~p', [Commit]), - reply_json_dict(json{url:URL, - file:File, - meta:Commit.put(symbolic, "HEAD") - }). + catch(gitty_update(Dir, File, Data, Meta, Commit), + Error, + true), + ( var(Error) + -> debug(storage, 'Updated: ~p', [Commit]), + reply_json_dict(json{url:URL, + file:File, + meta:Commit.put(symbolic, "HEAD") + }) + ; update_error(Error, Dir, Data, File, URL) + ). storage(delete, Request) :- authentity(Request, Meta), setting(directory, Dir), @@ -178,6 +184,39 @@ storage(delete, Request) :- gitty_update(Dir, File, "", Meta, _New), reply_json_dict(true). +%% update_error(+Error, +Storage, +Data, +File, +URL) +% +% If error signals an edit conflict, prepare an HTTP =|409 +% Conflict|= page + +update_error(error(gitty(commit_version(_, Head, Previous)), _), + Dir, Data, File, URL) :- !, + gitty_diff(Dir, Previous, Head, OtherEdit), + gitty_diff(Dir, Previous, data(Data), MyEdits), + Status0 = json{url:URL, + file:File, + error:edit_conflict, + edit:_{server:OtherEdit, + me:MyEdits} + }, + ( OtherDiff = OtherEdit.get(data) + -> PatchOptions = [status(_), stderr(_)], + patch(Data, OtherDiff, Merged, PatchOptions), + Status1 = Status0.put(merged, Merged), + foldl(patch_status, PatchOptions, Status1, Status) + ; Status = Status0 + ), + reply_json_dict(Status, [ status(409) ]). +update_error(Error, _Dir, _Data, _File, _URL) :- + throw(Error). + +patch_status(status(exit(0)), Dict, Dict) :- !. +patch_status(status(exit(Status)), Dict, Dict.put(patch_status, Status)) :- !. +patch_status(status(killed(Signal)), Dict, Dict.put(patch_killed, Signal)) :- !. +patch_status(stderr(""), Dict, Dict) :- !. +patch_status(stderr(Errors), Dict, Dict.put(patch_errors, Errors)) :- !. + + request_file(Request, Dir, File) :- option(path_info(File), Request), ( gitty_file(Dir, File, _Hash) @@ -189,7 +228,7 @@ storage_url(File, HREF) :- http_link_to_id(web_storage, path_postfix(File), HREF). %% meta_data(+Request, +Dict, -Meta) is det. -%% meta_data(+Request, Store, +Dict, -Meta) is det. +%% meta_data(+Request, +Store, +Dict, -Meta) is det. % % Gather meta-data from the Request (user, peer) and provided % meta-data. Illegal and unknown values are ignored. diff --git a/lib/swish/swish_debug.pl b/lib/swish/swish_debug.pl index ec995aa..89e2082 100644 --- a/lib/swish/swish_debug.pl +++ b/lib/swish/swish_debug.pl @@ -96,6 +96,10 @@ stale_module_property(M, thread_status, Status) :- catch(thread_property(Thread, status(Status)), _, fail). stale_module_property(M, program_space, Space) :- module_property(M, program_space(Space)). +stale_module_property(M, program_size, Size) :- + module_property(M, program_size(Size)). +stale_module_property(UUID, highlight_state, State) :- + current_highlight_state(UUID, State). %% swish_statistics(?State) diff --git a/lib/swish/trace.pl b/lib/swish/trace.pl index 5ca9787..f7fb6cd 100644 --- a/lib/swish/trace.pl +++ b/lib/swish/trace.pl @@ -194,8 +194,6 @@ strip_stack(Error, Error). '$swish wrapper'(Goal, '$residuals'(Residuals)) :- catch(swish_call(Goal), E, throw(E)), deterministic(Det), - Goal = M:_, - residuals(M, Residuals), ( tracing, Det == false -> ( notrace, @@ -205,7 +203,9 @@ strip_stack(Error, Error). fail ) ; notrace - ). + ), + Goal = M:_, + residuals(M, Residuals). swish_call(Goal) :- Goal, diff --git a/web/bower_components/codemirror/mode/css/css.js b/web/bower_components/codemirror/mode/css/css.js index ea7bd01..985287f 100644 --- a/web/bower_components/codemirror/mode/css/css.js +++ b/web/bower_components/codemirror/mode/css/css.js @@ -414,7 +414,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) { - keys[array[i]] = true; + keys[array[i].toLowerCase()] = true; } return keys; } @@ -494,7 +494,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "line-stacking-shift", "line-stacking-strategy", "list-style", "list-style-image", "list-style-position", "list-style-type", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", - "marker-offset", "marks", "marquee-direction", "marquee-loop", + "marks", "marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed", "marquee-style", "max-height", "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", "nav-left", "nav-right", "nav-up", "object-fit", "object-position", @@ -522,7 +522,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "text-wrap", "top", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", "unicode-bidi", - "vertical-align", "visibility", "voice-balance", "voice-duration", + "user-select", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space", "widows", "width", "word-break", "word-spacing", "word-wrap", "z-index", diff --git a/web/bower_components/codemirror/mode/javascript/javascript.js b/web/bower_components/codemirror/mode/javascript/javascript.js index d7c5716..a717745 100644 --- a/web/bower_components/codemirror/mode/javascript/javascript.js +++ b/web/bower_components/codemirror/mode/javascript/javascript.js @@ -54,6 +54,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { "namespace": C, "module": kw("module"), "enum": kw("module"), + "type": kw("type"), // scope modifiers "public": kw("modifier"), @@ -208,6 +209,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var arrow = stream.string.indexOf("=>", stream.start); if (arrow < 0) return; + if (isTS) { // Try to skip TypeScript return type declarations after the arguments + var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow)) + if (m) arrow = m.index + } + var depth = 0, sawSomething = false; for (var pos = arrow - 1; pos >= 0; --pos) { var ch = stream.string.charAt(pos); @@ -343,19 +349,19 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function statement(type, value) { if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); - if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); + if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex); if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "{") return cont(pushlex("}"), block, poplex); if (type == ";") return cont(); if (type == "if") { if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) cx.state.cc.pop()(); - return cont(pushlex("form"), expression, statement, poplex, maybeelse); + return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse); } if (type == "function") return cont(functiondef); if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); if (type == "variable") return cont(pushlex("stat"), maybelabel); - if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), + if (type == "switch") return cont(pushlex("form"), parenExpr, pushlex("}", "switch"), expect("{"), block, poplex, poplex); if (type == "case") return cont(expression, expect(":")); if (type == "default") return cont(expect(":")); @@ -365,6 +371,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "export") return cont(pushlex("stat"), afterExport, poplex); if (type == "import") return cont(pushlex("stat"), afterImport, poplex); if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) + if (type == "type") return cont(typeexpr, expect("operator"), typeexpr, expect(";")); if (type == "async") return cont(statement) return pass(pushlex("stat"), expression, expect(";"), poplex); } @@ -374,6 +381,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function expressionNoComma(type) { return expressionInner(type, true); } + function parenExpr(type) { + if (type != "(") return pass() + return cont(pushlex(")"), expression, expect(")"), poplex) + } function expressionInner(type, noComma) { if (cx.state.fatArrowAt == cx.stream.start) { var body = noComma ? arrowBodyNoComma : arrowBody; @@ -384,6 +395,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); if (type == "function") return cont(functiondef, maybeop); + if (type == "class") return cont(pushlex("form"), classExpression, poplex); if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression); if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); @@ -519,11 +531,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "}") return cont(); return pass(statement, block); } - function maybetype(type) { - if (isTS && type == ":") return cont(typeexpr); - } - function maybedefault(_, value) { - if (value == "=") return cont(expressionNoComma); + function maybetype(type, value) { + if (isTS) { + if (type == ":") return cont(typeexpr); + if (value == "?") return cont(maybetype); + } } function typeexpr(type) { if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);} @@ -606,24 +618,30 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } function funarg(type) { if (type == "spread") return cont(funarg); - return pass(pattern, maybetype, maybedefault); + return pass(pattern, maybetype, maybeAssign); + } + function classExpression(type, value) { + // Class expressions may have an optional name. + if (type == "variable") return className(type, value); + return classNameAfter(type, value); } function className(type, value) { if (type == "variable") {register(value); return cont(classNameAfter);} } function classNameAfter(type, value) { - if (value == "extends") return cont(isTS ? typeexpr : expression, classNameAfter); + if (value == "extends" || value == "implements") return cont(isTS ? typeexpr : expression, classNameAfter); if (type == "{") return cont(pushlex("}"), classBody, poplex); } function classBody(type, value) { if (type == "variable" || cx.style == "keyword") { - if (value == "static") { + if ((value == "static" || value == "get" || value == "set" || + (isTS && (value == "public" || value == "private" || value == "protected" || value == "readonly" || value == "abstract"))) && + cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) { cx.marked = "keyword"; return cont(classBody); } cx.marked = "property"; - if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); - return cont(functiondef, classBody); + return cont(isTS ? classfield : functiondef, classBody); } if (value == "*") { cx.marked = "keyword"; @@ -632,10 +650,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == ";") return cont(classBody); if (type == "}") return cont(); } - function classGetterSetter(type) { - if (type != "variable") return pass(); - cx.marked = "property"; - return cont(); + function classfield(type, value) { + if (value == "?") return cont(classfield) + if (type == ":") return cont(typeexpr, maybeAssign) + return pass(functiondef) } function afterExport(_type, value) { if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } @@ -704,14 +722,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { indent: function(state, textAfter) { if (state.tokenize == tokenComment) return CodeMirror.Pass; if (state.tokenize != tokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top // Kludge to prevent 'maybelse' from blocking lexical scope pops if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { var c = state.cc[i]; if (c == poplex) lexical = lexical.prev; else if (c != maybeelse) break; } - if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; + while ((lexical.type == "stat" || lexical.type == "form") && + (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) && + (top == maybeoperatorComma || top == maybeoperatorNoComma) && + !/^[,\.=+\-*:?[\(]/.test(textAfter)))) + lexical = lexical.prev; if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") lexical = lexical.prev; var type = lexical.type, closing = firstChar == type;