From 5faf6811099bf95f9fa9f2a1d921459c3527b079 Mon Sep 17 00:00:00 2001 From: cc-wr <47033353+cc-wr@users.noreply.github.com> Date: Tue, 9 Jul 2019 18:46:19 -0400 Subject: [PATCH] Add rewrites for named character names --- .../Resources/CompletionUtilities.wl | 84 +++++++++++++ .../Resources/Initialization.wl | 113 +++++++++++++++++- .../KernelForWolframLanguageForJupyter.wl | 6 +- .../Resources/OutputHandlingUtilities.wl | 5 +- .../Resources/RequestHandlers.wl | 57 ++++++++- extras/custom.js | 50 ++++++++ 6 files changed, 302 insertions(+), 13 deletions(-) create mode 100644 WolframLanguageForJupyter/Resources/CompletionUtilities.wl create mode 100644 extras/custom.js diff --git a/WolframLanguageForJupyter/Resources/CompletionUtilities.wl b/WolframLanguageForJupyter/Resources/CompletionUtilities.wl new file mode 100644 index 0000000..012d2ad --- /dev/null +++ b/WolframLanguageForJupyter/Resources/CompletionUtilities.wl @@ -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 *) diff --git a/WolframLanguageForJupyter/Resources/Initialization.wl b/WolframLanguageForJupyter/Resources/Initialization.wl index b2c3b75..17507fa 100644 --- a/WolframLanguageForJupyter/Resources/Initialization.wl +++ b/WolframLanguageForJupyter/Resources/Initialization.wl @@ -16,6 +16,8 @@ Symbols defined: keyString, baseString, heartbeatString, + verticalEllipsis, + unicodeNamedCharactersReplacements, ioPubString, controlString, inputString, @@ -61,6 +63,110 @@ If[ $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] + ] + ] & + ) /@ + ( + (* 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 ;;]] + ) + ); + + (* 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 @@ -101,13 +207,10 @@ If[ $outputSetToTraditionalForm := (Lookup[Options[$Output], FormatType] === TraditionalForm); $outputSetToTeXForm := (Lookup[Options[$Output], FormatType] === TeXForm); $trueFormatType := - Which[ + If[ $outputSetToTraditionalForm, TraditionalForm, - $outputSetToTeXForm, - TeXForm, - True, - Identity + If[$outputSetToTeXForm, TeXForm, #&] ]; (* obtain details on how to connect to Jupyter, from Jupyter's invocation of "KernelForWolframLanguageForJupyter.wl" *) diff --git a/WolframLanguageForJupyter/Resources/KernelForWolframLanguageForJupyter.wl b/WolframLanguageForJupyter/Resources/KernelForWolframLanguageForJupyter.wl index c44756f..6aec808 100644 --- a/WolframLanguageForJupyter/Resources/KernelForWolframLanguageForJupyter.wl +++ b/WolframLanguageForJupyter/Resources/KernelForWolframLanguageForJupyter.wl @@ -36,7 +36,7 @@ Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* init 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"}]]; (* executeRequestHandler, completeRequestHandler *) (************************************ private symbols @@ -96,6 +96,10 @@ loop[] := "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"; diff --git a/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl b/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl index 2f24222..93f8d15 100644 --- a/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl +++ b/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl @@ -209,10 +209,7 @@ If[ be identical to the string result of an InputForm-wrapped expression itself *) ({"&#", ToString[#1], ";"} & /@ ToCharacterCode[ - (* toStringUsingOutput[result] *) - Quiet[ - ToString[If[!isTeXWrapped, $trueFormatType[result], result]] - ], + (* toStringUsingOutput[result] *) ToString[If[!isTeXWrapped, $trueFormatType[result], result]], "Unicode" ]), diff --git a/WolframLanguageForJupyter/Resources/RequestHandlers.wl b/WolframLanguageForJupyter/Resources/RequestHandlers.wl index 2b0311e..8bdd7e7 100644 --- a/WolframLanguageForJupyter/Resources/RequestHandlers.wl +++ b/WolframLanguageForJupyter/Resources/RequestHandlers.wl @@ -5,7 +5,8 @@ Description: Handlers for message frames of type "x_request" arriving from Jupyter Symbols defined: - executeRequestHandler + executeRequestHandler, + completeRequestHandler *************************************************) (************************************ @@ -30,7 +31,10 @@ If[ Get[FileNameJoin[{DirectoryName[$InputFileName], "EvaluationUtilities.wl"}]]; (* simulatedEvaluate *) - Get[FileNameJoin[{DirectoryName[$InputFileName], "OutputHandlingUtilities.wl"}]]; (* textQ, toOutText, toOutImage *) + Get[FileNameJoin[{DirectoryName[$InputFileName], "OutputHandlingUtilities.wl"}]]; (* textQ, toOutText, toOutImage, + containsPUAQ *) + + Get[FileNameJoin[{DirectoryName[$InputFileName], "CompletionUtilities.wl"}]]; (* rewriteNamedCharacters *) (************************************ private symbols @@ -69,7 +73,6 @@ If[ (* 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"], @@ -278,6 +281,54 @@ If[ loopState["executionCount"] += totalResult["ConsumedIndices"]; ]; +(************************************ + handler for complete_requests +*************************************) + + (* handle complete_request messages 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` *) diff --git a/extras/custom.js b/extras/custom.js new file mode 100644 index 0000000..c625eb2 --- /dev/null +++ b/extras/custom.js @@ -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.preventDefault(); + } +} + +/* (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}); \ No newline at end of file