mirror of
https://github.com/WolframResearch/WolframLanguageForJupyter.git
synced 2025-04-16 03:36:04 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9a26ac7874 | ||
![]() |
17171b757a | ||
![]() |
551351a707 | ||
![]() |
8208cbc746 | ||
![]() |
0fd044f0c4 | ||
![]() |
1fb52c048d | ||
![]() |
a222be43cc | ||
![]() |
65f9950531 | ||
![]() |
12fb4bb728 | ||
![]() |
61835bab6c | ||
![]() |
4c26eca649 | ||
![]() |
d477809f50 | ||
![]() |
10c8ff5991 | ||
![]() |
5587c6b763 | ||
![]() |
ab6ab8a2e7 | ||
![]() |
c7673eb201 | ||
![]() |
4ef757301d | ||
![]() |
a96cd3e345 | ||
![]() |
5825796109 | ||
![]() |
03d50854c8 | ||
![]() |
d138761370 | ||
![]() |
66a03632cc | ||
![]() |
5f6f6363f0 | ||
![]() |
570eab43eb | ||
![]() |
1717628fc8 | ||
![]() |
40961d6029 | ||
![]() |
3523e815e6 | ||
![]() |
ff8acc78d1 | ||
![]() |
515c244124 | ||
![]() |
ea621b19d8 | ||
![]() |
6cca28f8ab | ||
![]() |
1607ff07cd | ||
![]() |
e006c4f7e1 | ||
![]() |
780488b2ad | ||
![]() |
f94a621620 | ||
![]() |
7bc5ee8ae6 | ||
![]() |
ebce0fa0fa | ||
![]() |
efffa66932 | ||
![]() |
6943c0ee3b | ||
![]() |
828604baa4 | ||
![]() |
bbbeecd253 | ||
![]() |
6dab4f7576 | ||
![]() |
7522c71207 | ||
![]() |
12ed6ff52b | ||
![]() |
1dc339e62e | ||
![]() |
3e9068fc4a | ||
![]() |
5ab855e94c | ||
![]() |
e5fd13daef | ||
![]() |
4883583139 | ||
![]() |
a0870db7be | ||
![]() |
c6033abeb0 | ||
![]() |
70d471dc81 | ||
![]() |
5faf681109 | ||
![]() |
f5bcdbe765 | ||
![]() |
75fc9c238d | ||
![]() |
b63c459fa3 | ||
![]() |
34084b057d |
29
README.md
29
README.md
@ -19,9 +19,19 @@ There are **two** ways to make the Wolfram Language available in Jupyter:
|
||||
|
||||
## Method 1: Using `wolframscript`
|
||||
|
||||
Run the following command to make the Wolfram Language engine available to Jupyter:
|
||||
On macOS/Unix: Clone the repository
|
||||
|
||||
configure-jupyter.wls add
|
||||
git clone https://github.com/WolframResearch/WolframLanguageForJupyter.git
|
||||
|
||||
Run the following command in your shell to make the Wolfram Language engine available to Jupyter:
|
||||
|
||||
./configure-jupyter.wls add
|
||||
|
||||
On Windows: Follow the fist two steps [here](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository), and on the the third step select `Download Zip`, and unzip the file using a tool for Windows. Open PowerShell in the directory of the unzipped folder
|
||||
|
||||
Run the following command in your shell to make the Wolfram Language engine available to Jupyter:
|
||||
|
||||
.\configure-jupyter.wls add
|
||||
|
||||
**Notes:**
|
||||
|
||||
@ -31,7 +41,7 @@ Run the following command to make the Wolfram Language engine available to Jupyt
|
||||
|
||||
For more configuration options run:
|
||||
|
||||
configure-jupyter.wls help
|
||||
./configure-jupyter.wls help
|
||||
|
||||
## Method 2: Using Wolfram Language
|
||||
|
||||
@ -39,7 +49,7 @@ You can download the latest version of the paclet here:
|
||||
|
||||
https://github.com/WolframResearch/WolframLanguageForJupyter/releases
|
||||
|
||||
To install the paclet, run the following command (replacing x, y, and z with the correct values):
|
||||
To install the paclet, run the following command with Wolfram Language (replacing x, y, and z with the correct values):
|
||||
|
||||
PacletInstall["WolframLanguageForJupyter-x.y.z.paclet"]
|
||||
|
||||
@ -69,9 +79,9 @@ The following command should now list the Wolfram Engine:
|
||||
|
||||
The output should include a line like this:
|
||||
|
||||
wolframlanguage-11.3.0 C:\ProgramData\jupyter\kernels\wolframlanguage-11.3.0
|
||||
wolframlanguage12 C:\ProgramData\jupyter\kernels\wolframlanguage12
|
||||
|
||||
To test your installation, run the following command:
|
||||
## To test your installation in a notebook, run the following command:
|
||||
|
||||
jupyter notebook
|
||||
|
||||
@ -91,11 +101,14 @@ Any messages that occur during evaluation are displayed:
|
||||
|
||||

