Skip to content

Commit

Permalink
Updated webr ext version
Browse files Browse the repository at this point in the history
  • Loading branch information
royfrancis committed Sep 30, 2023
1 parent dc11eb4 commit db55c1f
Show file tree
Hide file tree
Showing 10 changed files with 378 additions and 55 deletions.
2 changes: 1 addition & 1 deletion _extensions/coatless/webr/_extension.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: webr
title: Embedded webr code cells
author: James Joseph Balamuta
version: 0.1.0
version: 0.3.6
quarto-required: ">=1.2.198"
contributes:
filters:
Expand Down
10 changes: 10 additions & 0 deletions _extensions/coatless/webr/monaco-editor-init.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script src="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js"></script>
<script type="module" id="webr-monaco-editor-init">

// Configure the Monaco Editor's loader
require.config({
paths: {
'vs': 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs'
}
});
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,6 @@
hideCursorInOverviewRuler: true // Remove cursor indictor in right hand side scroll bar
});

// Add a keydown event listener for Shift+Enter using the addCommand method
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, function () {
// Code to run when Shift+Enter is pressed
executeCode(editor.getValue());
});

// Add a keydown event listener for Ctrl+Enter to run selected code
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () {
// Get the selected text from the editor
const selectedText = editor.getModel().getValueInRange(editor.getSelection());
// Code to run when Ctrl+Enter is pressed (run selected code)
executeCode(selectedText);
});

// Dynamically modify the height of the editor window if new lines are added.
let ignoreEvent = false;
const updateHeight = () => {
Expand All @@ -61,6 +47,65 @@
}
};

// Helper function to check if selected text is empty
function isEmptyCodeText(selectedCodeText) {
return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === "");
}

// Registry of keyboard shortcuts that should be re-added to each editor window
// when focus changes.
const addWebRKeyboardShortCutCommands = () => {
// Add a keydown event listener for Shift+Enter to run all code in cell
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {

// Retrieve all text inside the editor
executeCode(editor.getValue());
});

// Add a keydown event listener for CMD/Ctrl+Enter to run selected code
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {

// Get the selected text from the editor
const selectedText = editor.getModel().getValueInRange(editor.getSelection());
// Check if no code is selected
if (isEmptyCodeText(selectedText)) {
// Obtain the current cursor position
let currentPosition = editor.getPosition();
// Retrieve the current line content
let currentLine = editor.getModel().getLineContent(currentPosition.lineNumber);

// Propose a new position to move the cursor to
let newPosition = new monaco.Position(currentPosition.lineNumber + 1, 1);

// Check if the new position is beyond the last line of the editor
if (newPosition.lineNumber > editor.getModel().getLineCount()) {
// Add a new line at the end of the editor
editor.executeEdits("addNewLine", [{
range: new monaco.Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1),
text: "\n",
forceMoveMarkers: true,
}]);
}

// Run the entire line of code.
executeCode(currentLine);

// Move cursor to new position
editor.setPosition(newPosition);
} else {
// Code to run when Ctrl+Enter is pressed with selected code
executeCode(selectedText);
}
});
}

// Register an on focus event handler for when a code cell is selected to update
// what keyboard shortcut commands should work.
// This is a workaround to fix a regression that happened with multiple
// editor windows since Monaco 0.32.0
// https://github.com/microsoft/monaco-editor/issues/2947
editor.onDidFocusEditorText(addWebRKeyboardShortCutCommands);

// Register an on change event for when new code is added to the editor window
editor.onDidContentSizeChange(updateHeight);

Expand All @@ -79,15 +124,15 @@
// Initialize webR
await globalThis.webR.init();

// Setup a webR canvas
await webR.evalRVoid("canvas(width={{WIDTH}}, height={{HEIGHT}})");
// Setup a webR canvas by making a namespace call into the {webr} package
await webR.evalRVoid("webr::canvas(width={{WIDTH}}, height={{HEIGHT}})");

// Capture output data from evaluating the code
const result = await webRCodeShelter.captureR(codeToRun, {
withAutoprint: true,
captureStreams: true,
captureConditions: false,
env: webR.objs.emptyEnv,
captureConditions: false//,
// env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0
});

// Start attempting to parse the result data
Expand All @@ -106,16 +151,20 @@

