mirror of
https://github.com/WolframResearch/WolframLanguageForJupyter.git
synced 2025-04-16 11:46:05 +00:00
Merge pull request #68 from WolframResearch/feature/jupyter-console-support
Add jupyter-console support
This commit is contained in:
commit
3523e815e6
@ -9,6 +9,8 @@ Symbols defined:
|
||||
Print,
|
||||
Throw,
|
||||
Catch,
|
||||
redirectPrint,
|
||||
redirectMessages,
|
||||
simulatedEvaluate
|
||||
*************************************************)
|
||||
|
||||
@ -72,24 +74,72 @@ If[
|
||||
(* redirect Print calls into a message to Jupyter, in order to print in the Jupyter notebook *)
|
||||
(* TODO: review other methods: through EvaluationData or WSTP so we don't redefine Print *)
|
||||
Unprotect[Print];
|
||||
Print[args___, opts:OptionsPattern[]] :=
|
||||
Print[ourArgs___, opts:OptionsPattern[]] :=
|
||||
Block[
|
||||
{
|
||||
$inPrint=True,
|
||||
$Output={OpenWrite[FormatType->OutputForm]}
|
||||
$inPrint = True,
|
||||
$Output
|
||||
},
|
||||
If[
|
||||
!FailureQ[First[$Output]],
|
||||
Print[args, opts];
|
||||
loopState["printFunction"] =!= False,
|
||||
$Output = {OpenWrite[FormatType -> OutputForm]};
|
||||
If[
|
||||
!FailureQ[First[$Output]],
|
||||
Print[ourArgs, opts];
|
||||
loopState["printFunction"][
|
||||
Import[$Output[[1,1]], "String"]
|
||||
];
|
||||
Close[First[$Output]];
|
||||
];
|
||||
];
|
||||
Close[First[$Output]];
|
||||
Null
|
||||
] /; !TrueQ[$inPrint];
|
||||
Protect[Print];
|
||||
|
||||
(************************************
|
||||
versions of Quit and Exit that
|
||||
ask the Jupyter console
|
||||
to quit, if running under
|
||||
a Jupyter console
|
||||
*************************************)
|
||||
|
||||
Unprotect[Quit];
|
||||
Quit[ourArgs___] :=
|
||||
Block[
|
||||
{$inQuit = True},
|
||||
If[
|
||||
loopState["isCompleteRequestSent"],
|
||||
loopState["askExit"] = True;,
|
||||
Quit[ourArgs];
|
||||
];
|
||||
] /;
|
||||
(
|
||||
(!TrueQ[$inQuit]) &&
|
||||
(
|
||||
(Length[{ourArgs}] == 0) ||
|
||||
((Length[{ourArgs}] == 1) && IntegerQ[ourArgs])
|
||||
)
|
||||
);
|
||||
Protect[Quit];
|
||||
|
||||
Unprotect[Exit];
|
||||
Exit[ourArgs___] :=
|
||||
Block[
|
||||
{$inExit = True},
|
||||
If[
|
||||
loopState["isCompleteRequestSent"],
|
||||
loopState["askExit"] = True;,
|
||||
Exit[ourArgs];
|
||||
];
|
||||
] /;
|
||||
(
|
||||
(!TrueQ[$inExit]) &&
|
||||
(
|
||||
(Length[{ourArgs}] == 0) ||
|
||||
((Length[{ourArgs}] == 1) && IntegerQ[ourArgs])
|
||||
)
|
||||
);
|
||||
Protect[Exit];
|
||||
|
||||
(************************************
|
||||
versions of Throw and
|
||||
Catch that we can
|
||||
@ -112,6 +162,96 @@ If[
|
||||
];
|
||||
Protect[Catch];
|
||||
|
||||
(************************************
|
||||
redirection utilities
|
||||
*************************************)
|
||||
|
||||
(* redirect Print to Jupyter *)
|
||||
redirectPrint[currentSourceFrame_, printText_] :=
|
||||
(* send a frame *)
|
||||
sendFrame[
|
||||
(* on the IO Publish socket *)
|
||||
ioPubSocket,
|
||||
(* create the frame *)
|
||||
createReplyFrame[
|
||||
(* using the current source frame *)
|
||||
currentSourceFrame,
|
||||
(* see https://jupyter-client.readthedocs.io/en/stable/messaging.html#streams-stdout-stderr-etc *)
|
||||
(* with a message type of "stream" *)
|
||||
"stream",
|
||||
(* and with message content that tells Jupyter what to Print, and to use stdout *)
|
||||
ExportString[
|
||||
Association[
|
||||
"name" -> "stdout",
|
||||
"text" -> printText
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
],
|
||||
(* and without branching off *)
|
||||
False
|
||||
]
|
||||
];
|
||||
|
||||
(* redirect messages to Jupyter *)
|
||||
redirectMessages[currentSourceFrame_, messageName_, messageText_, addNewline_, dropMessageName_:False] :=
|
||||
Module[
|
||||
{
|
||||
(* string forms of the arguments messageName and messageText *)
|
||||
messageNameString,
|
||||
messageTextString
|
||||
},
|
||||
(* generate string forms of the arguments *)
|
||||
messageNameString = ToString[messageName];
|
||||
messageTextString = ToString[messageText];
|
||||
(* send a frame *)
|
||||
sendFrame[
|
||||
(* on the IO Publish socket *)
|
||||
ioPubSocket,
|
||||
(* create the frame *)
|
||||
createReplyFrame[
|
||||
(* using the current source frame *)
|
||||
currentSourceFrame,
|
||||
(* see https://jupyter-client.readthedocs.io/en/stable/messaging.html#execution-errors *)
|
||||
(* with a message type of "error" *)
|
||||
"error",
|
||||
(* and with appropriate message content *)
|
||||
ExportString[
|
||||
Association[
|
||||
(* use the provided message name here *)
|
||||
"ename" -> messageNameString,
|
||||
(* use the provided message text here *)
|
||||
"evalue" -> messageTextString,
|
||||
(* use the provided message name and message text here (unless dropMessageName) *)
|
||||
"traceback" ->
|
||||
{
|
||||
(* output the message in red *)
|
||||
StringJoin[
|
||||
"\033[0;31m",
|
||||
If[
|
||||
dropMessageName,
|
||||
(* use only the message text here if dropMessageName is True *)
|
||||
messageTextString,
|
||||
(* otherwise, combine the message name and message text *)
|
||||
ToString[System`ColonForm[messageName, messageText]]
|
||||
],
|
||||
(* if addNewline, add a newline *)
|
||||
If[addNewline, "\n", ""],
|
||||
"\033[0m"
|
||||
]
|
||||
}
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
],
|
||||
(* and without branching off *)
|
||||
False
|
||||
]
|
||||
];
|
||||
(* ... and return an empty string to the Wolfram Language message system *)
|
||||
Return[""];
|
||||
];
|
||||
|
||||
(************************************
|
||||
utilities for splitting
|
||||
input by Wolfram Language
|
||||
|
@ -185,6 +185,13 @@ If[
|
||||
(* flag for if WolframLanguageForJupyter should shut down *)
|
||||
"doShutdown" -> False,
|
||||
|
||||
(* flag for whether to redirect messages, or to, at the end of the execution of an input,
|
||||
bundle them with the result *)
|
||||
"redirectMessages" -> True,
|
||||
|
||||
(* flag for if an is_complete_request has ever been sent to the kernel *)
|
||||
"isCompleteRequestSent" -> False,
|
||||
|
||||
(* local to an iteration *)
|
||||
(* a received frame as an Association *)
|
||||
"frameAssoc" -> Null,
|
||||
@ -194,8 +201,11 @@ If[
|
||||
"replyContent" -> Null,
|
||||
(* message relpy frame to send on the IO Publish socket, if it is not Null *)
|
||||
"ioPubReplyFrame" -> Null,
|
||||
(* the function Print should use *)
|
||||
"printFunction" -> Function[#;]
|
||||
(* the redirect function Print should use *)
|
||||
"printFunction" -> False,
|
||||
(* flag for if the Jupyter console (if running under a Jupyter console)
|
||||
should ask the user if it should exit *)
|
||||
"askExit" -> False
|
||||
];
|
||||
|
||||
(* helper utility for applying hooks if they are set *)
|
||||
@ -225,7 +235,7 @@ If[
|
||||
bannerWarning =
|
||||
If[
|
||||
Length[$CommandLine] > 4,
|
||||
"\\n\\nThis Jupyter kernel was installed through the WolframLanguageForJupyter WolframScript script install option. Accordingly, updates to a WolframLanguageForJupyter paclet installed to a Wolfram Engine will not propagate to this installation.",
|
||||
"\\n\\nNote: This Jupyter kernel was installed through the WolframScript install method. Accordingly, updates to a WolframLanguageForJupyter paclet will not affect this kernel.",
|
||||
""
|
||||
];
|
||||
|
||||
|
@ -31,12 +31,12 @@ Needs["ZeroMQLink`"]; (* SocketReadMessage *)
|
||||
files
|
||||
*************************************)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* initialize WolframLanguageForJupyter; loopState, bannerWarning, shellSocket, ioPubSocket *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* initialize WolframLanguageForJupyter; loopState, bannerWarning, shellSocket, controlSocket, ioPubSocket *)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "SocketUtilities.wl"}]]; (* sendFrame *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "MessagingUtilities.wl"}]]; (* getFrameAssoc, createReplyFrame *)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "RequestHandlers.wl"}]]; (* executeRequestHandler, completeRequestHandler *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "RequestHandlers.wl"}]]; (* isCompleteRequestHandler, executeRequestHandler, completeRequestHandler *)
|
||||
|
||||
(************************************
|
||||
private symbols
|
||||
@ -49,24 +49,27 @@ Begin["`Private`"];
|
||||
loop[] :=
|
||||
Module[
|
||||
{
|
||||
(* the socket that has become ready *)
|
||||
readySocket,
|
||||
|
||||
(* the raw byte array frame received through SocketReadMessage *)
|
||||
rawFrame,
|
||||
|
||||
(* a frame for sending status updates on the IO Publish socket *)
|
||||
statusReplyFrame,
|
||||
|
||||
(* a frame for sending replies on the shell socket *)
|
||||
shellReplyFrame
|
||||
(* a frame for sending replies on a socket *)
|
||||
replyFrame
|
||||
},
|
||||
While[
|
||||
True,
|
||||
Switch[
|
||||
(* poll sockets until one is ready *)
|
||||
First[SocketWaitNext[{shellSocket}]],
|
||||
(* if the shell socket is ready, ... *)
|
||||
shellSocket,
|
||||
readySocket = First[SocketWaitNext[{shellSocket, controlSocket}]],
|
||||
(* if the shell socket or control socket is ready, ... *)
|
||||
shellSocket | controlSocket,
|
||||
(* receive a frame *)
|
||||
rawFrame = SocketReadMessage[shellSocket, "Multipart" -> True];
|
||||
rawFrame = SocketReadMessage[readySocket, "Multipart" -> True];
|
||||
(* check for any problems *)
|
||||
If[FailureQ[rawFrame],
|
||||
Quit[];
|
||||
@ -87,11 +90,10 @@ loop[] :=
|
||||
bannerWarning,
|
||||
"\"}"
|
||||
];,
|
||||
(* if asking if the input is complete, respond "unknown" *)
|
||||
(* if asking if the input is complete (relevant for jupyter-console), respond appropriately *)
|
||||
"is_complete_request",
|
||||
(* TODO: add syntax-Q checking *)
|
||||
loopState["replyMsgType"] = "is_complete_reply";
|
||||
loopState["replyContent"] = "{\"status\":\"unknown\"}";,
|
||||
(* isCompleteRequestHandler will read and update loopState *)
|
||||
isCompleteRequestHandler[];,
|
||||
(* if asking the kernel to execute something, use executeRequestHandler *)
|
||||
"execute_request",
|
||||
(* executeRequestHandler will read and update loopState *)
|
||||
@ -124,8 +126,8 @@ loop[] :=
|
||||
(* send the frame *)
|
||||
sendFrame[ioPubSocket, statusReplyFrame];
|
||||
|
||||
(* create a message frame to send a reply on the shell socket *)
|
||||
shellReplyFrame =
|
||||
(* create a message frame to send a reply on the socket that became ready *)
|
||||
replyFrame =
|
||||
createReplyFrame[
|
||||
(* use the current source frame *)
|
||||
loopState["frameAssoc"],
|
||||
@ -137,12 +139,13 @@ loop[] :=
|
||||
False
|
||||
];
|
||||
(* send the frame *)
|
||||
sendFrame[shellSocket, shellReplyFrame];
|
||||
sendFrame[readySocket, replyFrame];
|
||||
|
||||
(* if an ioPubReplyFrame was created, send it on the IO Publish socket *)
|
||||
If[!(loopState["ioPubReplyFrame"] === Association[]),
|
||||
If[
|
||||
loopState["ioPubReplyFrame"] =!= Association[],
|
||||
sendFrame[ioPubSocket, loopState["ioPubReplyFrame"]];
|
||||
(* reset ioPubReplyFrame *)
|
||||
(* -- also, reset ioPubReplyFrame *)
|
||||
loopState["ioPubReplyFrame"] = Association[];
|
||||
];
|
||||
|
||||
@ -161,10 +164,10 @@ loop[] :=
|
||||
]
|
||||
];
|
||||
|
||||
(* if the doShutdown flag was set as True, shut down*)
|
||||
(* if the doShutdown flag is True, shut down *)
|
||||
If[
|
||||
loopState["doShutdown"],
|
||||
Quit[];
|
||||
Block[{$inQuit = True}, Quit[]];
|
||||
];
|
||||
,
|
||||
_,
|
||||
|
@ -9,9 +9,10 @@ Description:
|
||||
notebooks
|
||||
Symbols defined:
|
||||
textQ,
|
||||
toOutText,
|
||||
toText,
|
||||
toOutTextHTML,
|
||||
toImageData,
|
||||
toOutImage
|
||||
toOutImageHTML
|
||||
*************************************************)
|
||||
|
||||
(************************************
|
||||
@ -178,20 +179,23 @@ If[
|
||||
results as text and images
|
||||
*************************************)
|
||||
|
||||
(* generate the textual form of a result *)
|
||||
(* NOTE: the OutputForm (which ToString uses) of any expressions wrapped with, say, InputForm should
|
||||
be identical to the string result of an InputForm-wrapped expression itself *)
|
||||
toText[result_] := ToString[If[Head[result] =!= TeXForm, $trueFormatType[result], result]];
|
||||
|
||||
(* generate HTML for the textual form of a result *)
|
||||
toOutText[result_] :=
|
||||
toOutTextHTML[result_] :=
|
||||
Module[
|
||||
{isTeXWrapped, isTeXFinal},
|
||||
(* check if this result is wrapped with TeXForm *)
|
||||
isTeXWrapped = (Head[result] === TeXForm);
|
||||
(* check if this result should be marked, in the end, as TeX *)
|
||||
isTeXFinal = isTeXWrapped || $outputSetToTeXForm;
|
||||
{isTeX},
|
||||
(* check if this result should be marked as TeX *)
|
||||
isTeX = (Head[result] === TeXForm) || $outputSetToTeXForm;
|
||||
Return[
|
||||
StringJoin[
|
||||
|
||||
(* mark this result as preformatted only if it isn't TeX *)
|
||||
If[
|
||||
!isTeXFinal,
|
||||
!isTeX,
|
||||
{
|
||||
(* preformatted *)
|
||||
"<pre style=\"",
|
||||
@ -203,23 +207,21 @@ If[
|
||||
],
|
||||
|
||||
(* mark the text as TeX, if is TeX *)
|
||||
If[isTeXFinal, "$$", ""],
|
||||
If[isTeX, "$$", ""],
|
||||
|
||||
(* the textual form of the result *)
|
||||
(* NOTE: the OutputForm (which ToString uses) of any expressions wrapped with, say, InputForm should
|
||||
be identical to the string result of an InputForm-wrapped expression itself *)
|
||||
({"&#", ToString[#1], ";"} & /@
|
||||
ToCharacterCode[
|
||||
(* toStringUsingOutput[result] *) ToString[If[!isTeXWrapped, $trueFormatType[result], result]],
|
||||
(* toStringUsingOutput[result] *) toText[result],
|
||||
"Unicode"
|
||||
]),
|
||||
|
||||
(* mark the text as TeX, if is TeX *)
|
||||
If[isTeXFinal, "$$", ""],
|
||||
If[isTeX, "$$", ""],
|
||||
|
||||
(* mark this result as preformatted only if it isn't TeX *)
|
||||
If[
|
||||
!isTeXFinal,
|
||||
!isTeX,
|
||||
{
|
||||
(* end the element *)
|
||||
"</pre>"
|
||||
@ -259,7 +261,7 @@ If[
|
||||
];
|
||||
|
||||
(* generate HTML for the rasterized form of a result *)
|
||||
toOutImage[result_] :=
|
||||
toOutImageHTML[result_] :=
|
||||
Module[
|
||||
{
|
||||
(* the rasterization of result *)
|
||||
|
@ -2,9 +2,10 @@
|
||||
RequestHandlers.wl
|
||||
*************************************************
|
||||
Description:
|
||||
Handlers for message frames of type
|
||||
"x_request" arriving from Jupyter
|
||||
Handlers for message frames of the form
|
||||
"*_request" arriving from Jupyter
|
||||
Symbols defined:
|
||||
isCompleteRequestHandler,
|
||||
executeRequestHandler,
|
||||
completeRequestHandler
|
||||
*************************************************)
|
||||
@ -29,10 +30,10 @@ If[
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "SocketUtilities.wl"}]]; (* sendFrame *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "MessagingUtilities.wl"}]]; (* createReplyFrame *)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "EvaluationUtilities.wl"}]]; (* simulatedEvaluate *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "EvaluationUtilities.wl"}]]; (* redirectPrint, redirectMessages, simulatedEvaluate *)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "OutputHandlingUtilities.wl"}]]; (* textQ, toOutText, toOutImage,
|
||||
containsPUAQ *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "OutputHandlingUtilities.wl"}]]; (* textQ, toOutTextHTML, toOutImageHTML,
|
||||
toText, containsPUAQ *)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "CompletionUtilities.wl"}]]; (* rewriteNamedCharacters *)
|
||||
|
||||
@ -43,11 +44,58 @@ If[
|
||||
(* begin the private context for WolframLanguageForJupyter *)
|
||||
Begin["`Private`"];
|
||||
|
||||
(************************************
|
||||
handler for is_complete_requests
|
||||
*************************************)
|
||||
|
||||
(* handle is_complete_request message frames received on the shell socket *)
|
||||
isCompleteRequestHandler[] :=
|
||||
Module[
|
||||
{
|
||||
(* the length of the code string to check completeness for *)
|
||||
stringLength,
|
||||
(* the value returned by SyntaxLength[] on the code string to check completeness for *)
|
||||
syntaxLength
|
||||
},
|
||||
|
||||
(* mark loopState["isCompleteRequestSent"] as True *)
|
||||
loopState["isCompleteRequestSent"] = True;
|
||||
|
||||
(* set the appropriate reply type *)
|
||||
loopState["replyMsgType"] = "is_complete_reply";
|
||||
|
||||
(* determine the length of the code string *)
|
||||
stringLength = StringLength[loopState["frameAssoc"]["content"]["code"]];
|
||||
(* determine the SyntaxLength[] value for the code string *)
|
||||
syntaxLength = SyntaxLength[loopState["frameAssoc"]["content"]["code"]];
|
||||
|
||||
(* test the value of syntaxLength to determine the completeness of the code string,
|
||||
setting the content of the reply appropriately *)
|
||||
Which[
|
||||
(* if the above values could not be correctly determined,
|
||||
the completeness status of the code string is unknown *)
|
||||
!IntegerQ[stringLength] || !IntegerQ[syntaxLength],
|
||||
loopState["replyContent"] = "{\"status\":\"unknown\"}";,
|
||||
(* if the SyntaxLength[] value for a code string is greater than its actual length,
|
||||
the code string is incomplete *)
|
||||
syntaxLength > stringLength,
|
||||
loopState["replyContent"] = "{\"status\":\"incomplete\"}";,
|
||||
(* if the SyntaxLength[] value for a code string is less than its actual length,
|
||||
the code string contains a syntax error (or is "invalid") *)
|
||||
syntaxLength < stringLength,
|
||||
loopState["replyContent"] = "{\"status\":\"invalid\"}";,
|
||||
(* if the SyntaxLength[] value for a code string is equal to its actual length,
|
||||
the code string is complete and correct *)
|
||||
syntaxLength == stringLength,
|
||||
loopState["replyContent"] = "{\"status\":\"complete\"}";
|
||||
];
|
||||
];
|
||||
|
||||
(************************************
|
||||
handler for execute_requests
|
||||
*************************************)
|
||||
|
||||
(* handle execute_request messages frames received on the shell socket *)
|
||||
(* handle execute_request message frames received on the shell socket *)
|
||||
executeRequestHandler[] :=
|
||||
Module[
|
||||
{
|
||||
@ -64,9 +112,58 @@ If[
|
||||
the total number of indices consumed by this evaluation ("ConsumedIndices"),
|
||||
generated messages ("GeneratedMessages")
|
||||
*)
|
||||
totalResult
|
||||
totalResult,
|
||||
|
||||
(* flag for if there are any unreported error messages after execution of the input *)
|
||||
unreportedErrorMessages
|
||||
},
|
||||
|
||||
(* if an is_complete_request has been sent, assume jupyter-console is running the kernel,
|
||||
redirect messages, and handle any "Quit", "Exit", "quit" or "exit" inputs *)
|
||||
If[
|
||||
loopState["isCompleteRequestSent"],
|
||||
loopState["redirectMessages"] = True;
|
||||
If[
|
||||
StringMatchQ[
|
||||
loopState["frameAssoc"]["content"]["code"],
|
||||
"Quit" | "Exit" | "quit" | "exit"
|
||||
],
|
||||
loopState["replyMsgType"] = "execute_reply";
|
||||
(* NOTE: uses payloads *)
|
||||
loopState["replyContent"] = ExportString[Association["status" -> "ok", "execution_count" -> loopState["executionCount"], "user_expressions" -> {}, "payload" -> {Association["source" -> "ask_exit", "keepkernel" -> False]}], "JSON", "Compact" -> True];
|
||||
Return[];
|
||||
];
|
||||
];
|
||||
|
||||
(* redirect Print so that it prints in the Jupyter notebook *)
|
||||
loopState["printFunction"] = (redirectPrint[loopState["frameAssoc"], #1] &);
|
||||
|
||||
(* if loopState["redirectMessages"] is True,
|
||||
update Jupyter explicitly with any errors that occur DURING the execution of the input *)
|
||||
If[
|
||||
loopState["redirectMessages"],
|
||||
Internal`$MessageFormatter =
|
||||
(
|
||||
redirectMessages[
|
||||
loopState["frameAssoc"],
|
||||
#1,
|
||||
#2,
|
||||
(* add a newline if loopState["isCompleteRequestSent"] *)
|
||||
loopState["isCompleteRequestSent"]
|
||||
]
|
||||
&
|
||||
);
|
||||
];
|
||||
|
||||
(* evaluate the input, and store the total result in totalResult *)
|
||||
totalResult = simulatedEvaluate[loopState["frameAssoc"]["content"]["code"]];
|
||||
|
||||
(* restore printFunction to False *)
|
||||
loopState["printFunction"] = False;
|
||||
|
||||
(* unset Internal`$MessageFormatter *)
|
||||
Unset[Internal`$MessageFormatter];
|
||||
|
||||
(* set the appropriate reply type *)
|
||||
loopState["replyMsgType"] = "execute_reply";
|
||||
|
||||
@ -76,59 +173,61 @@ If[
|
||||
Association[
|
||||
"status" -> "ok",
|
||||
"execution_count" -> loopState["executionCount"],
|
||||
"user_expressions" -> {}
|
||||
],
|
||||
"user_expressions" -> {},
|
||||
(* see https://jupyter-client.readthedocs.io/en/stable/messaging.html#payloads-deprecated *)
|
||||
(* if the "askExit" flag is True, add an "ask_exit" payload *)
|
||||
(* NOTE: uses payloads *)
|
||||
"payload" -> If[loopState["askExit"], {Association["source" -> "ask_exit", "keepkernel" -> False]}, {}]
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
];
|
||||
|
||||
(* redirect Print so that it prints in the Jupyter notebook *)
|
||||
loopState["printFunction"] =
|
||||
(* check if there are any unreported error messages *)
|
||||
unreportedErrorMessages =
|
||||
(
|
||||
(* send a frame *)
|
||||
sendFrame[
|
||||
(* on the IO Publish socket *)
|
||||
ioPubSocket,
|
||||
(* create the frame *)
|
||||
createReplyFrame[
|
||||
(* using the current source frame *)
|
||||
loopState["frameAssoc"],
|
||||
(* see https://jupyter-client.readthedocs.io/en/stable/messaging.html#streams-stdout-stderr-etc *)
|
||||
(* with a message type of "stream" *)
|
||||
"stream",
|
||||
(* and with message content that tells Jupyter what to Print, and to use stdout *)
|
||||
ExportString[
|
||||
Association[
|
||||
"name" -> "stdout",
|
||||
"text" -> #1
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
],
|
||||
(* and without branching off *)
|
||||
False
|
||||
]
|
||||
]
|
||||
&
|
||||
(* ... because messages are not being redirected *)
|
||||
(!loopState["redirectMessages"]) &&
|
||||
(* ... and because at least one message was generated *)
|
||||
(StringLength[totalResult["GeneratedMessages"]] > 0)
|
||||
);
|
||||
|
||||
(* evaluate the input, and store the total result in totalResult *)
|
||||
totalResult = simulatedEvaluate[loopState["frameAssoc"]["content"]["code"]];
|
||||
|
||||
(* restore printFunction to empty *)
|
||||
loopState["printFunction"] = Function[#;];
|
||||
(* if there are no results, or if the "askExit" flag is True,
|
||||
do not send anything on the IO Publish socket and return *)
|
||||
If[
|
||||
(Length[totalResult["EvaluationResultOutputLineIndices"]] == 0) ||
|
||||
(loopState["askExit"]),
|
||||
(* set the "askExit" flag to False *)
|
||||
loopState["askExit"] = False;
|
||||
(* send any unreported error messages *)
|
||||
If[unreportedErrorMessages,
|
||||
redirectMessages[
|
||||
loopState["frameAssoc"],
|
||||
"",
|
||||
totalResult["GeneratedMessages"],
|
||||
(* do not add a newline *)
|
||||
False,
|
||||
(* drop message name *)
|
||||
True
|
||||
];
|
||||
];
|
||||
(* increment loopState["executionCount"] as needed *)
|
||||
loopState["executionCount"] += totalResult["ConsumedIndices"];
|
||||
Return[];
|
||||
];
|
||||
|
||||
(* generate an HTML form of the message text *)
|
||||
errorMessage =
|
||||
If[StringLength[totalResult["GeneratedMessages"]] == 0,
|
||||
(* if there are no messages, no need to format anything *)
|
||||
If[
|
||||
!unreportedErrorMessages,
|
||||
(* if there are no unreported error messages, there is no need to format them *)
|
||||
{},
|
||||
(* build the HTML form of the message text *)
|
||||
{
|
||||
(* preformatted *)
|
||||
"<pre style=\"",
|
||||
(* the color of the text should be red, and should use Courier *)
|
||||
StringJoin[{"&#",ToString[#1], ";"} & /@ ToCharacterCode["color:red; font-family: \"Courier New\",Courier,monospace;", "UTF-8"]],
|
||||
StringJoin[{"&#", ToString[#1], ";"} & /@ ToCharacterCode["color:red; font-family: \"Courier New\",Courier,monospace;", "UTF-8"]],
|
||||
(* end pre tag *)
|
||||
"\">",
|
||||
(* the generated messages *)
|
||||
@ -138,14 +237,6 @@ If[
|
||||
}
|
||||
];
|
||||
|
||||
(* if there are no results, do not send anything on the IO Publish socket and return *)
|
||||
If[
|
||||
Length[totalResult["EvaluationResultOutputLineIndices"]] == 0,
|
||||
(* increment loopState["executionCount"] as needed *)
|
||||
loopState["executionCount"] += totalResult["ConsumedIndices"];
|
||||
Return[];
|
||||
];
|
||||
|
||||
(* format output as purely text, image, or cloud interface *)
|
||||
If[
|
||||
(* check if the input was wrapped with Interact,
|
||||
@ -161,99 +252,115 @@ If[
|
||||
"execution_count" -> First[totalResult["EvaluationResultOutputLineIndices"]],
|
||||
(* HTML code to embed output uploaded to the Cloud in the Jupyter notebook *)
|
||||
"data" ->
|
||||
{"text/html" ->
|
||||
StringJoin[
|
||||
(* display any generated messages as inlined PNG images encoded in base64 *)
|
||||
"<div><img alt=\"\" src=\"data:image/png;base64,",
|
||||
(* rasterize the generated messages in a dark red color, and convert the resulting image to base64*)
|
||||
BaseEncode[ExportByteArray[Rasterize[Style[totalResult["GeneratedMessages"], Darker[Red]]], "PNG"]],
|
||||
(* end image element *)
|
||||
"\">",
|
||||
(* embed the cloud object *)
|
||||
EmbedCode[CloudDeploy[totalResult["EvaluationResult"]], "HTML"][[1]]["CodeSection"]["Content"],
|
||||
(* end the whole element *)
|
||||
"</div>"
|
||||
]
|
||||
{
|
||||
"text/html" ->
|
||||
StringJoin[
|
||||
(* display any generated messages as inlined PNG images encoded in base64 *)
|
||||
"<div><img alt=\"\" src=\"data:image/png;base64,",
|
||||
(* rasterize the generated messages in a dark red color, and convert the resulting image to base64*)
|
||||
BaseEncode[ExportByteArray[Rasterize[Style[totalResult["GeneratedMessages"], Darker[Red]]], "PNG"]],
|
||||
(* end image element *)
|
||||
"\">",
|
||||
(* embed the cloud object *)
|
||||
EmbedCode[CloudDeploy[totalResult["EvaluationResult"]], "HTML"][[1]]["CodeSection"]["Content"],
|
||||
(* end the whole element *)
|
||||
"</div>"
|
||||
],
|
||||
"text/plain" -> ""
|
||||
},
|
||||
(* no metadata *)
|
||||
"metadata" -> {"text/html" -> {}}
|
||||
"metadata" -> {"text/html" -> {}, "text/plain" -> {}}
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
];
|
||||
,
|
||||
(* if every output line can be formatted as text, use a function that converts the output to text *)
|
||||
(* TODO: allow for mixing text and image results *)
|
||||
(* otherwise, use a function that converts the output to an image *)
|
||||
(* TODO: allow for mixing text and image results *)
|
||||
If[AllTrue[totalResult["EvaluationResult"], textQ],
|
||||
toOut = toOutText,
|
||||
toOut = toOutImage
|
||||
toOut = toOutTextHTML,
|
||||
toOut = toOutImageHTML
|
||||
];
|
||||
(* prepare the content for a reply message frame to be sent on the IO Publish socket *)
|
||||
ioPubReplyContent = ExportString[
|
||||
Association[
|
||||
(* the first output index *)
|
||||
"execution_count" -> First[totalResult["EvaluationResultOutputLineIndices"]],
|
||||
(* generate HTML of results and messages *)
|
||||
(* the data representing the results and messages *)
|
||||
"data" ->
|
||||
{"text/html" ->
|
||||
(* output the results in a grid *)
|
||||
If[
|
||||
Length[totalResult["EvaluationResult"]] > 1,
|
||||
StringJoin[
|
||||
(* add grid style *)
|
||||
"<style>
|
||||
.grid-container {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
</style>
|
||||
{
|
||||
(* generate HTML for the results and messages *)
|
||||
"text/html" ->
|
||||
(* output the results in a grid *)
|
||||
If[
|
||||
Length[totalResult["EvaluationResult"]] > 1,
|
||||
StringJoin[
|
||||
(* add grid style *)
|
||||
"<style>
|
||||
.grid-container {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>",
|
||||
(* display error message *)
|
||||
errorMessage,
|
||||
(* start the grid *)
|
||||
"<div class=\"grid-container\">",
|
||||
(* display the output lines *)
|
||||
<div>",
|
||||
(* display error message *)
|
||||
errorMessage,
|
||||
(* start the grid *)
|
||||
"<div class=\"grid-container\">",
|
||||
(* display the output lines *)
|
||||
Table[
|
||||
{
|
||||
(* start the grid item *)
|
||||
"<div class=\"grid-item\">",
|
||||
(* show the output line *)
|
||||
toOut[totalResult["EvaluationResult"][[outIndex]]],
|
||||
(* end the grid item *)
|
||||
"</div>"
|
||||
},
|
||||
{outIndex, 1, Length[totalResult["EvaluationResult"]]}
|
||||
],
|
||||
(* end the element *)
|
||||
"</div></div>"
|
||||
],
|
||||
StringJoin[
|
||||
(* start the element *)
|
||||
"<div>",
|
||||
(* display error message *)
|
||||
errorMessage,
|
||||
(* if there are messages, but no results, do not display a result *)
|
||||
If[
|
||||
Length[totalResult["EvaluationResult"]] == 0,
|
||||
"",
|
||||
(* otherwise, display a result *)
|
||||
toOut[First[totalResult["EvaluationResult"]]]
|
||||
],
|
||||
(* end the element *)
|
||||
"</div>"
|
||||
]
|
||||
],
|
||||
(* provide, as a backup, plain text for the results *)
|
||||
"text/plain" ->
|
||||
StringJoin[
|
||||
Table[
|
||||
{
|
||||
(* start the grid item *)
|
||||
"<div class=\"grid-item\">",
|
||||
(* show the output line *)
|
||||
toOut[totalResult["EvaluationResult"][[outIndex]]],
|
||||
(* end the grid item *)
|
||||
"</div>"
|
||||
toText[totalResult["EvaluationResult"][[outIndex]]],
|
||||
(* -- also, suppress newline if this is the last result *)
|
||||
If[outIndex != Length[totalResult["EvaluationResult"]], "\n", ""]
|
||||
},
|
||||
{outIndex, 1, Length[totalResult["EvaluationResult"]]}
|
||||
],
|
||||
(* end the element *)
|
||||
"</div></div>"
|
||||
],
|
||||
StringJoin[
|
||||
(* start the element *)
|
||||
"<div>",
|
||||
(* display error message *)
|
||||
errorMessage,
|
||||
(* if there are messages, but no results, do not display a result *)
|
||||
If[
|
||||
Length[totalResult["EvaluationResult"]] == 0,
|
||||
"",
|
||||
(* otherwise, display a result *)
|
||||
toOut[First[totalResult["EvaluationResult"]]]
|
||||
],
|
||||
(* end the element *)
|
||||
"</div>"
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
(* no metadata *)
|
||||
"metadata" -> {"text/html" -> {}}
|
||||
"metadata" -> {"text/html" -> {}, "text/plain" -> {}}
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
];
|
||||
];
|
||||
|
||||
|
||||
(* create frame from ioPubReplyContent *)
|
||||
loopState["ioPubReplyFrame"] =
|
||||
createReplyFrame[
|
||||
@ -275,7 +382,7 @@ If[
|
||||
handler for complete_requests
|
||||
*************************************)
|
||||
|
||||
(* handle complete_request messages frames received on the shell socket *)
|
||||
(* handle complete_request message frames received on the shell socket *)
|
||||
completeRequestHandler[] :=
|
||||
Module[
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user