|
||||
|
||||
## To test your installation in the terminal, run the following command:
|
||||
jupyter-console --kernel=wolframlanguage12
|
||||
|
||||
# Building the WolframLanguageForJupyter paclet
|
||||
|
||||
To build the WolframLanguageForJupyter paclet file yourself, run:
|
||||
|
||||
configure-jupyter.wls build
|
||||
./configure-jupyter.wls build
|
||||
|
||||
This creates the `WolframLanguageForJupyter-x.y.z.paclet` file (use the `PacletInfo.m` to increment the version).
|
||||
|
||||
@ -105,7 +118,7 @@ This creates the `WolframLanguageForJupyter-x.y.z.paclet` file (use the `PacletI
|
||||
|
||||
Run the following command to remove the Wolfram Language engine from Jupyter:
|
||||
|
||||
configure-jupyter.wls remove
|
||||
./configure-jupyter.wls remove
|
||||
|
||||
## Method 2: Using Wolfram Language
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
Paclet[
|
||||
Name -> "WolframLanguageForJupyter",
|
||||
Version -> "0.9.1",
|
||||
Version -> "0.9.3",
|
||||
MathematicaVersion -> "11.2+",
|
||||
Extensions -> {
|
||||
{"Kernel", Context -> {"WolframLanguageForJupyter`"}},
|
||||
|
84
WolframLanguageForJupyter/Resources/CompletionUtilities.wl
Normal file
84
WolframLanguageForJupyter/Resources/CompletionUtilities.wl
Normal file
@ -0,0 +1,84 @@
|
||||
(************************************************
|
||||
CompletionUtilities.wl
|
||||
*************************************************
|
||||
Description:
|
||||
Utilities for the aiding in the
|
||||
(auto-)completion of Wolfram Language
|
||||
code
|
||||
Symbols defined:
|
||||
rewriteNamedCharacters
|
||||
*************************************************)
|
||||
|
||||
(************************************
|
||||
Get[] guard
|
||||
*************************************)
|
||||
|
||||
If[
|
||||
!TrueQ[WolframLanguageForJupyter`Private`$GotCompletionUtilities],
|
||||
|
||||
WolframLanguageForJupyter`Private`$GotCompletionUtilities = True;
|
||||
|
||||
(************************************
|
||||
load required
|
||||
WolframLanguageForJupyter
|
||||
files
|
||||
*************************************)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* unicodeNamedCharactersReplacements,
|
||||
verticalEllipsis *)
|
||||
|
||||
(************************************
|
||||
private symbols
|
||||
*************************************)
|
||||
|
||||
(* begin the private context for WolframLanguageForJupyter *)
|
||||
Begin["`Private`"];
|
||||
|
||||
(************************************
|
||||
utilities for rewriting
|
||||
Wolfram Language code
|
||||
*************************************)
|
||||
|
||||
(* rewrite names (in a code string) into named characters *)
|
||||
rewriteNamedCharacters[codeToAnalyze_?StringQ] :=
|
||||
Module[
|
||||
{codeUsingFullReplacements},
|
||||
codeUsingFullReplacements =
|
||||
StringReplace[
|
||||
codeToAnalyze,
|
||||
Normal @ unicodeNamedCharactersReplacements
|
||||
];
|
||||
If[
|
||||
StringCount[
|
||||
codeUsingFullReplacements,
|
||||
verticalEllipsis | "\\["
|
||||
] != 1,
|
||||
Return[{codeUsingFullReplacements}];
|
||||
];
|
||||
Return[
|
||||
Flatten[
|
||||
StringCases[
|
||||
codeUsingFullReplacements,
|
||||
before___ ~~ name : ((verticalEllipsis | "\\[") ~~ rest__ ~~ EndOfString) :>
|
||||
(
|
||||
(StringJoin[before, #1] &) /@
|
||||
Values[
|
||||
KeySelect[
|
||||
unicodeNamedCharactersReplacements,
|
||||
StringMatchQ[#1, name ~~ ___] &
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
];
|
||||
];
|
||||
|
||||
(* end the private context for WolframLanguageForJupyter *)
|
||||
End[]; (* `Private` *)
|
||||
|
||||
(************************************
|
||||
Get[] guard
|
||||
*************************************)
|
||||
|
||||
] (* WolframLanguageForJupyter`Private`$GotCompletionUtilities *)
|
@ -7,8 +7,8 @@ Description:
|
||||
the Mathematica REPL
|
||||
Symbols defined:
|
||||
Print,
|
||||
Throw,
|
||||
Catch,
|
||||
redirectPrint,
|
||||
redirectMessages,
|
||||
simulatedEvaluate
|
||||
*************************************************)
|
||||
|
||||
@ -27,14 +27,14 @@ If[
|
||||
files
|
||||
*************************************)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* loopState *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* loopState, applyHook, $canUseFrontEnd *)
|
||||
|
||||
(************************************
|
||||
wrapper for interacting
|
||||
with the cloud
|
||||
*************************************)
|
||||
|
||||
(* Interact is wrapper, open to the user, for asking that the result of the evaluation
|
||||
(* Interact is a wrapper, open to the user, for asking that the result of the evaluation
|
||||
be displayed as an embedded cloud object that can be interacted with *)
|
||||
(* set Interact to not evaluate its arguments *)
|
||||
SetAttributes[Interact, HoldAll];
|
||||
@ -46,53 +46,396 @@ If[
|
||||
(* begin the private context for WolframLanguageForJupyter *)
|
||||
Begin["`Private`"];
|
||||
|
||||
(************************************
|
||||
helper utilities for
|
||||
diagnosing and removing
|
||||
command wrappers
|
||||
(e.g., Interact[])
|
||||
*************************************)
|
||||
|
||||
(* check if expr is wrapped with Interact *)
|
||||
interactQ[expr___] := MatchQ[expr, Hold[Interact[___]]];
|
||||
SetAttributes[interactQ, HoldAll];
|
||||
|
||||
(* remove any Interact wrappers,
|
||||
apply $Pre,
|
||||
and bring in the Front End for the evaluation of expr *)
|
||||
uninteract[Interact[expr_]] := UsingFrontEnd[applyHook[$Pre, expr]];
|
||||
uninteract[expr_] := UsingFrontEnd[applyHook[$Pre, expr]];
|
||||
SetAttributes[uninteract, HoldAll];
|
||||
|
||||
(************************************
|
||||
version of Print that
|
||||
sends output to Jupyter
|
||||
*************************************)
|
||||
|
||||
(* redirect Print calls into a message to Jupyter, in order to print in the Jupyter notebook *)
|
||||
(* redirect Print calls into a message to Jupyter, in order to print in Jupyter *)
|
||||
(* TODO: review other methods: through EvaluationData or WSTP so we don't redefine Print *)
|
||||
(* TODO: remove this and just permanently set $Output to {..., loopState["WolframLanguageForJupyter-stdout"], ...} *)
|
||||
Unprotect[Print];
|
||||
Print[args___, opts:OptionsPattern[]] :=
|
||||
Print[ourArgs___, opts:OptionsPattern[]] :=
|
||||
Block[
|
||||
{
|
||||
$inPrint=True,
|
||||
$Output={OpenWrite[FormatType->OutputForm]}
|
||||
$inPrint = True,
|
||||
$Output
|
||||
},
|
||||
If[
|
||||
loopState["printFunction"] =!= False,
|
||||
$Output = {OpenWrite[FormatType -> OutputForm]};
|
||||
If[
|
||||
!FailureQ[First[$Output]],
|
||||
Print[args, opts];
|
||||
Print[ourArgs, opts];
|
||||
loopState["printFunction"][
|
||||
Import[$Output[[1,1]], "String"]
|
||||
];
|
||||
];
|
||||
Close[First[$Output]];
|
||||
Null
|
||||
];
|
||||
];
|
||||
] /; !TrueQ[$inPrint];
|
||||
Protect[Print];
|
||||
|
||||
(************************************
|
||||
versions of Throw and
|
||||
Catch that we can
|
||||
more easily intercept
|
||||
version of Echo that
|
||||
sends output to Jupyter
|
||||
*************************************)
|
||||
|
||||
Unprotect[Throw];
|
||||
Throw[value_] :=
|
||||
Throw[
|
||||
value,
|
||||
WolframLanguageForJupyter`Private`$ThrowLabel
|
||||
(* redirect Echo calls into a message to Jupyter, in order to print/echo in Jupyter *)
|
||||
Echo[""];
|
||||
Unprotect[Echo];
|
||||
DownValues[Echo] =
|
||||
Prepend[
|
||||
DownValues[Echo],
|
||||
HoldPattern[Echo[ourArgs___, opts:OptionsPattern[]]] :> Block[{$inEcho = True, $Notebooks = False}, Echo[ourArgs, opts]] /; !TrueQ[$inEcho]
|
||||
];
|
||||
Protect[Throw];
|
||||
Protect[Echo];
|
||||
|
||||
Unprotect[Catch];
|
||||
Catch[expr_] :=
|
||||
Catch[
|
||||
expr,
|
||||
WolframLanguageForJupyter`Private`$ThrowLabel
|
||||
(************************************
|
||||
version of Write that
|
||||
sends output to Jupyter
|
||||
*************************************)
|
||||
|
||||
(* redirect Write["stdout", ourArgs___] calls to Write[loopState["WolframLanguageForJupyter-stdout"], ourArgs___],
|
||||
in order to print in Jupyter *)
|
||||
Unprotect[Write];
|
||||
Write["stdout", ourArgs___, opts:OptionsPattern[]] :=
|
||||
Block[
|
||||
{
|
||||
$inWrite = True
|
||||
},
|
||||
If[
|
||||
loopState["WolframLanguageForJupyter-stdout"] =!= False,
|
||||
Write[loopState["WolframLanguageForJupyter-stdout"], ourArgs]
|
||||
];
|
||||
] /; !TrueQ[$inWrite];
|
||||
Protect[Write];
|
||||
(* redirect Write[{before___, "stdout", after___}, ourArgs___] calls to
|
||||
Write[{before___, "WolframLanguageForJupyter-stdout", after___}, ourArgs___],
|
||||
in order to print in Jupyter *)
|
||||
Unprotect[Write];
|
||||
Write[{before___, "stdout", after___}, ourArgs___, opts:OptionsPattern[]] :=
|
||||
Block[
|
||||
{
|
||||
$inWrite = True
|
||||
},
|
||||
If[
|
||||
loopState["WolframLanguageForJupyter-stdout"] =!= False,
|
||||
Write[{before, loopState["WolframLanguageForJupyter-stdout"], after}, ourArgs]
|
||||
];
|
||||
] /; !TrueQ[$inWrite];
|
||||
Protect[Write];
|
||||
|
||||
(************************************
|
||||
version of WriteString that
|
||||
sends output to Jupyter
|
||||
*************************************)
|
||||
|
||||
(* redirect WriteString["stdout", ourArgs___] calls to WriteString[loopState["WolframLanguageForJupyter-stdout"], ourArgs___],
|
||||
in order to print in Jupyter *)
|
||||
Unprotect[WriteString];
|
||||
WriteString["stdout", ourArgs___, opts:OptionsPattern[]] :=
|
||||
Block[
|
||||
{
|
||||
$inWriteString = True
|
||||
},
|
||||
If[
|
||||
loopState["WolframLanguageForJupyter-stdout"] =!= False,
|
||||
WriteString[loopState["WolframLanguageForJupyter-stdout"], ourArgs]
|
||||
];
|
||||
] /; !TrueQ[$inWriteString];
|
||||
Protect[WriteString];
|
||||
(* redirect WriteString[{before___, "stdout", after___}, ourArgs___] calls to
|
||||
WriteString[{before___, "WolframLanguageForJupyter-stdout", after___}, ourArgs___],
|
||||
in order to print in Jupyter *)
|
||||
Unprotect[WriteString];
|
||||
WriteString[{before___, "stdout", after___}, ourArgs___, opts:OptionsPattern[]] :=
|
||||
Block[
|
||||
{
|
||||
$inWriteString = True
|
||||
},
|
||||
If[
|
||||
loopState["WolframLanguageForJupyter-stdout"] =!= False,
|
||||
WriteString[{before, loopState["WolframLanguageForJupyter-stdout"], after}, ourArgs]
|
||||
];
|
||||
] /; !TrueQ[$inWriteString];
|
||||
Protect[WriteString];
|
||||
|
||||
(************************************
|
||||
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];
|
||||
|
||||
(************************************
|
||||
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[HoldForm[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[HoldForm[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[""];
|
||||
];
|
||||
SetAttributes[redirectMessages, HoldAll];
|
||||
|
||||
(************************************
|
||||
utilities for splitting
|
||||
input by Wolfram Language
|
||||
expression
|
||||
*************************************)
|
||||
|
||||
(* start parsing the input, and return an Association for keeping track of said parse *)
|
||||
startParsingInput[codeStr_] :=
|
||||
Module[
|
||||
{
|
||||
(* an Association for keeping track of the parse containing:
|
||||
"LinesLeft" - the lines of input left to be processed,
|
||||
"ExpressionsParsed" - the number of expressions parsed out so far,
|
||||
"ExpressionString" - an expression string generated by a pass of the parser,
|
||||
"SyntaxError" - a flag for if the expression string contains a syntax error,
|
||||
"ParseDone" - a flag for if the parse is done *)
|
||||
parseTrackerInit
|
||||
},
|
||||
|
||||
(* initialize parseTrackerInit *)
|
||||
parseTrackerInit = Association[];
|
||||
|
||||
(* add the annotated lines left, and the defaults *)
|
||||
AssociateTo[
|
||||
parseTrackerInit,
|
||||
{
|
||||
"LinesLeft" -> StringSplit[StringJoin[" ", codeStr], "\r\n" | "\n"],
|
||||
"ExpressionsParsed" -> 0,
|
||||
"ExpressionString" -> " ",
|
||||
"SyntaxError" -> False,
|
||||
"ParseDone" -> False
|
||||
}
|
||||
];
|
||||
|
||||
(* return the parse tracker *)
|
||||
Return[parseTrackerInit];
|
||||
];
|
||||
|
||||
(* parse out an expression *)
|
||||
parseIntoExpr[tracker_] :=
|
||||
Module[
|
||||
{
|
||||
(* the parse tracker to be updated with the results of this parse *)
|
||||
newTracker,
|
||||
|
||||
(* for storing the lines of input left to be processed, annotated with their line numbers *)
|
||||
annotatedLinesLeft,
|
||||
|
||||
(* for storing the current line position when moving through annotatedLinesLeft *)
|
||||
linePos,
|
||||
|
||||
(* for storing the result *)
|
||||
result,
|
||||
|
||||
(* for storing the concatenation of some lines of input *)
|
||||
exprStr
|
||||
},
|
||||
|
||||
(* initialize the parse tracker to be updated *)
|
||||
newTracker = tracker;
|
||||
|
||||
(* if there are no lines of input left to be processed,
|
||||
set newTracker["ParseDone"] to True and return the updated tracker *)
|
||||
If[Length[newTracker["LinesLeft"]] == 0,
|
||||
newTracker["ParseDone"] = True;
|
||||
(* return the updated tracker *)
|
||||
Return[newTracker];
|
||||
];
|
||||
|
||||
(* annotate the lines of input left to be processed with their line numbers *)
|
||||
annotatedLinesLeft =
|
||||
Partition[
|
||||
Riffle[
|
||||
newTracker["LinesLeft"],
|
||||
Range[Length[newTracker["LinesLeft"]]]
|
||||
],
|
||||
2
|
||||
];
|
||||
|
||||
(* start with a line position of 1 *)
|
||||
linePos = 1;
|
||||
result =
|
||||
(* fold until an expression is built, or there are no lines of input left *)
|
||||
Fold[
|
||||
(
|
||||
(* save the new line position *)
|
||||
linePos = Last[{##}][[2]] + 1;
|
||||
(* save the new expression string with a new line of input *)
|
||||
exprStr = StringJoin[First /@ {##}];
|
||||
(* if exprStr is syntactically correct, it represents a complete expression,
|
||||
and folding can be stopped *)
|
||||
If[SyntaxQ[exprStr],
|
||||
(* return the complete expression string *)
|
||||
Return[{exprStr, linePos, False}, Fold];,
|
||||
(* exprStr may be incomplete, keep going if possible *)
|
||||
{exprStr, linePos, True}
|
||||
]
|
||||
) &,
|
||||
(* starting value *)
|
||||
{"", -1, False},
|
||||
(* the annotated lines of input left to be processed *)
|
||||
annotatedLinesLeft
|
||||
];
|
||||
|
||||
AssociateTo[
|
||||
newTracker,
|
||||
{
|
||||
(* discard the lines of input processed *)
|
||||
"LinesLeft" -> newTracker["LinesLeft"][[result[[2]];;]],
|
||||
(* increment ExpressionsParsed *)
|
||||
"ExpressionsParsed" -> newTracker["ExpressionsParsed"] + 1,
|
||||
(* save the result generated *)
|
||||
"ExpressionString" -> result[[1]],
|
||||
(* save the syntactic correctness of the result *)
|
||||
"SyntaxError" -> result[[3]]
|
||||
}
|
||||
];
|
||||
|
||||
(* return the updated tracker *)
|
||||
Return[newTracker];
|
||||
];
|
||||
Protect[Catch];
|
||||
|
||||
(************************************
|
||||
main evaluation command
|
||||
@ -100,7 +443,7 @@ Protect[Catch];
|
||||
|
||||
(* evaluate input, and capture required information such as generated messages *)
|
||||
(* TODO: review other method: evaluate input through WSTP in another kernel *)
|
||||
simulatedEvaluate[expr___] :=
|
||||
simulatedEvaluate[codeStr_] :=
|
||||
Module[
|
||||
{
|
||||
(* for saving $Messages before changing it *)
|
||||
@ -110,18 +453,18 @@ Protect[Catch];
|
||||
(* the string form of the generated messages, obtained from stream *)
|
||||
generatedMessages,
|
||||
|
||||
(* the length of expr *)
|
||||
exprLength,
|
||||
(* for keeping track of parsing the input into separate expressions *)
|
||||
parseTracker,
|
||||
|
||||
(* a new version of expr that simulates lines *)
|
||||
exprWithLines,
|
||||
(* a raw evaluation result to be built, before Nulls have been removed *)
|
||||
rawEvaluationResult,
|
||||
|
||||
(* for storing a single expression string *)
|
||||
exprStr,
|
||||
|
||||
(* the result of evaluation *)
|
||||
evaluationResult,
|
||||
|
||||
(* for storing intermediate results *)
|
||||
intermediate,
|
||||
|
||||
(* for storing final results *)
|
||||
result,
|
||||
|
||||
@ -130,7 +473,8 @@ Protect[Catch];
|
||||
the result of evaluation ("EvaluationResult"),
|
||||
indices of the output lines of the result ("EvaluationResultOutputLineIndices"),
|
||||
the total number of indices consumed by this evaluation ("ConsumedIndices"),
|
||||
generated messages ("GeneratedMessages")
|
||||
generated messages ("GeneratedMessages"),
|
||||
if the input was one expression and wrapped with Interact[] ("InteractStatus")
|
||||
*)
|
||||
totalResult
|
||||
},
|
||||
@ -149,80 +493,102 @@ Protect[Catch];
|
||||
(* set $Messages to use the new stream *)
|
||||
$Messages = {stream};
|
||||
|
||||
(* obtain the length of expr *)
|
||||
exprLength = Length[Unevaluated[{expr}]];
|
||||
(* start the parse of the input *)
|
||||
parseTracker =
|
||||
startParsingInput[
|
||||
(* apply $PreRead to the input *)
|
||||
applyHook[$PreRead, codeStr]
|
||||
];
|
||||
|
||||
(* predefine the In[n] for the input *)
|
||||
(* initialize rawEvaluationResult to an empty list *)
|
||||
rawEvaluationResult = {};
|
||||
(* while the parse is not done, keep evaluating expressions in the input *)
|
||||
While[
|
||||
(
|
||||
parseTracker = parseIntoExpr[parseTracker];
|
||||
!parseTracker["ParseDone"]
|
||||
),
|
||||
|
||||
(* save the current expression string *)
|
||||
exprStr = parseTracker["ExpressionString"];
|
||||
|
||||
If[
|
||||
!parseTracker["SyntaxError"],
|
||||
(* increment $Line *)
|
||||
$Line++;
|
||||
(* set InString *)
|
||||
Unprotect[InString];
|
||||
InString[
|
||||
loopState["executionCount"] + parseTracker["ExpressionsParsed"] - 1
|
||||
] = exprStr;
|
||||
Protect[InString];
|
||||
];
|
||||
|
||||
(* evaluate the expression string *)
|
||||
(* regarding Internal`AllowExceptions, we need to generate the results and messages that
|
||||
are expected when a user evaluation is interrupted by behavior such as an uncaught Throw
|
||||
statement, while making sure that the simulated evaluation loop is not interrupted by the
|
||||
same behavior; my hope is that we can achieve this by using Internal`AllowExceptions as
|
||||
essentially a version of CheckAll that does not silence messages such as Throw::nocatch *)
|
||||
result =
|
||||
Internal`AllowExceptions[
|
||||
ToExpression[
|
||||
exprStr,
|
||||
InputForm,
|
||||
uninteract
|
||||
]
|
||||
];
|
||||
|
||||
If[
|
||||
!parseTracker["SyntaxError"],
|
||||
(* set the In[] for this expression *)
|
||||
Unprotect[In];
|
||||
(* for every line input in the input, set In[n], using placeholders *)
|
||||
Table[
|
||||
ReleaseHold[
|
||||
(* replace index with the number of the input line,
|
||||
and replace placeHolder with the element in expr corresponding to inIndex'th input line using Extract *)
|
||||
Replace[
|
||||
Hold[
|
||||
ToExpression[exprStr, InputForm, Hold],
|
||||
Hold[held_] :>
|
||||
SetDelayed[
|
||||
In[index],
|
||||
ReleaseHold[placeHolder]
|
||||
]
|
||||
In[
|
||||
loopState["executionCount"] + parseTracker["ExpressionsParsed"] - 1
|
||||
],
|
||||
{
|
||||
index -> loopState["executionCount"] + inIndex - 1,
|
||||
placeHolder ->
|
||||
Extract[
|
||||
Hold[{expr}],
|
||||
{1, inIndex},
|
||||
Hold
|
||||
held
|
||||
]
|
||||
},
|
||||
(* the level of index and placeHolder *)
|
||||
{3}
|
||||
]
|
||||
];,
|
||||
{inIndex, 1, exprLength}
|
||||
];
|
||||
Protect[In];
|
||||
|
||||
(* create a new version of expr that simulates lines *)
|
||||
exprWithLines =
|
||||
Table[
|
||||
$Line++;
|
||||
(* catch any Throws that were not handled by the input itself *)
|
||||
intermediate =
|
||||
Catch[
|
||||
ReleaseHold[
|
||||
Extract[
|
||||
Hold[{expr}],
|
||||
{1, inIndex},
|
||||
Hold
|
||||
]
|
||||
],
|
||||
_,
|
||||
WolframLanguageForJupyter`Private`$ThrowNoCatch[#1, #2] &
|
||||
];
|
||||
If[
|
||||
Head[intermediate] =!= WolframLanguageForJupyter`Private`$ThrowNoCatch,
|
||||
(* if we did not catch anything, set result to intermediate *)
|
||||
result = intermediate;,
|
||||
(* if we did catch something, obtain the correct held form of the Throw to return, and message *)
|
||||
If[intermediate[[2]] === WolframLanguageForJupyter`Private`$ThrowLabel,
|
||||
result = Replace[Hold[Throw[placeHolder]], {placeHolder -> intermediate[[1]]}, {2}];,
|
||||
result = Replace[Hold[Throw[placeHolder1, placeHolder2]], {placeHolder1 -> intermediate[[1]], placeHolder2 -> intermediate[[2]]}, {2}];
|
||||
];
|
||||
(* message *)
|
||||
Message[
|
||||
Throw::nocatch,
|
||||
StringTrim[ToString[result, OutputForm], "Hold[" | "]"]
|
||||
];
|
||||
];
|
||||
(* the overall result *)
|
||||
result
|
||||
(* apply $Post to the result *)
|
||||
result = applyHook[$Post, result];
|
||||
(* set the Out[] for this expression *)
|
||||
Unprotect[Out];
|
||||
Out[loopState["executionCount"] + parseTracker["ExpressionsParsed"] - 1] = result;
|
||||
Protect[Out];
|
||||
(* create the overall result with $PrePrint *)
|
||||
result = applyHook[$PrePrint, result];
|
||||
,
|
||||
{inIndex, 1, exprLength}
|
||||
(* syntax error *)
|
||||
result = $Failed;
|
||||
];
|
||||
|
||||
(* save the result in rawEvaluationResult *)
|
||||
AppendTo[rawEvaluationResult, result];
|
||||
];
|
||||
|
||||
(* add the Interact[] wrapper status of the input *)
|
||||
AssociateTo[
|
||||
totalResult,
|
||||
"InteractStatus" ->
|
||||
(
|
||||
(* if the input has no syntax errors,
|
||||
is made up of only one expression,
|
||||
and is wrapped with Interact[],
|
||||
mark "InteractStatus" as True
|
||||
*)
|
||||
parseTracker["ExpressionsParsed"] == 1 &&
|
||||
!parseTracker["SyntaxError"] &&
|
||||
(interactQ @ ToExpression[parseTracker["ExpressionString"], InputForm, Hold])
|
||||
)
|
||||
];
|
||||
|
||||
(* evaluate the input from Jupyter, removing Nulls from the Output *)
|
||||
evaluationResult = DeleteCases[exprWithLines, Null];
|
||||
evaluationResult = DeleteCases[rawEvaluationResult, Null];
|
||||
|
||||
(* preserve the locations of the output lines with respect to the Nulls *)
|
||||
AssociateTo[
|
||||
@ -230,19 +596,10 @@ Protect[Catch];
|
||||
"EvaluationResultOutputLineIndices" ->
|
||||
(
|
||||
(loopState["executionCount"] - 1) +
|
||||
Flatten[Position[exprWithLines, Except[Null], {1}, Heads -> False]]
|
||||
Flatten[Position[rawEvaluationResult, Except[Null], {1}, Heads -> False]]
|
||||
)
|
||||
];
|
||||
|
||||
(* set Out[n] *)
|
||||
Unprotect[Out];
|
||||
(* for every output line, set the Out with the corresponding index *)
|
||||
Table[
|
||||
Out[loopState["executionCount"] + outIndex - 1] = exprWithLines[[outIndex]];,
|
||||
{outIndex, 1, Length[exprWithLines]}
|
||||
];
|
||||
Protect[Out];
|
||||
|
||||
(* restore $Messages *)
|
||||
$Messages = oldMessages;
|
||||
|
||||
@ -254,7 +611,13 @@ Protect[Catch];
|
||||
(* add the total number of indices consumed by this evaluation *)
|
||||
AssociateTo[
|
||||
totalResult,
|
||||
"ConsumedIndices" -> exprLength
|
||||
"ConsumedIndices" ->
|
||||
(* if parseTracker["SyntaxError"] is true, one less index was consumed *)
|
||||
If[
|
||||
parseTracker["SyntaxError"],
|
||||
parseTracker["ExpressionsParsed"] - 1,
|
||||
parseTracker["ExpressionsParsed"]
|
||||
]
|
||||
];
|
||||
|
||||
(* add the result of the evaluation and any generated messages to totalResult *)
|
||||
|
@ -5,11 +5,21 @@ Description:
|
||||
Initialization for
|
||||
WolframLanguageForJupyter
|
||||
Symbols defined:
|
||||
$defaultPageWidth,
|
||||
loopState,
|
||||
applyHook,
|
||||
$canUseFrontEnd,
|
||||
$outputSetToTraditionalForm,
|
||||
$outputSetToTeXForm,
|
||||
$trueFormatType,
|
||||
$truePageWidth,
|
||||
connectionAssoc,
|
||||
bannerWarning,
|
||||
keyString,
|
||||
baseString,
|
||||
heartbeatString,
|
||||
verticalEllipsis,
|
||||
unicodeNamedCharactersReplacements,
|
||||
ioPubString,
|
||||
controlString,
|
||||
inputString,
|
||||
@ -49,12 +59,119 @@ If[
|
||||
*************************************)
|
||||
|
||||
(* make Short[] work *)
|
||||
SetOptions[$Output, PageWidth -> 89];
|
||||
$defaultPageWidth = 89;
|
||||
SetOptions[$Output, PageWidth -> $defaultPageWidth];
|
||||
|
||||
(* do not output messages to the jupyter notebook invocation
|
||||
$Messages = {};
|
||||
$Output = {}; *)
|
||||
|
||||
(************************************
|
||||
discover the named unicode
|
||||
characters and their names
|
||||
*************************************)
|
||||
|
||||
(* the vertical ellipsis character *)
|
||||
verticalEllipsis = FromCharacterCode[8942, "Unicode"];
|
||||
|
||||
(* pre-define the association of names and named characters as empty *)
|
||||
unicodeNamedCharactersReplacements = Association[];
|
||||
|
||||
Block[
|
||||
{
|
||||
(* the absolute file name for "UnicodeCharacters.tr" *)
|
||||
unicodeCharactersTRFileName,
|
||||
|
||||
(* raw data extracted from "UnicodeCharacters.tr" *)
|
||||
charactersAndTheirNames
|
||||
},
|
||||
|
||||
(* attempt to get the full location of "UnicodeCharacters.tr" *)
|
||||
unicodeCharactersTRFileName = UsingFrontEnd[System`Dump`unicodeCharactersTR];
|
||||
|
||||
(* try again if using System`Dump`unicodeCharactersTR does not work *)
|
||||
If[
|
||||
!StringQ[unicodeCharactersTRFileName],
|
||||
unicodeCharactersTRFileName =
|
||||
UsingFrontEnd[
|
||||
ToFileName[
|
||||
FrontEnd`FileName[
|
||||
{
|
||||
$InstallationDirectory,
|
||||
"SystemFiles",
|
||||
"FrontEnd",
|
||||
"TextResources"
|
||||
},
|
||||
"UnicodeCharacters.tr"
|
||||
]
|
||||
]
|
||||
];
|
||||
];
|
||||
|
||||
If[
|
||||
StringQ[unicodeCharactersTRFileName],
|
||||
charactersAndTheirNames =
|
||||
(
|
||||
(* parse the third item of a row (for a named character) into a list *)
|
||||
ReplacePart[
|
||||
#1,
|
||||
3 ->
|
||||
StringCases[
|
||||
(* remove extraneous parentheses *)
|
||||
StringTrim[
|
||||
#1[[3]],
|
||||
"(" | ")"
|
||||
],
|
||||
(* extract the escape sequences for the named character *)
|
||||
Longest[escSeq : (Except[WhitespaceCharacter] ..)] :>
|
||||
StringJoin[verticalEllipsis, StringTrim[escSeq, "$"], verticalEllipsis]
|
||||
]
|
||||
] &
|
||||
) /@
|
||||
(* only use lists with at least three items *)
|
||||
Select[
|
||||
(* parse the rows into their items (where each item is separated by two tabs) *)
|
||||
(StringSplit[#1, "\t\t"] &) /@
|
||||
(
|
||||
(* split unicodeCharactersTRFileName into its lines *)
|
||||
StringSplit[
|
||||
Import[unicodeCharactersTRFileName, "String"],
|
||||
"\n"
|
||||
(* drop the first row *)
|
||||
][[2 ;;]]
|
||||
),
|
||||
(Length[#1] >= 3) &
|
||||
];
|
||||
|
||||
(* parse the data into an association of names and the named characters they correspond to *)
|
||||
unicodeNamedCharactersReplacements =
|
||||
(* sort the keys by string length *)
|
||||
KeySort[
|
||||
(* sort the keys in the default manner *)
|
||||
KeySort[
|
||||
(* drop "empty names" *)
|
||||
KeyDrop[
|
||||
(* make an association *)
|
||||
Association[
|
||||
(* create a list of rules of names and named characters *)
|
||||
Thread[
|
||||
Rule[
|
||||
(Prepend[#1[[3]], #1[[2]]]),
|
||||
FromCharacterCode[FromDigits[StringDrop[#1[[1]], 2], 16], "Unicode"]
|
||||
]
|
||||
] & /@ charactersAndTheirNames
|
||||
],
|
||||
{
|
||||
StringJoin[Table[verticalEllipsis, {2}]],
|
||||
"\\[]"
|
||||
}
|
||||
]
|
||||
],
|
||||
(StringLength[#1] < StringLength[#2]) &
|
||||
];
|
||||
];
|
||||
];
|
||||
|
||||
(************************************
|
||||
various important symbols
|
||||
for use by
|
||||
@ -71,6 +188,16 @@ 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,
|
||||
|
||||
(* OutputStream for Jupyter's stdout *)
|
||||
"WolframLanguageForJupyter-stdout" -> False,
|
||||
|
||||
(* local to an iteration *)
|
||||
(* a received frame as an Association *)
|
||||
"frameAssoc" -> Null,
|
||||
@ -80,18 +207,58 @@ 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 *)
|
||||
applyHook[hook_, value_] /; Length[OwnValues[hook]] != 0 := hook[value];
|
||||
applyHook[hook_, value_] := value;
|
||||
Attributes[applyHook] := HoldAll;
|
||||
|
||||
(* can we use the Front End? *)
|
||||
$canUseFrontEnd := (UsingFrontEnd[$FrontEnd] =!= Null);
|
||||
|
||||
$outputSetToTraditionalForm := (Lookup[Options[$Output], FormatType] === TraditionalForm);
|
||||
$outputSetToTeXForm := (Lookup[Options[$Output], FormatType] === TeXForm);
|
||||
$trueFormatType :=
|
||||
If[
|
||||
$outputSetToTraditionalForm,
|
||||
TraditionalForm,
|
||||
If[$outputSetToTeXForm, TeXForm, #&]
|
||||
];
|
||||
$truePageWidth :=
|
||||
Replace[
|
||||
Lookup[Options[$Output], PageWidth],
|
||||
Except[
|
||||
Alternatives[
|
||||
Infinity,
|
||||
pageWidth_ /; ((IntegerQ[pageWidth]) && (pageWidth > 0))
|
||||
]
|
||||
] ->
|
||||
$defaultPageWidth
|
||||
];
|
||||
|
||||
(* hard-coded base64 rasterization of $Failed *)
|
||||
failedInBase64 = "iVBORw0KGgoAAAANSUhEUgAAADcAAAARCAIAAAD2TKM6AAAAhXpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjaVYvBDcMwDAP/mqIjyLJM2uMYiQNkg45fuu0n9yApgbT1vi97felp2dgxABc5csRU6P6juNciDXn+X9MfZGWgoRhTluEkhnJXkqKFN+LCAahYcIbnIV8gNQN3o86928QyPusLVffpbh/5eCey76LuBgAAAAlwSFlzAAALEwAACxMBAJqcGAAAADx0RVh0U29mdHdhcmUAQ3JlYXRlZCB3aXRoIHRoZSBXb2xmcmFtIExhbmd1YWdlIDogd3d3LndvbGZyYW0uY29tXKKmhQAAACF0RVh0Q3JlYXRpb24gVGltZQAyMDE5OjA3OjAyIDAzOjExOjExSFD8JQAAA8xJREFUSInVlk9IKl8Ux68ajjThLynMwCKLICSDjBbRxpVSFhFkCtFfBMMKAhcu3GXhQopaJBEFISjhsn9YUGE6zSJqkRURFVpKLQqNcYgZHectpszfez9exoP33u+zmnPm3HO/c865w2XRNA3+eth/WkBOfKjEMAzH8T8o5Sd8qJybm1tcXPz1jCaTyeVyMc8URXV3d2s0Go1GEwgEvppqZGQERdEPldfX1yiKIghydnaWCerp6WFn0dbW9mlekiTdbrfX62VMNpvd398/MDAQCATu7u6+qnJ9fT0SiQAA8gAAJycnzc3NnZ2dfD5foVA4nc7W1lYAQDqd1uv1drudWZOXl/dpXi6XG4lEOBwOY7JYLLVaDQDIz8//qsRs2AAAt9vd2NjodrsXFhYuLy9VKlXmNQRB/7wDwzAAIBaLmc1miUTC5/MHBwfPz8+ZSJvNJpVKpVKpTCazWq2fbkySpMViEYvFAoFAp9M9Pz8z/v39/bq6Oj6fr9FoMueEDQBQKpUoihqNxtvb2+Li4kwlAADxePz6nXQ6DQC4uroKBoPz8/MoimIYZjabmciuri6Hw+FwOMRi8ePj46cqLRaLx+NZXV1dW1u7v78fGxsDAESj0fb29qamJp/PV19f//Ly8hZN0zRN016vVy6Xs9nsoaGh19dXxqnT6QAAzFByOJxYLEa/Q5Lk6enp9PQ0BEE4jtNZ9Pb2Go1G+t9UVla6XK6MmUwmIQja3t5mzI2NjYKCApqm7Xa7RCJJpVKMv6yszOPx0DT9dnpUKtXx8fHW1tbm5ubk5GTmi0dHRymKoigqlUoVFhYCAAiCMBqNQqFweHj48PCQIIh4PP5p5b4jHA4TBNHS0sLlcrlcbkdHB0EQOI7f3NzU1tZmN/Oj4wcHBxiGMVpVKlVm1P6TqakpBEEuLi4QBJmdnc1RFovFoigqY4pEIhaL5ff7SZIkSTKVSpEkCcOwQCAIBoM/LmcDACYmJqqrq/v6+rRarcfjYRr9kzJUVVWJRKJEImGz2XJU2dDQsLu7m06nHx4eAAAwDKvVapPJxExwNBpFEAQAoFAoQqHQ0tISSZIrKytM8JvKnZ0dxoVhmM/n02q1mQL8uJ/BYPD7/UKhsKKioqioCIKgXFTq9fq9vT0YhuVyOVPU5eXlkpKS8vLy0tJSqVTK/POVSuX4+LjBYODxeE6ns6am5m19ZqKtVuvMzAydAwRBhEIhiqJyCc5AUVQoFEomk9lOHMfD4fB3qRKJxNPTU7aHRb/fiY6Ojng8nkwmy7GJv5MPlX8z/4+b2zdkknhkRbjZsAAAAABJRU5ErkJggg==";
|
||||
|
||||
(* obtain details on how to connect to Jupyter, from Jupyter's invocation of "KernelForWolframLanguageForJupyter.wl" *)
|
||||
connectionAssoc = ToString /@ Association[Import[$CommandLine[[4]], "JSON"]];
|
||||
(* NOTE: We remove the extension of the file to be imported so as to avoid accidentally loading the format "MXNet," which has a bad interaction with the paclet SetReplace (https://github.com/maxitg/SetReplace) as of June 22, 2020 *)
|
||||
Block[
|
||||
{noExtensionFile},
|
||||
noExtensionFile = CopyFile[$CommandLine[[4]], CreateFile[], OverwriteTarget -> True];
|
||||
connectionAssoc = ToString /@ Association[Import[noExtensionFile, "JSON"]];
|
||||
DeleteFile[noExtensionFile];
|
||||
];
|
||||
|
||||
(* warnings to display in kernel information *)
|
||||
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.",
|
||||
""
|
||||
];
|
||||
|
||||
@ -108,6 +275,44 @@ If[
|
||||
inputString = StringJoin[baseString, connectionAssoc["stdin_port"]];
|
||||
shellString = StringJoin[baseString, connectionAssoc["shell_port"]];
|
||||
|
||||
Block[
|
||||
{
|
||||
(* for storing the result of defining a new OutputStream method *)
|
||||
customOutputStreamMethod
|
||||
},
|
||||
|
||||
(* define an OutputStream method that will allow writing to Jupyter's stdout *)
|
||||
customOutputStreamMethod =
|
||||
DefineOutputStreamMethod[
|
||||
"for-WolframLanguageForJupyter-stdout",
|
||||
{
|
||||
"ConstructorFunction" -> Function[{name, isAppend, caller, opts}, {True, {}}],
|
||||
"WriteFunction" ->
|
||||
Function[
|
||||
{state, bytes},
|
||||
If[
|
||||
loopState["frameAssoc"] =!= Null,
|
||||
redirectPrint[loopState["frameAssoc"], FromCharacterCode[bytes]];
|
||||
];
|
||||
{Length[bytes], {}}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
(* if defining a new OutputStream method did not fail,
|
||||
open an OutputStream using the new method, and store it in loopState *)
|
||||
If[
|
||||
!FailureQ[customOutputStreamMethod],
|
||||
loopState["WolframLanguageForJupyter-stdout"] =
|
||||
OpenWrite["WolframLanguageForJupyter-stdout", Method -> "for-WolframLanguageForJupyter-stdout"];
|
||||
(* -- also, if opening the OutputStream failed, reset loopState["WolframLanguageForJupyter-stdout"] back to False *)
|
||||
If[
|
||||
FailureQ[loopState["WolframLanguageForJupyter-stdout"]],
|
||||
loopState["WolframLanguageForJupyter-stdout"] = False;
|
||||
];
|
||||
];
|
||||
];
|
||||
|
||||
(************************************
|
||||
open all the non-heartbeat
|
||||
sockets
|
||||
|
@ -6,7 +6,7 @@ Description:
|
||||
Entry point for WolframLanguageForJupyter
|
||||
kernels started by Jupyter
|
||||
Symbols defined:
|
||||
loopState
|
||||
loop
|
||||
*************************************************)
|
||||
|
||||
(************************************
|
||||
@ -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 *)
|
||||
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[];
|
||||
@ -83,19 +86,22 @@ loop[] :=
|
||||
(* provide the information *)
|
||||
loopState["replyContent"] =
|
||||
StringJoin[
|
||||
"{\"protocol_version\": \"5.3.0\",\"implementation\": \"WolframLanguageForJupyter\",\"implementation_version\": \"0.0.1\",\"language_info\": {\"name\": \"Wolfram Language\",\"version\": \"12.0\",\"mimetype\": \"application/vnd.wolfram.m\",\"file_extension\": \".m\",\"pygments_lexer\": \"python\",\"codemirror_mode\": \"python\"},\"banner\" : \"Wolfram Language/Wolfram Engine Copyright 2019",
|
||||
"{\"protocol_version\": \"5.3.0\",\"implementation\": \"WolframLanguageForJupyter\",\"implementation_version\": \"0.0.1\",\"language_info\": {\"name\": \"Wolfram Language\",\"version\": \"12.0\",\"mimetype\": \"application/vnd.wolfram.m\",\"file_extension\": \".m\",\"pygments_lexer\": \"mathematica\",\"codemirror_mode\": \"mathematica\"},\"banner\" : \"Wolfram Language/Wolfram Engine Copyright 2019",
|
||||
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 *)
|
||||
executeRequestHandler[];,
|
||||
(* use the tab-completion functionality to rewrite named character names *)
|
||||
"complete_request",
|
||||
(* completeRequestHandler will read and update loopState *)
|
||||
completeRequestHandler[];,
|
||||
(* if asking the kernel to shutdown, set doShutdown to True *)
|
||||
"shutdown_request",
|
||||
loopState["replyMsgType"] = "shutdown_reply";
|
||||
@ -114,14 +120,14 @@ loop[] :=
|
||||
"status",
|
||||
(* the status message content *)
|
||||
"{\"execution_state\":\"busy\"}",
|
||||
(* branch off *)
|
||||
True
|
||||
(* do not branch off *)
|
||||
False
|
||||
];
|
||||
(* 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"],
|
||||
@ -133,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[];
|
||||
];
|
||||
|
||||
@ -152,15 +159,15 @@ loop[] :=
|
||||
"status",
|
||||
(* the status message content *)
|
||||
"{\"execution_state\":\"idle\"}",
|
||||
(* branch off *)
|
||||
True
|
||||
(* do not branch off *)
|
||||
False
|
||||
]
|
||||
];
|
||||
|
||||
(* 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[]];
|
||||
];
|
||||
,
|
||||
_,
|
||||
@ -180,6 +187,7 @@ End[]; (* `Private` *)
|
||||
|
||||
(* end the WolframLanguageForJupyter package *)
|
||||
EndPackage[]; (* WolframLanguageForJupyter` *)
|
||||
(* $ContextPath = DeleteCases[$ContextPath, "WolframLanguageForJupyter`"]; *)
|
||||
|
||||
(************************************
|
||||
evaluate loop[]
|
||||
|
@ -63,11 +63,11 @@ If[
|
||||
(* see https://jupyter-client.readthedocs.io/en/stable/messaging.html *)
|
||||
StringCases[
|
||||
frameStr,
|
||||
ident1___ ~~ "<IDS|MSG>" ~~ ___ ~~
|
||||
"{" ~~ json2___ ~~ "}" ~~
|
||||
"{" ~~ json3___ ~~ "}" ~~
|
||||
"{" ~~ json4___ ~~ "}" ~~
|
||||
"{" ~~ json5___ ~~ "}" ~~
|
||||
Shortest[ident1___] ~~ "<IDS|MSG>" ~~ Shortest[___] ~~
|
||||
"{" ~~ Shortest[json2___] ~~ "}" ~~
|
||||
"{" ~~ Shortest[json3___] ~~ "}" ~~
|
||||
"{" ~~ Shortest[json4___] ~~ "}" ~~
|
||||
"{" ~~ Shortest[json5___] ~~ "}" ~~
|
||||
EndOfString :>
|
||||
Prepend[
|
||||
(* add back in the brackets *)
|
||||
@ -108,7 +108,7 @@ If[
|
||||
(* the message type to be used for the reply message frame *)
|
||||
replyType_String,
|
||||
(* the content to be used for the reply message frame *)
|
||||
replyContent_String,
|
||||
replyContent : (_String | _ByteArray),
|
||||
(* whether to list sourceFrame as a parent for the reply message frame *)
|
||||
branchOff:(True|False)
|
||||
] :=
|
||||
@ -157,7 +157,7 @@ If[
|
||||
result["header"],
|
||||
result["pheader"],
|
||||
result["metadata"],
|
||||
result["content"]
|
||||
If[StringQ[result["content"]], result["content"], ByteArrayToString[result["content"]]]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
@ -9,8 +9,10 @@ Description:
|
||||
notebooks
|
||||
Symbols defined:
|
||||
textQ,
|
||||
toOutText,
|
||||
toOutImage
|
||||
toText,
|
||||
toOutTextHTML,
|
||||
toImageData,
|
||||
toOutImageHTML
|
||||
*************************************************)
|
||||
|
||||
(************************************
|
||||
@ -22,6 +24,17 @@ If[
|
||||
|
||||
WolframLanguageForJupyter`Private`$GotOutputHandlingUtilities = True;
|
||||
|
||||
(************************************
|
||||
load required
|
||||
WolframLanguageForJupyter
|
||||
files
|
||||
*************************************)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* $canUseFrontEnd, $outputSetToTeXForm,
|
||||
$outputSetToTraditionalForm,
|
||||
$trueFormatType, $truePageWidth,
|
||||
failedInBase64 *)
|
||||
|
||||
(************************************
|
||||
private symbols
|
||||
*************************************)
|
||||
@ -29,14 +42,37 @@ If[
|
||||
(* begin the private context for WolframLanguageForJupyter *)
|
||||
Begin["`Private`"];
|
||||
|
||||
(************************************
|
||||
helper utility for converting
|
||||
an expression into a
|
||||
textual form
|
||||
*************************************)
|
||||
|
||||
(* convert an expression into a textual form,
|
||||
using as much of the options already set for $Output as possible for ToString *)
|
||||
(* NOTE: toOutTextHTML used to call toStringUsingOutput *)
|
||||
toStringUsingOutput[expr_] :=
|
||||
ToString[
|
||||
expr,
|
||||
Sequence @@
|
||||
Cases[
|
||||
Options[$Output],
|
||||
Verbatim[Rule][opt_, val_] /;
|
||||
MemberQ[
|
||||
Keys[Options[ToString]],
|
||||
opt
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
(************************************
|
||||
helper utility for determining
|
||||
if a result should be
|
||||
displayed as text or an image
|
||||
*************************************)
|
||||
|
||||
(* check if a string contains any private use area characters *)
|
||||
containsPUAQ[str_] :=
|
||||
(* check if a string contains any private use area characters *)
|
||||
containsPUAQ[str_] :=
|
||||
AnyTrue[
|
||||
ToCharacterCode[str, "Unicode"],
|
||||
(57344 <= #1 <= 63743 || 983040 <= #1 <= 1048575 || 1048576 <= #1 <= 1114111) &
|
||||
@ -61,16 +97,32 @@ containsPUAQ[str_] :=
|
||||
|
||||
(* if we cannot use the frontend, use text *)
|
||||
If[
|
||||
UsingFrontEnd[$FrontEnd] === Null,
|
||||
!$canUseFrontEnd,
|
||||
Return[True];
|
||||
];
|
||||
|
||||
(* if the expression is wrapped with InputForm or OutputForm, automatically format as text *)
|
||||
(* save the head of the expression *)
|
||||
exprHead = Head[expr];
|
||||
|
||||
(* if the expression is wrapped with InputForm or OutputForm,
|
||||
automatically format as text *)
|
||||
If[exprHead === InputForm || exprHead === OutputForm,
|
||||
Return[True]
|
||||
];
|
||||
|
||||
(* if the FormatType of $Output is set to TeXForm, or if the expression is wrapped with TeXForm,
|
||||
and the expression has an acceptable textual form, format as text *)
|
||||
If[($outputSetToTeXForm || exprHead == TeXForm) && !containsPUAQ[ToString[expr]],
|
||||
Return[True];
|
||||
];
|
||||
|
||||
(* if the FormatType of $Output is set to TraditionalForm,
|
||||
or if the expression is wrapped with TraditionalForm,
|
||||
do not use text *)
|
||||
If[$outputSetToTraditionalForm || exprHead === TraditionalForm,
|
||||
Return[False]
|
||||
];
|
||||
|
||||
(* breakdown expr into atomic objects organized by their Head *)
|
||||
pObjects =
|
||||
GroupBy[
|
||||
@ -129,36 +181,165 @@ containsPUAQ[str_] :=
|
||||
results as text and images
|
||||
*************************************)
|
||||
|
||||
(* generate the textual form of a result using a given page width *)
|
||||
(* 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_, pageWidth_] :=
|
||||
ToString[
|
||||
(* make sure to apply $trueFormatType to the result if the result is not already headed by TeXForm *)
|
||||
If[
|
||||
Head[result] === TeXForm,
|
||||
result,
|
||||
$trueFormatType[result]
|
||||
],
|
||||
(* also, use the given page width *)
|
||||
PageWidth -> pageWidth
|
||||
];
|
||||
(* generate the textual form of a result using the current PageWidth setting for $Output *)
|
||||
toText[result_] := toText[result, $truePageWidth];
|
||||
|
||||
(* generate HTML for the textual form of a result *)
|
||||
toOutText[result_] :=
|
||||
toOutTextHTML[result_] :=
|
||||
Module[
|
||||
{
|
||||
(* if the result should be marked as TeX *)
|
||||
isTeX
|
||||
},
|
||||
(* check if the 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[
|
||||
!isTeX,
|
||||
{
|
||||
(* preformatted *)
|
||||
"<pre style=\"",
|
||||
(* use Courier *)
|
||||
StringJoin[{"&#", ToString[#1], ";"} & /@ ToCharacterCode["font-family: \"Courier New\",Courier,monospace;", "Unicode"]],
|
||||
"\">",
|
||||
"\">"
|
||||
},
|
||||
{}
|
||||
],
|
||||
|
||||
(* mark the text as TeX, if is TeX *)
|
||||
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 *)
|
||||
StringJoin[{"&#", ToString[#1], ";"} & /@ ToCharacterCode[ToString[result], "Unicode"]],
|
||||
({"&#", ToString[#1], ";"} & /@
|
||||
ToCharacterCode[
|
||||
If[
|
||||
isTeX,
|
||||
(* if the result is TeX, do not allow line breaks *)
|
||||
toText[result, Infinity],
|
||||
(* otherwise, just call toText *)
|
||||
toText[result]
|
||||
],
|
||||
"Unicode"
|
||||
]),
|
||||
|
||||
(* mark the text as TeX, if is TeX *)
|
||||
If[isTeX, "$$", ""],
|
||||
|
||||
(* mark this result as preformatted only if it isn't TeX *)
|
||||
If[
|
||||
!isTeX,
|
||||
{
|
||||
(* end the element *)
|
||||
"</pre>"
|
||||
},
|
||||
{}
|
||||
]
|
||||
]
|
||||
];
|
||||
];
|
||||
|
||||
(* generate a byte array of image data for the rasterized form of a result *)
|
||||
toImageData[result_] :=
|
||||
Module[
|
||||
{
|
||||
(* the preprocessed form of a result *)
|
||||
preprocessedForm
|
||||
},
|
||||
(* preprocess the result *)
|
||||
If[
|
||||
Head[result] === Manipulate,
|
||||
preprocessedForm = result;
|
||||
,
|
||||
preprocessedForm = Rasterize[result];
|
||||
];
|
||||
(* if the preprocessing failed, return $Failed *)
|
||||
If[
|
||||
FailureQ[preprocessedForm],
|
||||
Return[$Failed];
|
||||
];
|
||||
(* now return preprocessedForm as a byte array corresponding to the PNG format *)
|
||||
Return[
|
||||
ExportByteArray[
|
||||
preprocessedForm,
|
||||
"PNG"
|
||||
]
|
||||
];
|
||||
];
|
||||
|
||||
(* generate HTML for the rasterized form of a result *)
|
||||
toOutImage[result_] :=
|
||||
toOutImageHTML[result_] :=
|
||||
Module[
|
||||
{
|
||||
(* the rasterization of result *)
|
||||
imageData,
|
||||
(* the rasterization of result in base 64 *)
|
||||
imageDataInBase64
|
||||
},
|
||||
|
||||
(* rasterize the result *)
|
||||
imageData =
|
||||
toImageData[
|
||||
$trueFormatType[result]
|
||||
];
|
||||
If[
|
||||
!FailureQ[imageData],
|
||||
(* if the rasterization did not fail, convert it to base 64 *)
|
||||
imageInBase64 = BaseEncode[imageData];
|
||||
,
|
||||
(* if the rasterization did fail, try to rasterize result with Shallow *)
|
||||
imageData =
|
||||
toImageData[
|
||||
$trueFormatType[Shallow[result]]
|
||||
];
|
||||
If[
|
||||
!FailureQ[imageData],
|
||||
(* if the rasterization did not fail, convert it to base 64 *)
|
||||
imageInBase64 = BaseEncode[imageData];
|
||||
,
|
||||
(* if the rasterization did fail, try to rasterize $Failed *)
|
||||
imageData =
|
||||
toImageData[
|
||||
$trueFormatType[$Failed]
|
||||
];
|
||||
If[
|
||||
!FailureQ[imageData],
|
||||
(* if the rasterization did not fail, convert it to base 64 *)
|
||||
imageInBase64 = BaseEncode[imageData];
|
||||
,
|
||||
(* if the rasterization did fail, use a hard-coded base64 rasterization of $Failed *)
|
||||
imageInBase64 = failedInBase64;
|
||||
];
|
||||
];
|
||||
];
|
||||
|
||||
(* return HTML for the rasterized form of result *)
|
||||
Return[
|
||||
StringJoin[
|
||||
(* display a inlined PNG image encoded in base64 *)
|
||||
"<img alt=\"Output\" src=\"data:image/png;base64,",
|
||||
(* the rasterized form of the result, converted to base64 *)
|
||||
BaseEncode[
|
||||
UsingFrontEnd[ExportByteArray[
|
||||
If[Head[result] === Manipulate, result, Rasterize[result]],
|
||||
"PNG"
|
||||
]]
|
||||
],
|
||||
imageInBase64,
|
||||
(* end the element *)
|
||||
"\">"
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
(* end the private context for WolframLanguageForJupyter *)
|
||||
|
@ -2,12 +2,12 @@
|
||||
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:
|
||||
interactQ,
|
||||
uninteract,
|
||||
executeRequestHandler
|
||||
isCompleteRequestHandler,
|
||||
executeRequestHandler,
|
||||
completeRequestHandler
|
||||
*************************************************)
|
||||
|
||||
(************************************
|
||||
@ -30,9 +30,12 @@ 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 *)
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "OutputHandlingUtilities.wl"}]]; (* textQ, toOutTextHTML, toOutImageHTML,
|
||||
toText, containsPUAQ *)
|
||||
|
||||
Get[FileNameJoin[{DirectoryName[$InputFileName], "CompletionUtilities.wl"}]]; (* rewriteNamedCharacters *)
|
||||
|
||||
(************************************
|
||||
private symbols
|
||||
@ -42,32 +45,63 @@ If[
|
||||
Begin["`Private`"];
|
||||
|
||||
(************************************
|
||||
helper utilities for the
|
||||
handler for
|
||||
execute_requests for
|
||||
diagnosing command
|
||||
wrappers (Interact[___])
|
||||
and redirecting to
|
||||
simulatedEvaluate
|
||||
handler for is_complete_requests
|
||||
*************************************)
|
||||
|
||||
(* check if expr is wrapped with Interact *)
|
||||
interactQ[expr___] := MatchQ[expr, Hold[Interact[___]]];
|
||||
SetAttributes[interactQ, HoldAll];
|
||||
(* 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
|
||||
},
|
||||
|
||||
(* remove any Interact wrappers, and evaluate expr *)
|
||||
uninteract[Interact[expr___]] := simulatedEvaluate[expr];
|
||||
uninteract[expr___] := simulatedEvaluate[expr];
|
||||
SetAttributes[uninteract, HoldAll];
|
||||
(* 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[
|
||||
{
|
||||
(* message formatter function *)
|
||||
messageFormatter,
|
||||
|
||||
(* content of the desired frame to send on the IO Publish socket *)
|
||||
ioPubReplyContent,
|
||||
|
||||
@ -81,91 +115,122 @@ 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"],
|
||||
messageFormatter[messageName_, messageText_] :=
|
||||
redirectMessages[
|
||||
loopState["frameAssoc"],
|
||||
messageName,
|
||||
messageText,
|
||||
(* add a newline if loopState["isCompleteRequestSent"] *)
|
||||
loopState["isCompleteRequestSent"]
|
||||
];
|
||||
SetAttributes[messageFormatter, HoldAll];
|
||||
Internal`$MessageFormatter = messageFormatter;
|
||||
];
|
||||
|
||||
(* evaluate the input, and store the total result in totalResult *)
|
||||
totalResult = simulatedEvaluate[loopState["frameAssoc"]["content"]["code"]];
|
||||
|
||||
(* restore printFunction to False *)
|
||||
loopState["printFunction"] = False;
|
||||
|
||||
(* unset messageFormatter and Internal`$MessageFormatter *)
|
||||
Unset[messageFormatter];
|
||||
Unset[Internal`$MessageFormatter];
|
||||
|
||||
(* set the appropriate reply type *)
|
||||
loopState["replyMsgType"] = "execute_reply";
|
||||
|
||||
(* set the content of the reply to information about WolframLanguageForJupyter's execution of the input *)
|
||||
loopState["replyContent"] =
|
||||
ExportString[
|
||||
(* kind of self-explanatory *)
|
||||
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 = ToExpression[loopState["frameAssoc"]["content"]["code"], InputForm, uninteract];
|
||||
|
||||
(* restore printFunction to empty *)
|
||||
loopState["printFunction"] = Function[#;];
|
||||
|
||||
(* if totalResult fails (most likely due to a syntax error in the input),
|
||||
build a failure object form of totalResult, and use any messages generated by ToExpression *)
|
||||
If[FailureQ[totalResult],
|
||||
totalResult = Association[];
|
||||
(* failed object settings: *)
|
||||
totalResult["EvaluationResult"] = {$Failed};
|
||||
totalResult["EvaluationResultOutputLineIndices"] = {loopState["executionCount"]};
|
||||
(* when a failure like this occurs, do not increment loopState["executionCount"] *)
|
||||
totalResult["ConsumedIndices"] = 0;
|
||||
(* capture any messages generated by ToExpression *)
|
||||
totalResult["GeneratedMessages"] =
|
||||
StringJoin[
|
||||
EvaluationData[
|
||||
ToExpression[loopState["frameAssoc"]["content"]["code"], InputForm]
|
||||
]["MessagesText"]
|
||||
(* 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 *)
|
||||
@ -175,18 +240,11 @@ 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[
|
||||
(* interactQ checks if the input was wrapped with Interact, which is used when the output should be displayed as an embedded cloud object *)
|
||||
TrueQ[interactQ[ToExpression[loopState["frameAssoc"]["content"]["code"], InputForm, Hold]]] &&
|
||||
(* check if the input was wrapped with Interact,
|
||||
which is used when the output should be displayed as an embedded cloud object *)
|
||||
TrueQ[totalResult["InteractStatus"]] &&
|
||||
(* check if we are logged into the Cloud *)
|
||||
$CloudConnected,
|
||||
(* prepare the content for a reply message frame to be sent on the IO Publish socket *)
|
||||
@ -197,7 +255,8 @@ If[
|
||||
"execution_count" -> First[totalResult["EvaluationResultOutputLineIndices"]],
|
||||
(* HTML code to embed output uploaded to the Cloud in the Jupyter notebook *)
|
||||
"data" ->
|
||||
{"text/html" ->
|
||||
{
|
||||
"text/html" ->
|
||||
StringJoin[
|
||||
(* display any generated messages as inlined PNG images encoded in base64 *)
|
||||
"<div><img alt=\"\" src=\"data:image/png;base64,",
|
||||
@ -209,32 +268,39 @@ If[
|
||||
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[
|
||||
ioPubReplyContent = ExportByteArray[
|
||||
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" ->
|
||||
(* if there are multiple results, output as Out[1/n]:, Out[2/n]:, ..., Out[n/n]: *)
|
||||
(* otherwise, just display the single error message and/or single result *)
|
||||
{
|
||||
(* generate HTML for the results and messages *)
|
||||
"text/html" ->
|
||||
If[
|
||||
loopState["isCompleteRequestSent"],
|
||||
(* if an is_complete_request has been sent, assume jupyter-console is running the kernel,
|
||||
and do not generate HTML *)
|
||||
"",
|
||||
(* otherwise, output the results in a grid *)
|
||||
If[
|
||||
Length[totalResult["EvaluationResult"]] > 1,
|
||||
StringJoin[
|
||||
@ -242,7 +308,7 @@ If[
|
||||
"<style>
|
||||
.grid-container {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -254,20 +320,11 @@ If[
|
||||
(* display the output lines *)
|
||||
Table[
|
||||
{
|
||||
(* start the left grid item *)
|
||||
"<div class=\"grid-item\">
|
||||
<div class=\"prompt output_prompt\" style=\"text-align:left;padding:0em;padding-right:20px;line-height:20px;\">
|
||||
Out[",
|
||||
(* show the output index for this output line *)
|
||||
ToString[totalResult["EvaluationResultOutputLineIndices"][[outIndex]]],
|
||||
(* end the left grid item, and start the right grid item *)
|
||||
"]:
|
||||
</div>
|
||||
</div>
|
||||
<div class=\"grid-item\">",
|
||||
(* start the grid item *)
|
||||
"<div class=\"grid-item\">",
|
||||
(* show the output line *)
|
||||
toOut[totalResult["EvaluationResult"][[outIndex]]],
|
||||
(* end the right grid item *)
|
||||
(* end the grid item *)
|
||||
"</div>"
|
||||
},
|
||||
{outIndex, 1, Length[totalResult["EvaluationResult"]]}
|
||||
@ -291,9 +348,22 @@ If[
|
||||
"</div>"
|
||||
]
|
||||
]
|
||||
],
|
||||
(* provide, as a backup, plain text for the results *)
|
||||
"text/plain" ->
|
||||
StringJoin[
|
||||
Table[
|
||||
{
|
||||
toText[totalResult["EvaluationResult"][[outIndex]]],
|
||||
(* -- also, suppress newline if this is the last result *)
|
||||
If[outIndex != Length[totalResult["EvaluationResult"]], "\n", ""]
|
||||
},
|
||||
{outIndex, 1, Length[totalResult["EvaluationResult"]]}
|
||||
]
|
||||
]
|
||||
},
|
||||
(* no metadata *)
|
||||
"metadata" -> {"text/html" -> {}}
|
||||
"metadata" -> {"text/html" -> {}, "text/plain" -> {}}
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
@ -317,6 +387,54 @@ If[
|
||||
loopState["executionCount"] += totalResult["ConsumedIndices"];
|
||||
];
|
||||
|
||||
(************************************
|
||||
handler for complete_requests
|
||||
*************************************)
|
||||
|
||||
(* handle complete_request message frames received on the shell socket *)
|
||||
completeRequestHandler[] :=
|
||||
Module[
|
||||
{
|
||||
(* for storing the code string to offer completion suggestions on *)
|
||||
codeStr
|
||||
},
|
||||
(* get the code string to rewrite the named characters of, ending at the cursor *)
|
||||
codeStr =
|
||||
StringTake[
|
||||
loopState["frameAssoc"]["content"]["code"],
|
||||
{
|
||||
1,
|
||||
loopState["frameAssoc"]["content"]["cursor_pos"]
|
||||
}
|
||||
];
|
||||
(* set the appropriate reply type *)
|
||||
loopState["replyMsgType"] = "complete_reply";
|
||||
(* set the content of the reply to a list of rewrites for any named characters in the code string *)
|
||||
loopState["replyContent"] =
|
||||
ByteArrayToString[
|
||||
ExportByteArray[
|
||||
Association[
|
||||
"matches" ->
|
||||
DeleteDuplicates[
|
||||
Prepend[
|
||||
Select[
|
||||
rewriteNamedCharacters[codeStr],
|
||||
(!containsPUAQ[#1])&
|
||||
],
|
||||
codeStr
|
||||
]
|
||||
],
|
||||
"cursor_start" -> 0,
|
||||
"cursor_end" -> StringLength[codeStr],
|
||||
"metadata" -> {},
|
||||
"status" -> "ok"
|
||||
],
|
||||
"JSON",
|
||||
"Compact" -> True
|
||||
]
|
||||
];
|
||||
];
|
||||
|
||||
(* end the private context for WolframLanguageForJupyter *)
|
||||
End[]; (* `Private` *)
|
||||
|
||||
|
@ -74,7 +74,7 @@ If[
|
||||
|
||||
socketWriteFunction[
|
||||
socket,
|
||||
StringToByteArray[frame["content"]],
|
||||
If[ByteArrayQ[frame["content"]], frame["content"], StringToByteArray[frame["content"]]],
|
||||
"Multipart" -> False
|
||||
];
|
||||
];
|
||||
|
@ -139,7 +139,7 @@ splitPath :=
|
||||
|
||||
(* find Jupyter installation path *)
|
||||
(* returns above *)
|
||||
findJupyerPath[] :=
|
||||
findJupyterPath[] :=
|
||||
SelectFirst[
|
||||
splitPath,
|
||||
(* check every directory in PATH to see if a Jupyter binary is a member *)
|
||||
@ -211,7 +211,7 @@ configureJupyter[specs_Association, removeQ_?BooleanQ, removeAllQ_?BooleanQ] :=
|
||||
(* if no Jupyter installation path provided, determine it from PATH *)
|
||||
If[
|
||||
MissingQ[jupyterPath],
|
||||
jupyterPath = findJupyerPath[];
|
||||
jupyterPath = findJupyterPath[];
|
||||
(* if Jupyter not on PATH, message *)
|
||||
If[MissingQ[jupyterPath],
|
||||
Message[ConfigureJupyter::notfound, "Jupyter"];
|
||||
|
@ -160,7 +160,7 @@ attemptPathRegeneration[] := If[
|
||||
|
||||
(* find Jupyter installation path *)
|
||||
(* returns kernel IDs in Jupyter *)
|
||||
findJupyerPath[] :=
|
||||
findJupyterPath[] :=
|
||||
SelectFirst[
|
||||
splitPath,
|
||||
(* check every directory in PATH to see if a Jupyter binary is a member *)
|
||||
@ -231,7 +231,7 @@ configureJupyter[specs_Association, removeQ_?BooleanQ, removeAllQ_?BooleanQ] :=
|
||||
(* if no Jupyter installation path provided, determine it from PATH *)
|
||||
If[
|
||||
MissingQ[jupyterPath],
|
||||
jupyterPath = findJupyerPath[];
|
||||
jupyterPath = findJupyterPath[];
|
||||
(* if Jupyter not on PATH, message *)
|
||||
If[MissingQ[jupyterPath],
|
||||
Print[notfound];
|
||||
|
50
extras/custom.js
Normal file
50
extras/custom.js
Normal file
@ -0,0 +1,50 @@
|
||||
/* (adapted) from https://stackoverflow.com/a/19961519 by Erik Aigner */
|
||||
HTMLTextAreaElement.prototype.insertAtCaret = function (text) {
|
||||
text = text || '';
|
||||
if(document.selection) {
|
||||
// IE
|
||||
this.focus();
|
||||
var sel = document.selection.createRange();
|
||||
sel.text = text;
|
||||
}
|
||||
else if(this.selectionStart || this.selectionStart === 0) {
|
||||
// Others
|
||||
var startPos = this.selectionStart;
|
||||
var endPos = this.selectionEnd;
|
||||
this.value = this.value.substring(0, startPos) + text + this.value.substring(endPos, this.value.length);
|
||||
this.selectionStart = startPos + text.length;
|
||||
this.selectionEnd = startPos + text.length;
|
||||
}
|
||||
else {
|
||||
this.value += text;
|
||||
}
|
||||
};
|
||||
|
||||
/* (adapted) from https://stackoverflow.com/a/51114347 by bambam */
|
||||
function redirectEsc(event) {
|
||||
if(event.which == 27)
|
||||
{
|
||||
event.target.insertAtCaret(
|
||||
/* the vertical ellipsis character */
|
||||
String.fromCharCode(8942)
|
||||
);
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/* (adapted) from https://stackoverflow.com/a/51114347 by bambam */
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
|
||||
Array.from(
|
||||
document.querySelectorAll('.input_area')
|
||||
).forEach(
|
||||
textarea =>
|
||||
{
|
||||
textarea.removeEventListener('keydown', redirectEsc);
|
||||
textarea.addEventListener('keydown', redirectEsc);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
observer.observe(document, {childList:true, subtree:true});
|
Loading…
x
Reference in New Issue
Block a user