Compare commits

...

57 Commits

Author SHA1 Message Date
cc.wr
9a26ac7874 Increment paclet version to 0.9.3 2022-02-19 04:55:04 -05:00
cc.wr
17171b757a Fix typo in variable name 2022-02-07 12:36:53 -05:00
cc-wr
551351a707
Merge pull request #117 from WolframResearch/bugfix/throw-catch
Stop redefining Throw and Catch, and use a different method to handle uncaught tagless Throw statements
2021-10-21 16:09:40 -04:00
cc.wr
8208cbc746 Add a comment attempting to explain the use of Internal`AllowExceptions 2021-10-13 17:15:50 -04:00
cc.wr
0fd044f0c4 Stop redefining Throw and Catch, and use a different method to handle uncaught tagless Throw statements 2021-10-13 16:19:24 -04:00
cc-wr
1fb52c048d
Merge pull request #104 from WolframResearch/bugfix/Echo-simulation
Fix simulation of Echo for WolframLanguageForJupyter
2020-12-15 12:18:31 -05:00
cc-wr
a222be43cc
Merge pull request #102 from WolframResearch/bugfix/fix-message-frame-parsing-issue
Fix issue regarding message frame parsing
2020-12-15 12:18:06 -05:00
cc-wr
65f9950531
Merge pull request #84 from WolframResearch/bugfix/PageWidth-Infinity
Do not allow line breaks when generating TeX source for `text/html` results; allow setting PageWidth to Infinity
2020-12-15 12:17:54 -05:00
cc-wr
12fb4bb728
Merge pull request #92 from WolframResearch/bugfix/StopLoadingMXNetLink
Stop accidentally loading the paclet MXNetLink, which has a bad interaction with the paclet SetReplace
2020-12-15 12:17:20 -05:00
cc-wr
61835bab6c Fix simulation of Echo for WolframLanguageForJupyter 2020-12-08 11:07:28 -05:00
cc-wr
4c26eca649 Fix issue regarding message frame parsing 2020-12-08 07:21:28 -05:00
cc-wr
d477809f50 Stop accidentally loading the paclet MXNetLink, which has a bad interaction with the paclet SetReplace 2020-06-22 14:41:58 -04:00
cc-wr
10c8ff5991 Do not allow line breaks when generating TeX source for text/html results; allow setting PageWidth to Infinity 2020-03-09 12:27:01 -04:00
cc-wr
5587c6b763
Merge pull request #81 from WolframResearch/feature/README-md-cleanup
Clean up README.md
2020-02-07 06:36:32 -05:00
cc-wr
ab6ab8a2e7
Merge branch 'master' into feature/README-md-cleanup 2020-02-07 06:35:30 -05:00
cc-wr
c7673eb201
Merge pull request #71 from vegerot/patch-3
Documenting features
2020-02-07 06:34:25 -05:00
cc-wr
4ef757301d Increment PacletInfo.m 2020-02-04 18:19:02 -05:00
cc-wr
a96cd3e345
Merge pull request #79 from WolframResearch/bugfix/WriteString-Write-stdout
Redirect all string references to the stdout stream to a new WolframLanguageForJupyter-stdout stream
2020-02-04 17:52:33 -05:00
cc-wr
5825796109
Merge pull request #75 from WolframResearch/bugfix/no-line-breaking
Use the current PageWidth setting for $Output when converting a result into a string
2020-02-04 17:52:16 -05:00
cc-wr
03d50854c8
Merge pull request #73 from WolframResearch/bugfix/jupyter-console-problems
Fix some small problems with WolframLanguageForJupyter's jupyter-console mode
2020-02-04 17:51:51 -05:00
cc-wr
d138761370 Clean up README.md 2020-01-30 19:44:34 -05:00
cc-wr
66a03632cc Add TODO comment 2020-01-16 06:28:16 -05:00
cc-wr
5f6f6363f0 Change "the Jupyter" to "Jupyter" 2020-01-16 06:08:09 -05:00
cc-wr
570eab43eb Redirect all string references to the stdout stream to a new WolframLanguageForJupyter-stdout stream 2020-01-16 06:02:42 -05:00
cc-wr
1717628fc8 Use the current PageWidth setting for $Output when converting a result into a string 2019-12-22 23:49:13 -05:00
cc-wr
40961d6029 Fix some small problems with WolframLanguageForJupyter's jupyter-console mode 2019-12-13 08:59:27 -05:00
cc-wr
3523e815e6
Merge pull request #68 from WolframResearch/feature/jupyter-console-support
Add jupyter-console support
2019-12-13 05:21:35 -05:00
Max Coplan
ff8acc78d1
Documenting features 2019-12-05 09:55:34 -05:00
Arnoud Buzing
515c244124
Merge pull request #70 from vegerot/patch-2
Fixed formatting
2019-12-05 08:49:23 -06:00
Max Coplan
ea621b19d8
Fixed formatting
Don't know how I missed this
2019-12-05 09:46:39 -05:00
cc-wr
6cca28f8ab Limit the Quit[] and Exit[] redefinition to the arguments that the actual Quit[] and Exit[] can take 2019-12-05 08:44:19 -05:00
cc-wr
1607ff07cd Do not allow the evaluation of the exit code strings (i.e., "Quit", "Exit", "quit", and "exit") 2019-12-05 08:22:12 -05:00
cc-wr
e006c4f7e1 Add support for ending a jupyter-console session when Quit[___] or Exit[___] are evaluated, or when the code strings "Quit", "Exit", "quit", or "exit" exactly are sent over by the console 2019-12-05 08:09:05 -05:00
cc-wr
780488b2ad
Merge pull request #67 from WolframResearch/bugfix/function-name-typo-2
WolframLanguageForJupyter.m: fix typo in function name
2019-12-03 11:57:11 -05:00
cc-wr
f94a621620
Merge pull request #66 from anonymouse64/bugfix/function-name-typo
configure-jupyter.wls: fix typo in function name
2019-12-03 11:56:57 -05:00
cc-wr
7bc5ee8ae6 Add jupyter-console support, and add redirection of Wolfram Language messages during the execution of an input 2019-11-24 20:45:50 -05:00
Arnoud Buzing
ebce0fa0fa
Merge pull request #64 from vegerot/patch-1
Clarifying instructions
2019-11-20 09:37:11 -06:00
Max Coplan
efffa66932
Added support for Windows
Jesus, Windows sucks 🤭.  I don't use Windows for development so if someone knows an easier way to do this let me know
2019-11-20 10:23:20 -05:00
cc-wr
6943c0ee3b WolframLanguageForJupyter.m: fix typo in function name 2019-11-20 06:54:48 -06:00
Ian Johnson
828604baa4 configure-jupyter.wls: fix typo in function name
Signed-off-by: Ian Johnson <person.uwsome@gmail.com>
2019-11-19 19:09:20 -06:00
Max Coplan
bbbeecd253
Clarifying instructions
Clarifying users need to clone the repo first (duh), and unless the CWD is in their PATH they will need to run `./` first.
2019-11-18 19:52:19 -05:00
Arnoud Buzing
6dab4f7576
Merge pull request #59 from WolframResearch/feature/remove-out-labels-for-multiple-results
Remove second level of Out[] labels for multiple results
2019-09-25 09:37:15 -05:00
cc-wr
7522c71207 Remove second level of Out[] labels for multiple results 2019-09-15 06:47:30 -04:00
cc-wr
12ed6ff52b
Merge pull request #57 from WolframResearch/bugfix/jupyterlab-issues
Do not branch off when sending kernel status messages
2019-09-09 16:44:03 -04:00
cc-wr
1dc339e62e
Merge pull request #53 from WolframResearch/bugfix/rasterization-failure
Fix a bug where large results can not be outputted
2019-09-09 16:43:51 -04:00
cc-wr
3e9068fc4a
Merge pull request #51 from WolframResearch/feature/add-rewrites-for-named-character-names
Add rewrites for named character names (to named characters)
2019-09-09 16:43:35 -04:00
cc-wr
5ab855e94c
Merge pull request #52 from WolframResearch/feature/handle-texform
Add handling of TeXForm
2019-09-09 16:43:16 -04:00
Arnoud Buzing
e5fd13daef
Merge pull request #56 from WolframResearch/feature/change-syntax-highlighting
Change the syntax highlighting to that of the Wolfram Language (or Mathematica, as is used in the project)
2019-08-27 08:24:48 -05:00
cc-wr
4883583139 Change the syntax highlighting to that of the Wolfram Language (or Mathematica, as is used in the project) 2019-08-27 05:20:38 -04:00
cc-wr
a0870db7be Do not branch off when sending kernel status messages 2019-08-25 17:44:01 -04:00
cc-wr
c6033abeb0 Fix some small, but blocking, issues 2019-08-24 15:08:55 -04:00
cc-wr
70d471dc81 If rasterization of a result fails, try again with Shallow applied (to the result) 2019-07-09 18:57:03 -04:00
cc-wr
5faf681109 Add rewrites for named character names 2019-07-09 18:46:19 -04:00
cc-wr
f5bcdbe765 Add handling of TeXForm 2019-07-09 18:44:02 -04:00
cc-wr
75fc9c238d
Merge pull request #46 from WolframResearch/feature/add-hooks-and-improve-multi-expression-input-handling
Make common idioms for setting a persistent format type for output effective
2019-06-20 11:02:24 -04:00
cc-wr
b63c459fa3 Add the rest of the hooks for this pull request 2019-06-12 02:10:18 -04:00
cc-wr
34084b057d Initial commit for a provisional improvement where standard Wolfram Language hooks are better handled by the WolframLanguageForJupyter loop; and some broad improvements for the handling of multi-expression input; and some other changes that make it easier to set TraditionalForm as a persistent format type 2019-06-07 14:00:23 -04:00
13 changed files with 1402 additions and 380 deletions

View File

@ -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:
![in-out-3](images/in-out-03.png)
## 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

View File

@ -6,7 +6,7 @@
Paclet[
Name -> "WolframLanguageForJupyter",
Version -> "0.9.1",
Version -> "0.9.3",
MathematicaVersion -> "11.2+",
Extensions -> {
{"Kernel", Context -> {"WolframLanguageForJupyter`"}},

View 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 *)

View File

@ -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 *)

View File

@ -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

View File

@ -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[]

View File

@ -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"]]]
]
]
];

View File

@ -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, "&#36;&#36;", ""],
(* 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, "&#36;&#36;", ""],
(* 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 *)

View File

@ -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` *)

View File

@ -74,7 +74,7 @@ If[
socketWriteFunction[
socket,
StringToByteArray[frame["content"]],
If[ByteArrayQ[frame["content"]], frame["content"], StringToByteArray[frame["content"]]],
"Multipart" -> False
];
];

View File

@ -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"];

View File

@ -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
View 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});