// Output each image stored
msgs.forEach(msg => {
if (msg.type === "canvasExec") {
if (!canvas) {
// Determine if old canvas can be used or a new canvas is required.
if (msg.type === 'canvas'){
// Add image to the current canvas
if (msg.data.event === 'canvasImage') {
canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
} else if (msg.data.event === 'canvasNewPage') {
// Generate a new canvas element
canvas = document.createElement("canvas");
canvas.setAttribute("width", 2 * {{WIDTH}});
canvas.setAttribute("height", 2 * {{HEIGHT}});
canvas.style.width = "700px";
canvas.style.display = "block";
canvas.style.margin = "auto";
}
Function(`this.getContext("2d").${msg.data}`).bind(canvas)();
}
});

Expand Down
90 changes: 90 additions & 0 deletions _extensions/coatless/webr/webr-context-output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<div id="webr-code-output-{{WEBRCOUNTER}}" aria-live="assertive">
<pre style="visibility: hidden"></pre>
</div>
<script type="module">
// Retrieve webR code cell information
const outputDiv = document.getElementById("webr-code-output-{{WEBRCOUNTER}}");

// Function to execute the code (accepts code as an argument)
async function executeOutputOnlyCode(codeToRun) {
// Create a canvas variable for graphics
let canvas = undefined;

// Initialize webR
await globalThis.webR.init();

// Setup a webR canvas by making a namespace call into the {webr} package
await webR.evalRVoid("webr::canvas(width={{WIDTH}}, height={{HEIGHT}})");

// Capture output data from evaluating the code
const result = await webRCodeShelter.captureR(codeToRun, {
withAutoprint: true,
captureStreams: true,
captureConditions: false//,
// env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0
});

// Start attempting to parse the result data
try {

// Stop creating images
await webR.evalRVoid("dev.off()");

// Merge output streams of STDOUT and STDErr (messages and errors are combined.)
const out = result.output.filter(
evt => evt.type == "stdout" || evt.type == "stderr"
).map((evt) => evt.data).join("\n");

// Clean the state
const msgs = await webR.flush();

// Output each image stored
msgs.forEach(msg => {
// Determine if old canvas can be used or a new canvas is required.
if (msg.type === 'canvas'){
// Add image to the current canvas
if (msg.data.event === 'canvasImage') {
canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
} else if (msg.data.event === 'canvasNewPage') {
// Generate a new canvas element
canvas = document.createElement("canvas");
canvas.setAttribute("width", 2 * {{WIDTH}});
canvas.setAttribute("height", 2 * {{HEIGHT}});
canvas.style.width = "700px";
canvas.style.display = "block";
canvas.style.margin = "auto";
}
}
});

// Nullify the outputDiv of content
outputDiv.innerHTML = "";

// Design an output object for messages
const pre = document.createElement("pre");
if (/\S/.test(out)) {
// Display results as text
const code = document.createElement("code");
code.innerText = out;
pre.appendChild(code);
} else {
// If nothing is present, hide the element.
pre.style.visibility = "hidden";
}
outputDiv.appendChild(pre);

// Place the graphics on the canvas
if (canvas) {
const p = document.createElement("p");
p.appendChild(canvas);
outputDiv.appendChild(p);
}
} finally {
// Clean up the remaining code
webRCodeShelter.purge();
}
}

// Run the code
executeOutputOnlyCode(`{{WEBRCODE}}`)
</script>
9 changes: 9 additions & 0 deletions _extensions/coatless/webr/webr-context-setup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script type="module">
// Initialization WebR
await globalThis.webR.init();

// Run R code without focusing on storing data.
await globalThis.webR.evalRVoid(`
{{WEBRCODE}}
`)
</script>
67 changes: 52 additions & 15 deletions _extensions/coatless/webr/webr-init.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/editor/editor.main.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/editor/editor.main.css" />

<style>
.monaco-editor pre {
background-color: unset !important;
Expand All @@ -7,19 +8,40 @@
.btn-webr {
background-color: #EEEEEE;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-bottom-right-radius: 0; /* Extra styling for consistency */
display: inline-block;
font-weight: 400;
line-height: 1.5;
color: #000;
text-align: center;
text-decoration: none;
-webkit-text-decoration: none;
-moz-text-decoration: none;
-ms-text-decoration: none;
-o-text-decoration: none;
vertical-align: middle;
-webkit-user-select: none;
border-color: #dee2e6;
border: 1px solid rgba(0,0,0,0);
padding: 0.375rem 0.75rem;
font-size: 1rem;
border-radius: 0.25rem;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js"></script>
<script type="module">

// Configure the Monaco Editor's loader
require.config({
paths: {
'vs': 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs'
}
});
.btn-webr:hover {
color: #000;
background-color: #e3e6ea;
border-color: #e1e5e9;
}

.btn-webr:disabled,.btn-webr.disabled,fieldset:disabled .btn-webr {
pointer-events: none;
opacity: .65
}
</style>

<script type="module">

// Start a timer
const initializeWebRTimerStart = performance.now();
Expand Down Expand Up @@ -74,19 +96,34 @@
quartoTitleMeta.appendChild(firstInnerDiv);

// Add new element as last child in header element
var header = document.getElementsByTagName("header")[0];
header.appendChild(quartoTitleMeta);
var header = document.getElementById("title-block-header");

// Check if the header option is present or missing
if(header) {
// If present, directly append the child element.
header.appendChild(quartoTitleMeta);
} else {
// Attempt to place the new element inside a header _after_ the Monaco initialization
var monacoScript = document.getElementById("webr-monaco-editor-init");
var header = document.createElement("header");
header.setAttribute("id", "title-block-header");
// Now attempt to add the webR area to the title
header.appendChild(quartoTitleMeta);
// Include the header after the script tag
monacoScript.after(header);
}
}

// Retrieve the webr.mjs
import { WebR } from "https://webr.r-wasm.org/v0.1.1/webr.mjs";
import { WebR, ChannelType } from "https://webr.r-wasm.org/v0.2.1/webr.mjs";

// Populate WebR options with defaults or new values based on
// webr meta
globalThis.webR = new WebR({
"baseURL": "{{BASEURL}}",
"serviceWorkerUrl": "{{SERVICEWORKERURL}}",
"homedir": "{{HOMEDIR}}"
"homedir": "{{HOMEDIR}}",
"channelType": {{CHANNELTYPE}}
});

// Initialization WebR
Expand Down
2 changes: 1 addition & 1 deletion _extensions/coatless/webr/webr-serviceworker.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
importScripts('https://webr.r-wasm.org/v0.1.1/webr-serviceworker.js');
importScripts('https://webr.r-wasm.org/v0.2.1/webr-serviceworker.js');
2 changes: 1 addition & 1 deletion _extensions/coatless/webr/webr-worker.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
importScripts('https://webr.r-wasm.org/v0.1.1/webr-worker.js');
importScripts('https://webr.r-wasm.org/v0.2.1/webr-worker.js');
Loading

0 comments on commit db55c1f

Please sign in to comment.