From a82352d603f4f4dcf92533d359db504903859e54 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Mon, 16 May 2016 14:28:24 +0200 Subject: [PATCH 01/27] basically working --- imathics/kernel.py | 34 +- imathics/mathics.js | 806 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 839 insertions(+), 1 deletion(-) create mode 100644 imathics/mathics.js diff --git a/imathics/kernel.py b/imathics/kernel.py index 515e187..5d57e41 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -12,6 +12,8 @@ from mathics import settings from mathics.version import __version__ from mathics.doc.doc import Doc +import os +import base64 class MathicsKernel(Kernel): @@ -77,9 +79,39 @@ def out_callback(self, out): self.send_response(self.iopub_socket, 'stream', content) def result_callback(self, result): + mathics_js = "" + + with open(os.path.dirname(os.path.abspath(__file__)) + '/mathics.js', 'r') as f: + mathics_js += f.read() + + html = result.data['text/html'] + + js = " + """ # result.data['text/html']) + + # js = "var cell = Jupyter.notebook.insert_cell_at_bottom('markdown'); cell.set_text(text);" + # js = "" + + data = {'text/html': js} + content = { 'execution_count': result.line_no, - 'data': result.data, + 'data': data, # result.data, 'metadata': result.metadata, } self.send_response(self.iopub_socket, 'execute_result', content) diff --git a/imathics/mathics.js b/imathics/mathics.js new file mode 100644 index 0000000..47a82ff --- /dev/null +++ b/imathics/mathics.js @@ -0,0 +1,806 @@ +/* +IMPORTANT + +in order for mathjax hub queue to work, Jupyter must _not_ set its web font to "STIX-Web" + + "HTML-CSS": { + availableFonts: [], + imageFont: null, + preferredFont: null, + webFont: null, + styles: {'.MathJax_Display': {"margin": 0}}, + linebreaks: { automatic: true } + }, + +*/ + + +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +var HTML_ENTITIES = { + 'amp': 38, + 'gt': 62, + 'lt': 60, + 'quot': 34, + 'nbsp': 160, + 'ndash': 8211, + 'mdash': 8212, + 'euro': 8364, + 'copy': 169, + 'trade': 8482, + 'hellip': 8230, + 'ldquo': 8220, + 'rdquo': 8221, + 'bdquo': 8222, + 'reg': 174, + 'larr': 8592, + 'rarr': 8594 +}; + +function $E(tag, properties) { + var children; + var isDOMElement = function(object) { + return object && !!object.nodeType; + }; + if (isDOMElement(properties)) { + children = $A(arguments).slice(1); + properties = {}; + } else + children = $A(arguments).slice(2); + if (tag == 'a' && properties.href == null) + properties.href = 'javascript:;'; + var element = document.createElement(tag); + + for (var key in properties) { + if (properties.hasOwnProperty(key)) { + element.setAttribute(key, properties[key]); + } + } + + children.forEach(function(child) { + if (child) + element.appendChild(child); + }); + return element; +} + +function $T(text) { + return document.createTextNode(text); +} + + + +var deleting; +var blurredElement; + +var movedItem; + +var clickedQuery; + +var lastFocus = null; + +function getLetterWidth(element) { + var letter = $E('span', $T('m')); + letter.setStyle({ + fontFamily: element.getStyle('font-family'), + fontSize: element.getStyle('font-size') + }); + var parent = $$('body')[0]; + parent.appendChild(letter); + var width = letter.getWidth(); + parent.removeChild(letter); + delete letter; + return width; +} + +function refreshInputSize(textarea) { + var letterWidth = getLetterWidth(textarea); + var width = textarea.getWidth() - 15; + var lines = textarea.value.split('\n'); + var lineCount = 0; + for (var index = 0; index < lines.length; ++index) { + var line = lines[index]; + lineCount += Math.ceil(1.0 * (line.length + 1) * letterWidth / width); + } + textarea.rows = lineCount; +} + +/*function refreshInputSizes() { + $$('textarea.request').each(function(textarea) { + refreshInputSize(textarea); + }); + + $$('#queries ul').each(function(ul) { + afterProcessResult(ul, 'Rerender'); + }); +}*/ + +function inputChange(event) { + refreshInputSize(this); +} + +function isEmpty(textarea) { + return textarea.value.strip() == '' && !textarea.submitted; +} + +function prepareText(text) { + if (text == '') { + text = String.fromCharCode(160); + } + return text; + + /* + // Place ­ between every two characters. + // Problem: Copy & paste yields weird results! + var result = ''; + for (var index = 0; index < text.length; ++index) { + result += text.charAt(index); + if (index < text.length - 1) + result += String.fromCharCode(173); // ­ + } + return result; + */ +} + +function getDimensions(math, callback) { + var all = $('calc_all').cloneNode(true); + all.id = null; + var body = $$('body')[0]; + body.appendChild(all); + var container = all.select('.calc_container')[0]; + container.appendChild(translateDOMElement(math)); + + MathJax.Hub.Queue(["Typeset", MathJax.Hub, container]); + MathJax.Hub.Queue(function() { + var pos = container.cumulativeOffset(); + var next = all.select('.calc_next')[0].cumulativeOffset(); + var below = all.select('.calc_below')[0].cumulativeOffset(); + var width = next.left - pos.left + 4; + var height = below.top - pos.top + 20; + body.removeChild(all); + callback(width, height); + }); +} + +function drawMeshGradient(ctx, points) { + function color(c, a) { + var result = 'rgba(' + Math.round(c[0]*255) + ', ' + Math.round(c[1]*255) + ', ' + + Math.round(c[2]*255) + ', ' + a + ')'; + return result; + } + + var grad1 = ctx.createLinearGradient(0, 0, 0.5, 0.5); + grad1.addColorStop(0, color(points[0][1], 1)); + grad1.addColorStop(1, color(points[0][1], 0)); + var grad2 = ctx.createLinearGradient(1, 0, 0, 0); + grad2.addColorStop(0, color(points[1][1], 1)); + grad2.addColorStop(1, color(points[1][1], 0)); + var grad3 = ctx.createLinearGradient(0, 1, 0, 0); + grad3.addColorStop(0, color(points[2][1], 1)); + grad3.addColorStop(1, color(points[2][1], 0)); + + ctx.save(); + ctx.setTransform(points[1][0][0]-points[0][0][0], points[1][0][1]-points[0][0][1], + points[2][0][0]-points[0][0][0], points[2][0][1]-points[0][0][1], points[0][0][0], points[0][0][1]); + + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(1, 0); + ctx.lineTo(0, 1); + ctx.closePath(); + + ctx.globalCompositeOperation = "lighter"; + ctx.fillStyle = grad1; + ctx.fill(); + ctx.fillStyle = grad2; + ctx.fill(); + ctx.fillStyle = grad3; + ctx.fill(); + ctx.restore(); +} + +function createMathNode(nodeName) { + if (['svg', 'g', 'rect', 'circle', 'polyline', 'polygon', 'path', 'ellipse', 'foreignObject'].indexOf(nodeName) != -1) + return document.createElementNS("http://www.w3.org/2000/svg", nodeName); + else { + return document.createElement(nodeName); + } +} + +var objectsPrefix = 'math_object_'; +var objectsCount = 0; +var objects = {}; + +function translateDOMElement(element, svg) { + if (element.nodeType == 3) { + var text = element.nodeValue; + return $T(text); + } + var dom = null; + var nodeName = element.nodeName; + if (nodeName != 'meshgradient' && nodeName != 'graphics3d') { + dom = createMathNode(element.nodeName); + for (var i = 0; i < element.attributes.length; ++i) { + var attr = element.attributes[i]; + if (attr.nodeName != 'ox' && attr.nodeName != 'oy') + dom.setAttribute(attr.nodeName, attr.nodeValue); + } + } + if (nodeName == 'foreignObject') { + dom.setAttribute('width', svg.getAttribute('width')); + dom.setAttribute('height', svg.getAttribute('height')); + dom.setAttribute('style', dom.getAttribute('style') + '; text-align: left; padding-left: 2px; padding-right: 2px;'); + var ox = parseFloat(element.getAttribute('ox')); + var oy = parseFloat(element.getAttribute('oy')); + dom.setAttribute('ox', ox); + dom.setAttribute('oy', oy); + } + if (nodeName == 'mo') { + var op = element.childNodes[0].nodeValue; + if (op == '[' || op == ']' || op == '{' || op == '}' || op == String.fromCharCode(12314) || op == String.fromCharCode(12315)) + dom.setAttribute('maxsize', '3'); + } + if (nodeName == 'meshgradient') { + if (!MathJax.Hub.Browser.isOpera) { + var data = element.getAttribute('data').evalJSON(); + var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); + var foreign = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'); + foreign.setAttribute('width', svg.getAttribute('width')); + foreign.setAttribute('height', svg.getAttribute('height')); + foreign.setAttribute('x', '0px'); + foreign.setAttribute('y', '0px'); + foreign.appendChild(div); + + var canvas = createMathNode('canvas'); + canvas.setAttribute('width', svg.getAttribute('width')); + canvas.setAttribute('height', svg.getAttribute('height')); + div.appendChild(canvas); + + var ctx = canvas.getContext('2d'); + for (var index = 0; index < data.length; ++index) { + var points = data[index]; + if (points.length == 3) { + drawMeshGradient(ctx, points); + } + } + + dom = foreign; + } + } + var object = null; + if (nodeName == 'graphics3d') { + var data = element.getAttribute('data').evalJSON(); + var div = document.createElement('div'); + drawGraphics3D(div, data); + dom = div; + } + if (nodeName == 'svg' || nodeName == 'graphics3d') { + // create that will contain the graphics + object = createMathNode('mspace'); + var width, height; + if (nodeName == 'svg') { + width = dom.getAttribute('width'); + height = dom.getAttribute('height'); + } else { + // TODO: calculate appropriate height and recalculate on every view change + width = height = '400'; + } + object.setAttribute('width', width + 'px'); + object.setAttribute('height', height + 'px'); + } + if (nodeName == 'svg') + svg = dom; + var rows = [[]]; + $A(element.childNodes).forEach(function(child) { + if (child.nodeName == 'mspace' && child.getAttribute('linebreak') == 'newline') + rows.push([]); + else + rows[rows.length - 1].push(child); + }); + var childParent = dom; + if (nodeName == 'math') { + var mstyle = createMathNode('mstyle'); + mstyle.setAttribute('displaystyle', 'true'); + dom.appendChild(mstyle); + childParent = mstyle; + } + if (rows.length > 1) { + var mtable = createMathNode('mtable'); + mtable.setAttribute('rowspacing', '0'); + mtable.setAttribute('columnalign', 'left'); + var nospace = 'cell-spacing: 0; cell-padding: 0; row-spacing: 0; row-padding: 0; border-spacing: 0; padding: 0; margin: 0'; + mtable.setAttribute('style', nospace); + rows.forEach(function(row) { + var mtr = createMathNode('mtr'); + mtr.setAttribute('style', nospace); + var mtd = createMathNode('mtd'); + mtd.setAttribute('style', nospace); + row.forEach(function(element) { + var elmt = translateDOMElement(element, svg); + if (nodeName == 'mtext') { + // wrap element in mtext + var outer = createMathNode('mtext'); + outer.appendChild(elmt); + elmt = outer; + } + mtd.appendChild(elmt); + }); + mtr.appendChild(mtd); + mtable.appendChild(mtr); + }); + if (nodeName == 'mtext') { + // no mtable inside mtext, but mtable instead of mtext + dom = mtable; + } else + childParent.appendChild(mtable); + } else + rows[0].forEach(function(element) { + childParent.appendChild(translateDOMElement(element, svg)); + }); + if (object) { + var id = objectsCount++; + object.setAttribute('mathics_id', objectsPrefix + id); + objects[id] = dom; + return object; + } + return dom; +} + +function createLine(value) { + if (value.startsWith('';*/ + + var dom = document.createElement('div'); + + var updateDOM = function(element, content) { + // convert named entities to numerical entities before calling update + content = content.replace(/&([a-zA-Z]+);/g, function(match, contents, offset, s) { + var code = HTML_ENTITIES[contents]; + return "&#" + code + ";"; + }); + element.innerHTML = content; + // updateElement(element, content); + }; + + updateDOM(dom, value); + + var el = translateDOMElement(dom.childNodes[0]); + + var container = document.createElement('div'); + container.appendChild(el); + + // alert('mathjax hub typeset: ' + container.innerHTML); + + MathJax.Hub.Queue(['Typeset', MathJax.Hub, container]); + + MathJax.Hub.Register.StartupHook("End",function () { + afterProcessResult(container); + }); + + return container; + } else { + var lines = value.split('\n'); + var p = $E('p'); + for (var index = 0; index < lines.length; ++index) { + p.appendChild($T(prepareText(lines[index]))); + if (index < lines.length - 1) + p.appendChild($E('br')); + } + return p; + } +} + +function afterProcessResult(ul, command) { + // command is either 'Typeset' (default) or 'Rerender' + if (!command) + command = 'Typeset'; + MathJax.Hub.Queue(function() { + // inject SVG and other non-MathML objects into corresponding s + // alert('mathjax hub callback: ' + ul.querySelectorAll('.mspace').length + '/' + ul.innerHTML); + Array.prototype.forEach.call(ul.querySelectorAll('.mspace'), function(mspace) { + var id = mspace.getAttribute('mathics_id').substr(objectsPrefix.length); + // alert('mathjax hub callback for ' + id); + var object = objects[id]; + if (object) { + mspace.appendChild(object); + } + }); + }); + if (!MathJax.Hub.Browser.isOpera) { + // Opera 11.01 Build 1190 on Mac OS X 10.5.8 crashes on this call for Plot[x,{x,0,1}] + // => leave inner MathML untouched + MathJax.Hub.Queue(['Typeset', MathJax.Hub, ul]); + } + MathJax.Hub.Queue(function() { + Array.prototype.forEach.call(ul.querySelectorAll('foreignObject >span >nobr >span.math'), function(math) { + var content = math.childNodes[0].childNodes[0].childNodes[0]; + math.removeChild(math.childNodes[0]); + math.insertBefore(content, math.childNodes[0]); + + if (command == 'Typeset') { + // recalculate positions of insets based on ox/oy properties + var foreignObject = math.parentNode.parentNode.parentNode; + var dimensions = math.getDimensions(); + var w = dimensions.width + 4; + var h = dimensions.height + 4; + var x = parseFloat(foreignObject.getAttribute('x').substr()); + var y = parseFloat(foreignObject.getAttribute('y')); + var ox = parseFloat(foreignObject.getAttribute('ox')); + var oy = parseFloat(foreignObject.getAttribute('oy')); + x = x - w/2.0 - ox*w/2.0; + y = y - h/2.0 + oy*h/2.0; + foreignObject.setAttribute('x', x + 'px'); + foreignObject.setAttribute('y', y + 'px'); + } + }); + }); +} + +/*function setResult(ul, results) { + results.forEach(function(result) { + var resultUl = $E('ul', {'class': 'out'}); + result.out.forEach(function(out) { + var li = $E('li', {'class': (out.message ? 'message' : 'print')}); + if (out.message) + li.appendChild($T(out.prefix + ': ')); + li.appendChild(createLine(out.text)); + resultUl.appendChild(li); + }); + if (result.result != null) { + var li = $E('li', {'class': 'result'}, createLine(result.result)); + resultUl.appendChild(li); + } + ul.appendChild($E('li', {'class': 'out'}, resultUl)); + }); + afterProcessResult(ul); +}*/ + +/*function submitQuery(textarea, onfinish) { + $('welcomeContainer').fade({duration: 0.5}); + + textarea.li.addClassName('loading'); + new Ajax.Request('/ajax/query/', { + method: 'post', + parameters: { + query: textarea.value + }, + onSuccess: function(transport) { + textarea.ul.select('li[class!=request][class!=submitbutton]').invoke('deleteElement'); + if (!transport.responseText) { + // A fatal Python error has occured, e.g. on 4.4329408320439^43214234345 + // ("Fatal Python error: mp_reallocate failure") + // -> print overflow message + transport.responseText = '{"results": [{"out": [{"prefix": "General::noserver", "message": true, "tag": "noserver", "symbol": "General", "text": "No server running."}]}]}'; + } + var response = transport.responseText.evalJSON(); + setResult(textarea.ul, response.results); + textarea.submitted = true; + textarea.results = response.results; + var next = textarea.li.nextSibling; + if (next) + next.textarea.focus(); + else + createQuery(); + }, + onFailure: function(transport) { + textarea.ul.select('li[class!=request]').invoke('deleteElement'); + var li = $E('li', {'class': 'serverError'}, $T("Sorry, an error occurred while processing your request!")); + textarea.ul.appendChild(li); + textarea.submitted = true; + }, + onComplete: function() { + textarea.li.removeClassName('loading'); + if (onfinish) + onfinish(); + } + }); +}*/ + +function getSelection() { + // TODO +} + +/*function keyDown(event) { + var textarea = lastFocus; + if (!textarea) + return; + refreshInputSize(textarea); + + if (event.keyCode == Event.KEY_RETURN && (event.shiftKey || event.keyLocation == 3)) { + if (!Prototype.Browser.IE) + event.stop(); + + var query = textarea.value.strip(); + if (query) { + submitQuery(textarea); + } + } else if (event.keyCode == Event.KEY_UP) { + if (textarea.selectionStart == 0 && textarea.selectionEnd == 0) { + if (isEmpty(textarea)) { + if (textarea.li.previousSibling) + textarea.li.previousSibling.textarea.focus(); + } else + createQuery(textarea.li); + } + } else if (event.keyCode == Event.KEY_DOWN) { + if (textarea.selectionStart == textarea.value.length && textarea.selectionEnd == textarea.selectionStart) { + if (isEmpty(textarea)) { + if (textarea.li.nextSibling) + textarea.li.nextSibling.textarea.focus(); + } else + createQuery(textarea.li.nextSibling); + } + } else + if (isGlobalKey(event)) + event.stop(); +}*/ + +/*function deleteMouseDown(event) { + if (event.isLeftClick()) + deleting = true; +} + +function deleteClick(event) { + if (lastFocus == this.li.textarea) + lastFocus = null; + this.li.deleteElement(); + deleting = false; + if (blurredElement) { + blurredElement.focus(); + blurredElement = null; + } + if ($('queries').childElements().length == 0) + createQuery(); +} + +function moveMouseDown(event) { + movedItem = this.li; + movedItem.addClassName('moving'); +} + +function moveMouseUp(event) { + if (movedItem) { + movedItem.removeClassName('moving'); + movedItem.textarea.focus(); + movedItem = null; + } +} + +function onFocus(event) { + var textarea = this; + textarea.li.addClassName('focused'); + lastFocus = textarea; +} + +function onBlur(event) { + var textarea = this; + blurredElement = textarea; + if (!deleting && textarea.li != movedItem && isEmpty(textarea) && $('queries').childElements().length > 1) { + textarea.li.hide(); + if (textarea == lastFocus) + lastFocus = null; + window.setTimeout(function() { + textarea.li.deleteElement(); + }, 10); + } + textarea.li.removeClassName('focused'); +} + +function createSortable() { + Position.includeScrollOffsets = true; + Sortable.create('queries', { + handle: 'move', + scroll: 'document', + scrollSensitivity: 1 // otherwise strange flying-away of item at top + }); +} + +var queryIndex = 0; +*/ +/*function createQuery(before, noFocus, updatingAll) { + var ul, textarea, moveHandle, deleteHandle, submitButton; + // Items need id in order for Sortable.onUpdate to work. + var li = $E('li', {'id': 'query_' + queryIndex++, 'class': 'query'}, + ul = $E('ul', {'class': 'query'}, + $E('li', {'class': 'request'}, + textarea = $E('textarea', {'class': 'request'}), + $E('span', {'class': 'submitbutton', 'title': "Submit [Shift+Return]"}, + submitButton = $E('span', $T('=')) + ) + ) + ), + moveHandle = $E('span', {'class': 'move'}), + deleteHandle = $E('span', {'class': 'delete', 'title': "Delete"}, $T(String.fromCharCode(215))) + ); + textarea.rows = 1; + textarea.ul = ul; + textarea.li = li; + textarea.submitted = false; + moveHandle.li = li; + deleteHandle.li = li; + li.textarea = textarea; + li.ul = ul; + if (before) + $('queries').insertBefore(li, before); + else + $('queries').appendChild(li); + if (!updatingAll) + refreshInputSize(textarea); + new Form.Element.Observer(textarea, 0.2, inputChange.bindAsEventListener(textarea)); + textarea.observe('focus', onFocus.bindAsEventListener(textarea)); + textarea.observe('blur', onBlur.bindAsEventListener(textarea)); + li.observe('mousedown', queryMouseDown.bindAsEventListener(li)); + deleteHandle.observe('click', deleteClick.bindAsEventListener(deleteHandle)); + deleteHandle.observe('mousedown', deleteMouseDown.bindAsEventListener(deleteHandle)); + moveHandle.observe('mousedown', moveMouseDown.bindAsEventListener(moveHandle)); + moveHandle.observe('mouseup', moveMouseUp.bindAsEventListener(moveHandle)); + $(document).observe('mouseup', moveMouseUp.bindAsEventListener($(document))); + submitButton.observe('mousedown', function() { + if (textarea.value.strip()) + submitQuery(textarea); + else + window.setTimeout(function() { + textarea.focus(); + }, 10); + }); + if (!updatingAll) { + createSortable(); + // calling directly fails in Safari on document loading + //window.setTimeout(createSortable, 10); + } + // Immediately setting focus doesn't work in IE. + if (!noFocus) + window.setTimeout(function() { + textarea.focus(); + }, 10); + return li; +}*/ +/* +var mouseDownEvent = null; + +function documentMouseDown(event) { + if (event.isLeftClick()) { + if (clickedQuery) { + clickedQuery = null; + mouseDownEvent = null; + return; + } + event.stop(); // strangely, doesn't work otherwise + mouseDownEvent = event; + } +} +*/ +/*function documentClick(event) { + // In Firefox, mousedown also fires when user clicks scrollbars. + // -> listen to click + event = mouseDownEvent; + if (!event) + return; + if ($('queries').childElements().length == 1 && isEmpty($('queries').childElements()[0].textarea)) { + $('queries').childElements()[0].textarea.focus(); + return; + } + var offset = $('document').cumulativeOffset(); + var y = event.pointerY() - offset.top + $('document').scrollTop; + var element = null; + $('queries').childElements().forEach(function(li) { + var offset = li.positionedOffset(); // margin-top: 10px + if (offset.top + 20 > y) { + element = li; + throw $break; + } + }); + createQuery(element); +}*/ +/* +function queryMouseDown(event) { + clickedQuery = this; +} + +function focusLast() { + if (lastFocus) + lastFocus.focus(); + else + createQuery(); +} + +function isGlobalKey(event) { + if (event.ctrlKey) { + switch(event.keyCode) { + case 68: + case 67: + case 83: + case 79: + return true; + } + } + return false; +} + +function globalKeyUp(event) { + if (!popup && event.ctrlKey) { + switch (event.keyCode) { + case 68: // D + $('search').select(); + event.stop(); + break; + case 67: // C + focusLast(); + event.stop(); + break; + case 83: // S + showSave(); + break; + case 79: // O + showOpen(); + break; + } + } +} + +function domLoaded() { + MathJax.Hub.Config({ + "HTML-CSS": { + imageFont: null, + linebreaks: { automatic: true } + }, + MMLorHTML: { + // + // The output jax that is to be preferred when both are possible + // (set to "MML" for native MathML, "HTML" for MathJax's HTML-CSS output jax). + // + prefer: { + MSIE: "HTML", + Firefox: "HTML", + Opera: "HTML", + other: "HTML" + } + } + }); + MathJax.Hub.Configured(); + + if ($('welcomeBrowser')) + if (!(Prototype.Browser.WebKit || Prototype.Browser.MobileSafari || Prototype.Browser.Gecko)) + $('welcomeBrowser').show(); + + $$('body')[0].observe('resize', refreshInputSizes); + + if ($('queriesContainer')) { + $('queriesContainer').appendChild($E('ul', {'id': 'queries'})); + + $('document').observe('mousedown', documentMouseDown.bindAsEventListener($('document'))); + $('document').observe('click', documentClick.bindAsEventListener($('document'))); + + $(document).observe('keydown', keyDown.bindAsEventListener()); + if (Prototype.Browser.IE) { + document.body.addEventListener('keydown', function(event) { + if (event.keyCode == Event.KEY_RETURN && event.shiftKey) { + event.stopPropagation(); + event.preventDefault(); + keyDown(event); + } + }, true); + } + if (Prototype.Browser.Opera || Prototype.Browser.IE) { + // Opera needs another hook so it doesn't insert newlines after Shift+Return + $(document).observe('keypress', function(event) { + if (event.keyCode == Event.KEY_RETURN && event.shiftKey) + event.stop(); + }.bindAsEventListener()); + } + + $(document).observe('keyup', globalKeyUp.bindAsEventListener($('document'))); + + if (!loadLink()) + createQuery(); + } +} +*/ \ No newline at end of file From 0da1ce536c67fbf4b332ee9f0c7d6c1ed102de33 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Mon, 16 May 2016 14:53:15 +0200 Subject: [PATCH 02/27] fixes problems with svgs in tables --- imathics/mathics.js | 48 +++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/imathics/mathics.js b/imathics/mathics.js index 47a82ff..7a102c8 100644 --- a/imathics/mathics.js +++ b/imathics/mathics.js @@ -355,8 +355,7 @@ function translateDOMElement(element, svg) { function createLine(value) { if (value.startsWith('';*/ + // value = '1'; var dom = document.createElement('div'); @@ -381,9 +380,9 @@ function createLine(value) { MathJax.Hub.Queue(['Typeset', MathJax.Hub, container]); - MathJax.Hub.Register.StartupHook("End",function () { - afterProcessResult(container); - }); + afterProcessResult(container); + //MathJax.Hub.Register.StartupHook("End",function () { + //}); return container; } else { @@ -402,22 +401,37 @@ function afterProcessResult(ul, command) { // command is either 'Typeset' (default) or 'Rerender' if (!command) command = 'Typeset'; - MathJax.Hub.Queue(function() { + + var state = {'retries': 0}; + + function relayout() { // inject SVG and other non-MathML objects into corresponding s - // alert('mathjax hub callback: ' + ul.querySelectorAll('.mspace').length + '/' + ul.innerHTML); - Array.prototype.forEach.call(ul.querySelectorAll('.mspace'), function(mspace) { - var id = mspace.getAttribute('mathics_id').substr(objectsPrefix.length); - // alert('mathjax hub callback for ' + id); - var object = objects[id]; - if (object) { - mspace.appendChild(object); - } - }); - }); + + if (ul.querySelector('.MathJax_Error')) { + // alert('mathjax hub: MathJax_Error error found'); + // we're too early, try again later + if (++state.retries < 2) { + MathJax.Hub.Queue(relayout); + } + } else { + // alert('mathjax hub callback: ' + ul.querySelectorAll('.mspace').length + '/' + ul.innerHTML); + Array.prototype.forEach.call(ul.querySelectorAll('.mspace'), function(mspace) { + var id = mspace.getAttribute('mathics_id').substr(objectsPrefix.length); + // alert('mathjax hub callback for ' + id); + var object = objects[id]; + if (object) { + mspace.appendChild(object); + } + }); + } + } + + MathJax.Hub.Queue(relayout); + if (!MathJax.Hub.Browser.isOpera) { // Opera 11.01 Build 1190 on Mac OS X 10.5.8 crashes on this call for Plot[x,{x,0,1}] // => leave inner MathML untouched - MathJax.Hub.Queue(['Typeset', MathJax.Hub, ul]); + // MathJax.Hub.Queue(['Typeset', MathJax.Hub, ul]); } MathJax.Hub.Queue(function() { Array.prototype.forEach.call(ul.querySelectorAll('foreignObject >span >nobr >span.math'), function(math) { From 2a5b13f37344489ed20ba9ad3deede426f67f50e Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Mon, 16 May 2016 17:12:11 +0200 Subject: [PATCH 03/27] fixes for Firefox native MML, fixes for --- imathics/mathics.js | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/imathics/mathics.js b/imathics/mathics.js index 7a102c8..25f2abe 100644 --- a/imathics/mathics.js +++ b/imathics/mathics.js @@ -213,7 +213,11 @@ function createMathNode(nodeName) { } } -var objectsPrefix = 'math_object_'; +function debug(s) { + alert(s); +} + +var mathicsIdName = 'mathics_id'; var objectsCount = 0; var objects = {}; @@ -223,7 +227,12 @@ function translateDOMElement(element, svg) { return $T(text); } var dom = null; - var nodeName = element.nodeName; + var nodeName = element.nodeName.toLowerCase(); + if (nodeName == 'mtext' && element.childNodes[0].nodeName.toLowerCase() == 'img') { + debug('detected special case '); + return translateDOMElement(element.childNodes[0]); // remove here + } + if (nodeName != 'meshgradient' && nodeName != 'graphics3d') { dom = createMathNode(element.nodeName); for (var i = 0; i < element.attributes.length; ++i) { @@ -232,7 +241,7 @@ function translateDOMElement(element, svg) { dom.setAttribute(attr.nodeName, attr.nodeValue); } } - if (nodeName == 'foreignObject') { + if (nodeName == 'foreignobject') { dom.setAttribute('width', svg.getAttribute('width')); dom.setAttribute('height', svg.getAttribute('height')); dom.setAttribute('style', dom.getAttribute('style') + '; text-align: left; padding-left: 2px; padding-right: 2px;'); @@ -280,8 +289,9 @@ function translateDOMElement(element, svg) { drawGraphics3D(div, data); dom = div; } - if (nodeName == 'svg' || nodeName == 'graphics3d') { + if (nodeName == 'svg' || nodeName == 'graphics3d' || nodeName == 'img') { // create that will contain the graphics + debug('looking at node ' + nodeName); object = createMathNode('mspace'); var width, height; if (nodeName == 'svg') { @@ -345,7 +355,8 @@ function translateDOMElement(element, svg) { }); if (object) { var id = objectsCount++; - object.setAttribute('mathics_id', objectsPrefix + id); + // debug('adding object to id ' + id); + object.setAttribute(mathicsIdName, id); objects[id] = dom; return object; } @@ -376,7 +387,7 @@ function createLine(value) { var container = document.createElement('div'); container.appendChild(el); - // alert('mathjax hub typeset: ' + container.innerHTML); + debug('mathjax hub typeset: ' + container.innerHTML); MathJax.Hub.Queue(['Typeset', MathJax.Hub, container]); @@ -408,17 +419,24 @@ function afterProcessResult(ul, command) { // inject SVG and other non-MathML objects into corresponding s if (ul.querySelector('.MathJax_Error')) { - // alert('mathjax hub: MathJax_Error error found'); + debug('mathjax hub: MathJax_Error error found'); // we're too early, try again later if (++state.retries < 2) { MathJax.Hub.Queue(relayout); } } else { - // alert('mathjax hub callback: ' + ul.querySelectorAll('.mspace').length + '/' + ul.innerHTML); - Array.prototype.forEach.call(ul.querySelectorAll('.mspace'), function(mspace) { - var id = mspace.getAttribute('mathics_id').substr(objectsPrefix.length); - // alert('mathjax hub callback for ' + id); + debug('mathjax hub callback: ' + ul.querySelectorAll('.mspace').length + '/' + ul.innerHTML); + + var selector; + if (ul.querySelector('.MathJax_MathML')) { // native MathML (e.g. Firefox) + selector = 'mspace'; + } else { // HTML output (Chrome etc.) + selector = '.mspace'; + } + Array.prototype.forEach.call(ul.querySelectorAll(selector), function(mspace) { + var id = mspace.getAttribute(mathicsIdName); var object = objects[id]; + debug('mathjax hub callback for ' + id + " with object " + JSON.stringify(object)); if (object) { mspace.appendChild(object); } From c33f67fa2ffc1c65a8cb846dcb1bcbe07ca8ad26 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Mon, 16 May 2016 17:26:27 +0200 Subject: [PATCH 04/27] honouring image dimensions now --- imathics/mathics.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imathics/mathics.js b/imathics/mathics.js index 25f2abe..7d9ebfc 100644 --- a/imathics/mathics.js +++ b/imathics/mathics.js @@ -214,7 +214,7 @@ function createMathNode(nodeName) { } function debug(s) { - alert(s); + // alert(s); } var mathicsIdName = 'mathics_id'; @@ -294,9 +294,10 @@ function translateDOMElement(element, svg) { debug('looking at node ' + nodeName); object = createMathNode('mspace'); var width, height; - if (nodeName == 'svg') { + if (nodeName == 'svg' || nodeName == 'img') { width = dom.getAttribute('width'); height = dom.getAttribute('height'); + debug('found ' + nodeName + ': ' + width + '/' + height); } else { // TODO: calculate appropriate height and recalculate on every view change width = height = '400'; From 4d37e3a8ef34dd0f63d1d0c4cd49eecfd569b657 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Mon, 16 May 2016 17:34:57 +0200 Subject: [PATCH 05/27] cleanup, output now hidden during MathJax formatting phase --- imathics/mathics.js | 484 ++++---------------------------------------- 1 file changed, 35 insertions(+), 449 deletions(-) diff --git a/imathics/mathics.js b/imathics/mathics.js index 7d9ebfc..3bb67bf 100644 --- a/imathics/mathics.js +++ b/imathics/mathics.js @@ -74,61 +74,6 @@ function $T(text) { return document.createTextNode(text); } - - -var deleting; -var blurredElement; - -var movedItem; - -var clickedQuery; - -var lastFocus = null; - -function getLetterWidth(element) { - var letter = $E('span', $T('m')); - letter.setStyle({ - fontFamily: element.getStyle('font-family'), - fontSize: element.getStyle('font-size') - }); - var parent = $$('body')[0]; - parent.appendChild(letter); - var width = letter.getWidth(); - parent.removeChild(letter); - delete letter; - return width; -} - -function refreshInputSize(textarea) { - var letterWidth = getLetterWidth(textarea); - var width = textarea.getWidth() - 15; - var lines = textarea.value.split('\n'); - var lineCount = 0; - for (var index = 0; index < lines.length; ++index) { - var line = lines[index]; - lineCount += Math.ceil(1.0 * (line.length + 1) * letterWidth / width); - } - textarea.rows = lineCount; -} - -/*function refreshInputSizes() { - $$('textarea.request').each(function(textarea) { - refreshInputSize(textarea); - }); - - $$('#queries ul').each(function(ul) { - afterProcessResult(ul, 'Rerender'); - }); -}*/ - -function inputChange(event) { - refreshInputSize(this); -} - -function isEmpty(textarea) { - return textarea.value.strip() == '' && !textarea.submitted; -} - function prepareText(text) { if (text == '') { text = String.fromCharCode(160); @@ -387,6 +332,7 @@ function createLine(value) { var container = document.createElement('div'); container.appendChild(el); + container.setAttribute("style", "display:none;"); debug('mathjax hub typeset: ' + container.innerHTML); @@ -409,51 +355,13 @@ function createLine(value) { } } -function afterProcessResult(ul, command) { +function afterProcessResult(container, command) { // command is either 'Typeset' (default) or 'Rerender' if (!command) command = 'Typeset'; - var state = {'retries': 0}; - - function relayout() { - // inject SVG and other non-MathML objects into corresponding s - - if (ul.querySelector('.MathJax_Error')) { - debug('mathjax hub: MathJax_Error error found'); - // we're too early, try again later - if (++state.retries < 2) { - MathJax.Hub.Queue(relayout); - } - } else { - debug('mathjax hub callback: ' + ul.querySelectorAll('.mspace').length + '/' + ul.innerHTML); - - var selector; - if (ul.querySelector('.MathJax_MathML')) { // native MathML (e.g. Firefox) - selector = 'mspace'; - } else { // HTML output (Chrome etc.) - selector = '.mspace'; - } - Array.prototype.forEach.call(ul.querySelectorAll(selector), function(mspace) { - var id = mspace.getAttribute(mathicsIdName); - var object = objects[id]; - debug('mathjax hub callback for ' + id + " with object " + JSON.stringify(object)); - if (object) { - mspace.appendChild(object); - } - }); - } - } - - MathJax.Hub.Queue(relayout); - - if (!MathJax.Hub.Browser.isOpera) { - // Opera 11.01 Build 1190 on Mac OS X 10.5.8 crashes on this call for Plot[x,{x,0,1}] - // => leave inner MathML untouched - // MathJax.Hub.Queue(['Typeset', MathJax.Hub, ul]); - } - MathJax.Hub.Queue(function() { - Array.prototype.forEach.call(ul.querySelectorAll('foreignObject >span >nobr >span.math'), function(math) { + function relayout2() { + Array.prototype.forEach.call(container.querySelectorAll('foreignObject >span >nobr >span.math'), function(math) { var content = math.childNodes[0].childNodes[0].childNodes[0]; math.removeChild(math.childNodes[0]); math.insertBefore(content, math.childNodes[0]); @@ -474,366 +382,44 @@ function afterProcessResult(ul, command) { foreignObject.setAttribute('y', y + 'px'); } }); - }); -} - -/*function setResult(ul, results) { - results.forEach(function(result) { - var resultUl = $E('ul', {'class': 'out'}); - result.out.forEach(function(out) { - var li = $E('li', {'class': (out.message ? 'message' : 'print')}); - if (out.message) - li.appendChild($T(out.prefix + ': ')); - li.appendChild(createLine(out.text)); - resultUl.appendChild(li); - }); - if (result.result != null) { - var li = $E('li', {'class': 'result'}, createLine(result.result)); - resultUl.appendChild(li); - } - ul.appendChild($E('li', {'class': 'out'}, resultUl)); - }); - afterProcessResult(ul); -}*/ - -/*function submitQuery(textarea, onfinish) { - $('welcomeContainer').fade({duration: 0.5}); - - textarea.li.addClassName('loading'); - new Ajax.Request('/ajax/query/', { - method: 'post', - parameters: { - query: textarea.value - }, - onSuccess: function(transport) { - textarea.ul.select('li[class!=request][class!=submitbutton]').invoke('deleteElement'); - if (!transport.responseText) { - // A fatal Python error has occured, e.g. on 4.4329408320439^43214234345 - // ("Fatal Python error: mp_reallocate failure") - // -> print overflow message - transport.responseText = '{"results": [{"out": [{"prefix": "General::noserver", "message": true, "tag": "noserver", "symbol": "General", "text": "No server running."}]}]}'; - } - var response = transport.responseText.evalJSON(); - setResult(textarea.ul, response.results); - textarea.submitted = true; - textarea.results = response.results; - var next = textarea.li.nextSibling; - if (next) - next.textarea.focus(); - else - createQuery(); - }, - onFailure: function(transport) { - textarea.ul.select('li[class!=request]').invoke('deleteElement'); - var li = $E('li', {'class': 'serverError'}, $T("Sorry, an error occurred while processing your request!")); - textarea.ul.appendChild(li); - textarea.submitted = true; - }, - onComplete: function() { - textarea.li.removeClassName('loading'); - if (onfinish) - onfinish(); - } - }); -}*/ - -function getSelection() { - // TODO -} - -/*function keyDown(event) { - var textarea = lastFocus; - if (!textarea) - return; - refreshInputSize(textarea); - - if (event.keyCode == Event.KEY_RETURN && (event.shiftKey || event.keyLocation == 3)) { - if (!Prototype.Browser.IE) - event.stop(); - - var query = textarea.value.strip(); - if (query) { - submitQuery(textarea); - } - } else if (event.keyCode == Event.KEY_UP) { - if (textarea.selectionStart == 0 && textarea.selectionEnd == 0) { - if (isEmpty(textarea)) { - if (textarea.li.previousSibling) - textarea.li.previousSibling.textarea.focus(); - } else - createQuery(textarea.li); - } - } else if (event.keyCode == Event.KEY_DOWN) { - if (textarea.selectionStart == textarea.value.length && textarea.selectionEnd == textarea.selectionStart) { - if (isEmpty(textarea)) { - if (textarea.li.nextSibling) - textarea.li.nextSibling.textarea.focus(); - } else - createQuery(textarea.li.nextSibling); - } - } else - if (isGlobalKey(event)) - event.stop(); -}*/ - -/*function deleteMouseDown(event) { - if (event.isLeftClick()) - deleting = true; -} - -function deleteClick(event) { - if (lastFocus == this.li.textarea) - lastFocus = null; - this.li.deleteElement(); - deleting = false; - if (blurredElement) { - blurredElement.focus(); - blurredElement = null; } - if ($('queries').childElements().length == 0) - createQuery(); -} - -function moveMouseDown(event) { - movedItem = this.li; - movedItem.addClassName('moving'); -} - -function moveMouseUp(event) { - if (movedItem) { - movedItem.removeClassName('moving'); - movedItem.textarea.focus(); - movedItem = null; - } -} -function onFocus(event) { - var textarea = this; - textarea.li.addClassName('focused'); - lastFocus = textarea; -} - -function onBlur(event) { - var textarea = this; - blurredElement = textarea; - if (!deleting && textarea.li != movedItem && isEmpty(textarea) && $('queries').childElements().length > 1) { - textarea.li.hide(); - if (textarea == lastFocus) - lastFocus = null; - window.setTimeout(function() { - textarea.li.deleteElement(); - }, 10); - } - textarea.li.removeClassName('focused'); -} + var state = {'retries': 0}; -function createSortable() { - Position.includeScrollOffsets = true; - Sortable.create('queries', { - handle: 'move', - scroll: 'document', - scrollSensitivity: 1 // otherwise strange flying-away of item at top - }); -} + function relayout() { + // inject SVG and other non-MathML objects into corresponding s -var queryIndex = 0; -*/ -/*function createQuery(before, noFocus, updatingAll) { - var ul, textarea, moveHandle, deleteHandle, submitButton; - // Items need id in order for Sortable.onUpdate to work. - var li = $E('li', {'id': 'query_' + queryIndex++, 'class': 'query'}, - ul = $E('ul', {'class': 'query'}, - $E('li', {'class': 'request'}, - textarea = $E('textarea', {'class': 'request'}), - $E('span', {'class': 'submitbutton', 'title': "Submit [Shift+Return]"}, - submitButton = $E('span', $T('=')) - ) - ) - ), - moveHandle = $E('span', {'class': 'move'}), - deleteHandle = $E('span', {'class': 'delete', 'title': "Delete"}, $T(String.fromCharCode(215))) - ); - textarea.rows = 1; - textarea.ul = ul; - textarea.li = li; - textarea.submitted = false; - moveHandle.li = li; - deleteHandle.li = li; - li.textarea = textarea; - li.ul = ul; - if (before) - $('queries').insertBefore(li, before); - else - $('queries').appendChild(li); - if (!updatingAll) - refreshInputSize(textarea); - new Form.Element.Observer(textarea, 0.2, inputChange.bindAsEventListener(textarea)); - textarea.observe('focus', onFocus.bindAsEventListener(textarea)); - textarea.observe('blur', onBlur.bindAsEventListener(textarea)); - li.observe('mousedown', queryMouseDown.bindAsEventListener(li)); - deleteHandle.observe('click', deleteClick.bindAsEventListener(deleteHandle)); - deleteHandle.observe('mousedown', deleteMouseDown.bindAsEventListener(deleteHandle)); - moveHandle.observe('mousedown', moveMouseDown.bindAsEventListener(moveHandle)); - moveHandle.observe('mouseup', moveMouseUp.bindAsEventListener(moveHandle)); - $(document).observe('mouseup', moveMouseUp.bindAsEventListener($(document))); - submitButton.observe('mousedown', function() { - if (textarea.value.strip()) - submitQuery(textarea); - else - window.setTimeout(function() { - textarea.focus(); - }, 10); - }); - if (!updatingAll) { - createSortable(); - // calling directly fails in Safari on document loading - //window.setTimeout(createSortable, 10); - } - // Immediately setting focus doesn't work in IE. - if (!noFocus) - window.setTimeout(function() { - textarea.focus(); - }, 10); - return li; -}*/ -/* -var mouseDownEvent = null; - -function documentMouseDown(event) { - if (event.isLeftClick()) { - if (clickedQuery) { - clickedQuery = null; - mouseDownEvent = null; - return; - } - event.stop(); // strangely, doesn't work otherwise - mouseDownEvent = event; - } -} -*/ -/*function documentClick(event) { - // In Firefox, mousedown also fires when user clicks scrollbars. - // -> listen to click - event = mouseDownEvent; - if (!event) - return; - if ($('queries').childElements().length == 1 && isEmpty($('queries').childElements()[0].textarea)) { - $('queries').childElements()[0].textarea.focus(); - return; - } - var offset = $('document').cumulativeOffset(); - var y = event.pointerY() - offset.top + $('document').scrollTop; - var element = null; - $('queries').childElements().forEach(function(li) { - var offset = li.positionedOffset(); // margin-top: 10px - if (offset.top + 20 > y) { - element = li; - throw $break; - } - }); - createQuery(element); -}*/ -/* -function queryMouseDown(event) { - clickedQuery = this; -} + if (container.querySelector('.MathJax_Error')) { + debug('mathjax hub: MathJax_Error error found'); + // we're too early, try again later + if (++state.retries < 2) { + MathJax.Hub.Queue(relayout); + } else { + container.setAttribute('style', ''); // display + } + } else { + debug('mathjax hub callback: ' + container.querySelectorAll('.mspace').length + '/' + container.innerHTML); -function focusLast() { - if (lastFocus) - lastFocus.focus(); - else - createQuery(); -} + var selector; + if (container.querySelector('.MathJax_MathML')) { // native MathML (e.g. Firefox) + selector = 'mspace'; + } else { // HTML output (Chrome etc.) + selector = '.mspace'; + } + Array.prototype.forEach.call(container.querySelectorAll(selector), function(mspace) { + var id = mspace.getAttribute(mathicsIdName); + var object = objects[id]; + debug('mathjax hub callback for ' + id + " with object " + JSON.stringify(object)); + if (object) { + mspace.appendChild(object); + } + }); -function isGlobalKey(event) { - if (event.ctrlKey) { - switch(event.keyCode) { - case 68: - case 67: - case 83: - case 79: - return true; - } - } - return false; -} + relayout2(); -function globalKeyUp(event) { - if (!popup && event.ctrlKey) { - switch (event.keyCode) { - case 68: // D - $('search').select(); - event.stop(); - break; - case 67: // C - focusLast(); - event.stop(); - break; - case 83: // S - showSave(); - break; - case 79: // O - showOpen(); - break; - } + container.setAttribute('style', ''); // display + } } -} - -function domLoaded() { - MathJax.Hub.Config({ - "HTML-CSS": { - imageFont: null, - linebreaks: { automatic: true } - }, - MMLorHTML: { - // - // The output jax that is to be preferred when both are possible - // (set to "MML" for native MathML, "HTML" for MathJax's HTML-CSS output jax). - // - prefer: { - MSIE: "HTML", - Firefox: "HTML", - Opera: "HTML", - other: "HTML" - } - } - }); - MathJax.Hub.Configured(); - - if ($('welcomeBrowser')) - if (!(Prototype.Browser.WebKit || Prototype.Browser.MobileSafari || Prototype.Browser.Gecko)) - $('welcomeBrowser').show(); - - $$('body')[0].observe('resize', refreshInputSizes); - - if ($('queriesContainer')) { - $('queriesContainer').appendChild($E('ul', {'id': 'queries'})); - $('document').observe('mousedown', documentMouseDown.bindAsEventListener($('document'))); - $('document').observe('click', documentClick.bindAsEventListener($('document'))); - - $(document).observe('keydown', keyDown.bindAsEventListener()); - if (Prototype.Browser.IE) { - document.body.addEventListener('keydown', function(event) { - if (event.keyCode == Event.KEY_RETURN && event.shiftKey) { - event.stopPropagation(); - event.preventDefault(); - keyDown(event); - } - }, true); - } - if (Prototype.Browser.Opera || Prototype.Browser.IE) { - // Opera needs another hook so it doesn't insert newlines after Shift+Return - $(document).observe('keypress', function(event) { - if (event.keyCode == Event.KEY_RETURN && event.shiftKey) - event.stop(); - }.bindAsEventListener()); - } - - $(document).observe('keyup', globalKeyUp.bindAsEventListener($('document'))); - - if (!loadLink()) - createQuery(); - } + MathJax.Hub.Queue(relayout); } -*/ \ No newline at end of file From 43e7db604f0ebf16b0a5e064c43460b3190af446 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Fri, 12 Aug 2016 06:47:10 +0200 Subject: [PATCH 06/27] work in progress: Jupyter 5/new MathJax/updated Mathics --- imathics/kernel.py | 122 ++++++++++++++++++++++++++++++-------------- imathics/mathics.js | 40 +++++++++------ setup.py | 4 ++ 3 files changed, 114 insertions(+), 52 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 5d57e41..7b66f78 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -5,9 +5,12 @@ from ipykernel.comm import CommManager from mathics.core.definitions import Definitions -from mathics.core.evaluation import Evaluation, Message, Result +from mathics.core.evaluation import Evaluation, Message, Result, Callbacks from mathics.core.expression import Integer -from mathics.core.parser import parse_lines, IncompleteSyntaxError, TranslateError, MathicsScanner, ScanError +from mathics.core.parser import IncompleteSyntaxError, TranslateError, ScanError +from mathics.core.parser.util import parse +from mathics.core.parser.feed import SingleLineFeeder +from mathics.core.parser.tokeniser import Tokeniser from mathics.builtin import builtins from mathics import settings from mathics.version import __version__ @@ -16,6 +19,47 @@ import base64 +def parse_lines(lines, definitions): + ''' + Given some lines of code try to construct a list of expressions. + + In the case of incomplete lines append more lines until a complete + expression is found. If the end is reached and no complete expression is + found then reraise the exception. + + We use a generator so that each expression can be evaluated (as the parser + is dependent on defintions and evaluation may change the definitions). + ''' + query = '' + lines = lines.splitlines() + + incomplete_exc = None + for line in lines: + if not line: + query += ' ' + continue + query += line + if query.endswith('\\'): + query = query.rstrip('\\') + incomplete_exc = IncompleteSyntaxError(len(query)-1) + continue + try: + expression = parse(SingleLineFeeder(lines), definitions) + except IncompleteSyntaxError as exc: + incomplete_exc = exc + else: + if expression is not None: + yield expression + query = '' + incomplete_exc = None + + if incomplete_exc is not None: + # ran out of lines + raise incomplete_exc + + raise StopIteration + + class MathicsKernel(Kernel): implementation = 'Mathics' implementation_version = '0.1' @@ -47,20 +91,27 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, 'user_expressions': {}, } - evaluation = Evaluation(self.definitions, result_callback=self.result_callback, - out_callback=self.out_callback, clear_output_callback=self.clear_output_callback, - display_data_callback=self.display_data_callback) + formats = { + 'text/plain': 'text', + 'text/html': 'xml', + 'text/latex': 'tex', + } + + evaluation = Evaluation(self.definitions, callbacks=Callbacks(out=self.out_callback), format=formats) try: - results = evaluation.parse_evaluate(code, timeout=settings.TIMEOUT) + result = evaluation.parse_evaluate(code, timeout=settings.TIMEOUT) + if result: + self.result_callback(result) except Exception as exc: # internal error response['status'] = 'error' response['ename'] = 'System:exception' response['traceback'] = traceback.format_exception(*sys.exc_info()) - results = [] + result = [] else: response['status'] = 'ok' response['execution_count'] = self.definitions.get_line_no() + return response def out_callback(self, out): @@ -79,40 +130,40 @@ def out_callback(self, out): self.send_response(self.iopub_socket, 'stream', content) def result_callback(self, result): - mathics_js = "" - - with open(os.path.dirname(os.path.abspath(__file__)) + '/mathics.js', 'r') as f: - mathics_js += f.read() + if False: + mathics_js = "" - html = result.data['text/html'] + with open(os.path.dirname(os.path.abspath(__file__)) + '/mathics.js', 'r') as f: + mathics_js += f.read() - js = " + """ # result.data['text/html']) - - """ # result.data['text/html']) + data = {'text/html': js} + else: + html = result.result['text/html'] - # js = "var cell = Jupyter.notebook.insert_cell_at_bottom('markdown'); cell.set_text(text);" - # js = "" + # see https://github.com/mathjax/MathJax/issues/896 - data = {'text/html': js} + data = {'text/html': html} content = { 'execution_count': result.line_no, 'data': data, # result.data, - 'metadata': result.metadata, + 'metadata': {}, } self.send_response(self.iopub_socket, 'execute_result', content) @@ -205,26 +256,23 @@ def find_symbol_name(code, cursor_pos): >>> MathicsKernel.find_symbol_name('Sin `', 4) ''' - scanner = MathicsScanner() - scanner.build() - scanner.lexer.input(code) + tokeniser = Tokeniser(SingleLineFeeder(code)) start_pos = None end_pos = None name = None while True: try: - token = scanner.lexer.token() + token = tokeniser.next() except ScanError: - scanner.lexer.skip(1) continue - if token is None: + if token.tag == 'END': break # ran out of tokens # find first token which contains cursor_pos - if scanner.lexer.lexpos >= cursor_pos: - if token.type == 'symbol': - name = token.value - start_pos = token.lexpos - end_pos = scanner.lexer.lexpos + if tokeniser.pos >= cursor_pos: + if token.tag == 'Symbol': + name = token.text + start_pos = token.pos + end_pos = tokeniser.pos break return start_pos, end_pos, name diff --git a/imathics/mathics.js b/imathics/mathics.js index 3bb67bf..137f52f 100644 --- a/imathics/mathics.js +++ b/imathics/mathics.js @@ -304,6 +304,7 @@ function translateDOMElement(element, svg) { // debug('adding object to id ' + id); object.setAttribute(mathicsIdName, id); objects[id] = dom; + debug('setting object ' + id + ' to ' + dom + '/' + JSON.stringify(objects)); return object; } return dom; @@ -398,22 +399,31 @@ function afterProcessResult(container, command) { container.setAttribute('style', ''); // display } } else { - debug('mathjax hub callback: ' + container.querySelectorAll('.mspace').length + '/' + container.innerHTML); + var selector = 'script[type="math/mml"]'; - var selector; - if (container.querySelector('.MathJax_MathML')) { // native MathML (e.g. Firefox) - selector = 'mspace'; - } else { // HTML output (Chrome etc.) - selector = '.mspace'; - } - Array.prototype.forEach.call(container.querySelectorAll(selector), function(mspace) { - var id = mspace.getAttribute(mathicsIdName); - var object = objects[id]; - debug('mathjax hub callback for ' + id + " with object " + JSON.stringify(object)); - if (object) { - mspace.appendChild(object); - } - }); + debug('mathjax hub callback: ' + container.querySelectorAll(selector).length + '/' + container.innerHTML); + + /* example: + """ + if False: mathics_js = "" @@ -158,7 +171,7 @@ def result_callback(self, result): # see https://github.com/mathjax/MathJax/issues/896 - data = {'text/html': html} + data = {'text/html': safeModeJS + html} content = { 'execution_count': result.line_no, From 232d6021dbd978a76c57a997a2e58d39200d6bdb Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Mon, 15 Aug 2016 18:16:07 +0200 Subject: [PATCH 08/27] output for svgs and imgs based on mglyphs --- imathics/kernel.py | 130 +++++++++++++++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 34 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 699d0de..41b197d 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -5,7 +5,7 @@ from ipykernel.comm import CommManager from mathics.core.definitions import Definitions -from mathics.core.evaluation import Evaluation, Message, Result, Callbacks +from mathics.core.evaluation import Evaluation, Message, Result, Output from mathics.core.expression import Integer from mathics.core.parser import IncompleteSyntaxError, TranslateError, ScanError from mathics.core.parser.util import parse @@ -15,6 +15,8 @@ from mathics import settings from mathics.version import __version__ from mathics.doc.doc import Doc + +from string import Template import os import base64 @@ -60,6 +62,46 @@ def parse_lines(lines, definitions): raise StopIteration +class KernelOutput(Output): + svg = Template(''' + + $data + + ''') + + def __init__(self, kernel): + self.kernel = kernel + + def out(self, out): + self.kernel.out_callback(out) + + def clear_output(self, wait=False): + self.kernel.clear_output_callback(wait=wait) + + def display_data(self, result): + self.kernel.display_data_callback(result) + + def svg_xml(self, data, width, height, viewbox): + # relies on https://github.com/jupyter/notebook/pull/1680 + svg = self.svg.substitute( + data=data, + viewbox=' '.join(['%f' % t for t in viewbox])) + return '' % ( + int(width), + int(height), + base64.b64encode(svg.encode('utf8')).decode('utf8')) + + def img_xml(self, data, width, height): + # relies on https://github.com/jupyter/notebook/pull/1680 + return '' % ( + int(width), + int(height), + data) + + class MathicsKernel(Kernel): implementation = 'Mathics' implementation_version = '0.1' @@ -97,7 +139,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, 'text/latex': 'tex', } - evaluation = Evaluation(self.definitions, callbacks=Callbacks(out=self.out_callback), format=formats) + evaluation = Evaluation(self.definitions, output=KernelOutput(self), format=formats) try: result = evaluation.parse_evaluate(code, timeout=settings.TIMEOUT) if result: @@ -129,53 +171,71 @@ def out_callback(self, out): raise ValueError('Unknown out') self.send_response(self.iopub_socket, 'stream', content) - def result_callback(self, result): - safeModeJS = """""" + def legacy_result_callback(self, result): + # this is code that tries to replicate the classic Mathics server JavaScript logic. hopefully we + # can find a better way. - if False: - mathics_js = "" + mathics_js = "" - with open(os.path.dirname(os.path.abspath(__file__)) + '/mathics.js', 'r') as f: - mathics_js += f.read() + with open(os.path.dirname(os.path.abspath(__file__)) + '/mathics.js', 'r') as f: + mathics_js += f.read() - html = result.result['text/html'] + html = result.result['text/html'] - js = " - """ # result.data['text/html']) + + """ - data = {'text/html': js} - else: - html = result.result['text/html'] + data = {'text/html': js} + + content = { + 'execution_count': result.line_no, + 'data': data, # result.data, + 'metadata': {}, + } + self.send_response(self.iopub_socket, 'execute_result', content) + + def reconfigure_mathjax(self): + # Jupyter's default MathJax configuration ("safe" mode) blocks the use + # of data uris which we use in mglyphs for displaying svgs and imgs. + # enable the "data" protocol here. also remove font size restrictions. + + safeModeJS = """ + MathJax.Hub.Config({ + Safe: { + safeProtocols: { + data: true + }, + allow: { + fontsize: "all" + } + } + }); + """ - # see https://github.com/mathjax/MathJax/issues/896 + # see http://jupyter-client.readthedocs.org/en/latest/messaging.html + content = { + 'data': {'application/javascript': safeModeJS}, + 'metadata': {}, + } + self.send_response(self.iopub_socket, 'display_data', content) - data = {'text/html': safeModeJS + html} + def result_callback(self, result): + self.reconfigure_mathjax() content = { 'execution_count': result.line_no, - 'data': data, # result.data, + 'data': result.result, 'metadata': {}, } self.send_response(self.iopub_socket, 'execute_result', content) @@ -186,6 +246,8 @@ def clear_output_callback(self, wait=False): self.send_response(self.iopub_socket, 'clear_output', content) def display_data_callback(self, result): + self.reconfigure_mathjax() + # see http://jupyter-client.readthedocs.org/en/latest/messaging.html content = { 'data': result.data, From 41900a9a51c1b8c476d642b78e47e54bb066344d Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Tue, 16 Aug 2016 15:22:10 +0200 Subject: [PATCH 09/27] KernelOutput changed to match new Output class definition --- imathics/kernel.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 41b197d..f808f86 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -63,15 +63,6 @@ def parse_lines(lines, definitions): class KernelOutput(Output): - svg = Template(''' - - $data - - ''') - def __init__(self, kernel): self.kernel = kernel @@ -84,23 +75,6 @@ def clear_output(self, wait=False): def display_data(self, result): self.kernel.display_data_callback(result) - def svg_xml(self, data, width, height, viewbox): - # relies on https://github.com/jupyter/notebook/pull/1680 - svg = self.svg.substitute( - data=data, - viewbox=' '.join(['%f' % t for t in viewbox])) - return '' % ( - int(width), - int(height), - base64.b64encode(svg.encode('utf8')).decode('utf8')) - - def img_xml(self, data, width, height): - # relies on https://github.com/jupyter/notebook/pull/1680 - return '' % ( - int(width), - int(height), - data) - class MathicsKernel(Kernel): implementation = 'Mathics' From b42f705eddec469ab74e12f6c1cbf913d69233b5 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Wed, 17 Aug 2016 13:03:59 +0200 Subject: [PATCH 10/27] changes for Manipulate for Jupyter 5, changed Output interface --- imathics/kernel.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index f808f86..dc816d7 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -3,6 +3,8 @@ from ipykernel.kernelbase import Kernel from ipykernel.comm import CommManager +from traitlets import Instance, Type, Any +from ipykernel.zmqshell import ZMQInteractiveShell from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation, Message, Result, Output @@ -16,7 +18,6 @@ from mathics.version import __version__ from mathics.doc.doc import Doc -from string import Template import os import base64 @@ -69,11 +70,11 @@ def __init__(self, kernel): def out(self, out): self.kernel.out_callback(out) - def clear_output(self, wait=False): + def clear(self, wait=False): self.kernel.clear_output_callback(wait=wait) - def display_data(self, result): - self.kernel.display_data_callback(result) + def display(self, data, metadata): + self.kernel.display_data_callback(data, metadata) class MathicsKernel(Kernel): @@ -86,6 +87,12 @@ class MathicsKernel(Kernel): } banner = "Mathics kernel" # TODO + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) + shell_class = Type(ZMQInteractiveShell) + + user_module = Any() + user_ns = Instance(dict, args=None, allow_none=True) + def __init__(self, **kwargs): Kernel.__init__(self, **kwargs) self.definitions = Definitions(add_builtin=True) # TODO Cache @@ -93,6 +100,20 @@ def __init__(self, **kwargs): self.establish_comm_manager() # needed for ipywidgets and Manipulate[] def establish_comm_manager(self): + # see ipykernel/ipkernel.py + + self.shell = self.shell_class.instance( + parent=self, + profile_dir=self.profile_dir, + user_module=self.user_module, + user_ns=self.user_ns, + kernel=self) + self.shell.displayhook.session = self.session + self.shell.displayhook.pub_socket = self.iopub_socket + self.shell.displayhook.topic = self._topic('execute_result') + self.shell.display_pub.session = self.session + self.shell.display_pub.pub_socket = self.iopub_socket + self.comm_manager = CommManager(parent=self, kernel=self) comm_msg_types = ['comm_open', 'comm_msg', 'comm_close'] for msg_type in comm_msg_types: @@ -219,13 +240,13 @@ def clear_output_callback(self, wait=False): content = dict(wait=wait) self.send_response(self.iopub_socket, 'clear_output', content) - def display_data_callback(self, result): + def display_data_callback(self, data, metadata): self.reconfigure_mathjax() # see http://jupyter-client.readthedocs.org/en/latest/messaging.html content = { - 'data': result.data, - 'metadata': result.metadata, + 'data': data, + 'metadata': metadata, } self.send_response(self.iopub_socket, 'display_data', content) From 081bdf983091eaa69da5139eb7d6ef00cdf69b34 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Thu, 18 Aug 2016 09:34:31 +0200 Subject: [PATCH 11/27] set processSectionDelay to 0 for Manipulate --- imathics/kernel.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/imathics/kernel.py b/imathics/kernel.py index dc816d7..389b000 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -205,6 +205,10 @@ def reconfigure_mathjax(self): # of data uris which we use in mglyphs for displaying svgs and imgs. # enable the "data" protocol here. also remove font size restrictions. + # we set processSectionDelay to 0 since that drastically improves the + # visual experience of Manipulate as there's a lot less jitter, also see + # http://docs.mathjax.org/en/latest/api/hub.html + safeModeJS = """ MathJax.Hub.Config({ Safe: { @@ -216,6 +220,8 @@ def reconfigure_mathjax(self): } } }); + + MathJax.Hub.processSectionDelay = 0; """ # see http://jupyter-client.readthedocs.org/en/latest/messaging.html From f46952770f69a6bea0cac235fdb512253e171e7a Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Tue, 30 Aug 2016 13:12:16 +0200 Subject: [PATCH 12/27] support for LayoutEngine --- imathics/kernel.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/imathics/kernel.py b/imathics/kernel.py index 389b000..fdc5bf6 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -17,6 +17,7 @@ from mathics import settings from mathics.version import __version__ from mathics.doc.doc import Doc +from mathics.layout.client import LayoutEngine import os import base64 @@ -65,8 +66,12 @@ def parse_lines(lines, definitions): class KernelOutput(Output): def __init__(self, kernel): + super(KernelOutput, self).__init__(kernel.layout_engine) self.kernel = kernel + def layout(self): + return 'svg' + def out(self, out): self.kernel.out_callback(out) @@ -98,6 +103,7 @@ def __init__(self, **kwargs): self.definitions = Definitions(add_builtin=True) # TODO Cache self.definitions.set_ownvalue('$Line', Integer(0)) # Reset the line number self.establish_comm_manager() # needed for ipywidgets and Manipulate[] + self.layout_engine = LayoutEngine() def establish_comm_manager(self): # see ipykernel/ipkernel.py From 6c5208818e4d1c21f32f34fa140f373adca04291 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Tue, 30 Aug 2016 16:11:09 +0200 Subject: [PATCH 13/27] changed layout() to svgify() --- imathics/kernel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index fdc5bf6..0660073 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -69,8 +69,8 @@ def __init__(self, kernel): super(KernelOutput, self).__init__(kernel.layout_engine) self.kernel = kernel - def layout(self): - return 'svg' + def svgify(self): + return True def out(self, out): self.kernel.out_callback(out) From e1f44257b5276f2e7ef89dee2e8ff57f05b65f9d Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Wed, 31 Aug 2016 16:04:59 +0200 Subject: [PATCH 14/27] better error handling and reporting --- imathics/kernel.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 0660073..883a6cb 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -7,7 +7,7 @@ from ipykernel.zmqshell import ZMQInteractiveShell from mathics.core.definitions import Definitions -from mathics.core.evaluation import Evaluation, Message, Result, Output +from mathics.core.evaluation import Evaluation, Message, Result, Output, Print from mathics.core.expression import Integer from mathics.core.parser import IncompleteSyntaxError, TranslateError, ScanError from mathics.core.parser.util import parse @@ -103,7 +103,7 @@ def __init__(self, **kwargs): self.definitions = Definitions(add_builtin=True) # TODO Cache self.definitions.set_ownvalue('$Line', Integer(0)) # Reset the line number self.establish_comm_manager() # needed for ipywidgets and Manipulate[] - self.layout_engine = LayoutEngine() + self.layout_engine = None def establish_comm_manager(self): # see ipykernel/ipkernel.py @@ -140,19 +140,26 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, 'text/latex': 'tex', } - evaluation = Evaluation(self.definitions, output=KernelOutput(self), format=formats) try: + if self.layout_engine is None: + self.layout_engine = LayoutEngine() + + evaluation = Evaluation(self.definitions, output=KernelOutput(self), format=formats) + result = evaluation.parse_evaluate(code, timeout=settings.TIMEOUT) + if result: self.result_callback(result) except Exception as exc: + self.out_callback(Print('An error occured: ') + str(exc)) + # internal error response['status'] = 'error' response['ename'] = 'System:exception' response['traceback'] = traceback.format_exception(*sys.exc_info()) - result = [] else: response['status'] = 'ok' + response['execution_count'] = self.definitions.get_line_no() return response From 17b3b94cd87ef57742241f0af39f20ae4c8c18fa Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Wed, 31 Aug 2016 16:07:03 +0200 Subject: [PATCH 15/27] fixes typo --- imathics/kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 883a6cb..e9b6321 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -151,7 +151,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, if result: self.result_callback(result) except Exception as exc: - self.out_callback(Print('An error occured: ') + str(exc)) + self.out_callback(Print('An error occured: ' + str(exc))) # internal error response['status'] = 'error' From 4839fbc3df68483ebf5c5740ec291d50c8d7f460 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Wed, 31 Aug 2016 16:29:16 +0200 Subject: [PATCH 16/27] LayoutEngine error handling --- imathics/kernel.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index e9b6321..05bf550 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -125,6 +125,10 @@ def establish_comm_manager(self): for msg_type in comm_msg_types: self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) + def init_layout_engine(self): + if self.layout_engine is None: + self.layout_engine = LayoutEngine() + def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): # TODO update user definitions @@ -141,8 +145,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, } try: - if self.layout_engine is None: - self.layout_engine = LayoutEngine() + self.init_layout_engine() evaluation = Evaluation(self.definitions, output=KernelOutput(self), format=formats) From 3a4710fd5c39374af359c69afb74feaa3a2995f4 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Fri, 2 Sep 2016 07:45:17 +0200 Subject: [PATCH 17/27] removed mathjax settings js injection --- imathics/kernel.py | 69 ---------------------------------------------- 1 file changed, 69 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 05bf550..be9b407 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -182,74 +182,7 @@ def out_callback(self, out): raise ValueError('Unknown out') self.send_response(self.iopub_socket, 'stream', content) - def legacy_result_callback(self, result): - # this is code that tries to replicate the classic Mathics server JavaScript logic. hopefully we - # can find a better way. - - mathics_js = "" - - with open(os.path.dirname(os.path.abspath(__file__)) + '/mathics.js', 'r') as f: - mathics_js += f.read() - - html = result.result['text/html'] - - js = " - """ - - data = {'text/html': js} - - content = { - 'execution_count': result.line_no, - 'data': data, # result.data, - 'metadata': {}, - } - self.send_response(self.iopub_socket, 'execute_result', content) - - def reconfigure_mathjax(self): - # Jupyter's default MathJax configuration ("safe" mode) blocks the use - # of data uris which we use in mglyphs for displaying svgs and imgs. - # enable the "data" protocol here. also remove font size restrictions. - - # we set processSectionDelay to 0 since that drastically improves the - # visual experience of Manipulate as there's a lot less jitter, also see - # http://docs.mathjax.org/en/latest/api/hub.html - - safeModeJS = """ - MathJax.Hub.Config({ - Safe: { - safeProtocols: { - data: true - }, - allow: { - fontsize: "all" - } - } - }); - - MathJax.Hub.processSectionDelay = 0; - """ - - # see http://jupyter-client.readthedocs.org/en/latest/messaging.html - content = { - 'data': {'application/javascript': safeModeJS}, - 'metadata': {}, - } - self.send_response(self.iopub_socket, 'display_data', content) - def result_callback(self, result): - self.reconfigure_mathjax() - content = { 'execution_count': result.line_no, 'data': result.result, @@ -263,8 +196,6 @@ def clear_output_callback(self, wait=False): self.send_response(self.iopub_socket, 'clear_output', content) def display_data_callback(self, data, metadata): - self.reconfigure_mathjax() - # see http://jupyter-client.readthedocs.org/en/latest/messaging.html content = { 'data': data, From b268087e54cbf6c91181c6da2d518a22757a8f5c Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Fri, 2 Sep 2016 16:46:58 +0200 Subject: [PATCH 18/27] undo removal of reconfigure_mathjax --- imathics/kernel.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/imathics/kernel.py b/imathics/kernel.py index be9b407..4460b6e 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -182,7 +182,40 @@ def out_callback(self, out): raise ValueError('Unknown out') self.send_response(self.iopub_socket, 'stream', content) + def reconfigure_mathjax(self): + # Jupyter's default MathJax configuration ("safe" mode) blocks the use + # of data uris which we use in mglyphs for displaying svgs and imgs. + # enable the "data" protocol here. also remove font size restrictions. + + # we set processSectionDelay to 0 since that drastically improves the + # visual experience of Manipulate as there's a lot less jitter, also see + # http://docs.mathjax.org/en/latest/api/hub.html + + safeModeJS = """ + MathJax.Hub.Config({ + Safe: { + safeProtocols: { + data: true + }, + allow: { + fontsize: "all" + } + } + }); + + MathJax.Hub.processSectionDelay = 0; + """ + + # see http://jupyter-client.readthedocs.org/en/latest/messaging.html + content = { + 'data': {'application/javascript': safeModeJS}, + 'metadata': {}, + } + self.send_response(self.iopub_socket, 'display_data', content) + def result_callback(self, result): + self.reconfigure_mathjax() + content = { 'execution_count': result.line_no, 'data': result.result, @@ -196,6 +229,8 @@ def clear_output_callback(self, wait=False): self.send_response(self.iopub_socket, 'clear_output', content) def display_data_callback(self, data, metadata): + self.reconfigure_mathjax() + # see http://jupyter-client.readthedocs.org/en/latest/messaging.html content = { 'data': data, From 4b19d680daa3b5d0c3d4730c6563d37293e43c9d Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Sun, 4 Sep 2016 18:32:28 +0200 Subject: [PATCH 19/27] renamed LayoutEngine to WebEngine --- imathics/kernel.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 4460b6e..af5651d 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -17,7 +17,7 @@ from mathics import settings from mathics.version import __version__ from mathics.doc.doc import Doc -from mathics.layout.client import LayoutEngine +from mathics.layout.client import WebEngine import os import base64 @@ -66,7 +66,7 @@ def parse_lines(lines, definitions): class KernelOutput(Output): def __init__(self, kernel): - super(KernelOutput, self).__init__(kernel.layout_engine) + super(KernelOutput, self).__init__(kernel.web_engine) self.kernel = kernel def svgify(self): @@ -103,7 +103,7 @@ def __init__(self, **kwargs): self.definitions = Definitions(add_builtin=True) # TODO Cache self.definitions.set_ownvalue('$Line', Integer(0)) # Reset the line number self.establish_comm_manager() # needed for ipywidgets and Manipulate[] - self.layout_engine = None + self.web_engine = None def establish_comm_manager(self): # see ipykernel/ipkernel.py @@ -125,9 +125,9 @@ def establish_comm_manager(self): for msg_type in comm_msg_types: self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) - def init_layout_engine(self): - if self.layout_engine is None: - self.layout_engine = LayoutEngine() + def init_web_engine(self): + if self.web_engine is None: + self.web_engine = WebEngine() def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): @@ -145,7 +145,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, } try: - self.init_layout_engine() + self.init_web_engine() evaluation = Evaluation(self.definitions, output=KernelOutput(self), format=formats) From f990d3ce36981b3a5b1836291be9ff92a01f44a1 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Sun, 4 Sep 2016 22:56:39 +0200 Subject: [PATCH 20/27] enable warnings about missing web engine --- imathics/kernel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index af5651d..de811d1 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -69,9 +69,6 @@ def __init__(self, kernel): super(KernelOutput, self).__init__(kernel.web_engine) self.kernel = kernel - def svgify(self): - return True - def out(self, out): self.kernel.out_callback(out) @@ -81,6 +78,9 @@ def clear(self, wait=False): def display(self, data, metadata): self.kernel.display_data_callback(data, metadata) + def warn_about_web_engine(self): + return True + class MathicsKernel(Kernel): implementation = 'Mathics' From f8ae73ce4710b4d1d53e9466ab855935b8fd1b14 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Tue, 13 Sep 2016 14:08:13 +0200 Subject: [PATCH 21/27] changed to use mathics.git master --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 598b96b..691cab6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: - pip install ipykernel - pip install jupyter_kernel_test - pip install unittest2 - - pip install git+https://github.com/mathics/mathics.git@imathics + - pip install git+https://github.com/mathics/mathics.git install: - python setup.py install script: From 01bba4741e77942718829db5be159020cc72d081 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Tue, 13 Sep 2016 14:22:20 +0200 Subject: [PATCH 22/27] made import of WebEngine optional --- imathics/kernel.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index de811d1..50f2ce0 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -17,10 +17,12 @@ from mathics import settings from mathics.version import __version__ from mathics.doc.doc import Doc -from mathics.layout.client import WebEngine -import os -import base64 +try: + from mathics.layout.client import WebEngine + web_engine_available = True +except ImportError: + web_engine_available = False def parse_lines(lines, definitions): @@ -126,7 +128,7 @@ def establish_comm_manager(self): self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) def init_web_engine(self): - if self.web_engine is None: + if self.web_engine is None and web_engine_available: self.web_engine = WebEngine() def do_execute(self, code, silent, store_history=True, user_expressions=None, From 1acdfc42af2b245e78546847e575bfff883a1b8e Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Tue, 13 Sep 2016 14:48:52 +0200 Subject: [PATCH 23/27] expect layoutengine branch of mathics --- .travis.yml | 2 +- imathics/kernel.py | 13 ++++--------- setup.py | 8 +++++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 691cab6..875f51a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: - pip install ipykernel - pip install jupyter_kernel_test - pip install unittest2 - - pip install git+https://github.com/mathics/mathics.git + - pip install git+https://github.com/poke1024/mathics.git@layoutengine install: - python setup.py install script: diff --git a/imathics/kernel.py b/imathics/kernel.py index 50f2ce0..18f8714 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -10,19 +10,14 @@ from mathics.core.evaluation import Evaluation, Message, Result, Output, Print from mathics.core.expression import Integer from mathics.core.parser import IncompleteSyntaxError, TranslateError, ScanError -from mathics.core.parser.util import parse +from mathics.core.parser import parse, TranslateError from mathics.core.parser.feed import SingleLineFeeder from mathics.core.parser.tokeniser import Tokeniser from mathics.builtin import builtins from mathics import settings from mathics.version import __version__ from mathics.doc.doc import Doc - -try: - from mathics.layout.client import WebEngine - web_engine_available = True -except ImportError: - web_engine_available = False +from mathics.layout.client import WebEngine def parse_lines(lines, definitions): @@ -50,7 +45,7 @@ def parse_lines(lines, definitions): incomplete_exc = IncompleteSyntaxError(len(query)-1) continue try: - expression = parse(SingleLineFeeder(lines), definitions) + expression = parse(definitions, SingleLineFeeder(query)) except IncompleteSyntaxError as exc: incomplete_exc = exc else: @@ -128,7 +123,7 @@ def establish_comm_manager(self): self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) def init_web_engine(self): - if self.web_engine is None and web_engine_available: + if self.web_engine is None: self.web_engine = WebEngine() def do_execute(self, code, silent, store_history=True, user_expressions=None, diff --git a/setup.py b/setup.py index 76ef311..343f31b 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,13 @@ # General Requirements SETUP_REQUIRES = ['ipython', 'ipykernel'] -INSTALL_REQUIRES = ['mathics>=1.0.dev0'] + SETUP_REQUIRES +# as long as imathics as mathics are in experimental development, it's a good idea to +# expect the user to manually install the lastest version of mathics, instead of pulling +# some old version here that breaks everything then. + +#INSTALL_REQUIRES = ['mathics>=1.0.dev0'] + SETUP_REQUIRES + +INSTALL_REQUIRES = SETUP_REQUIRES kernel_json = { From d6eb4a172d9b2eaa5a8776713b12c6128784e75f Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Fri, 23 Sep 2016 18:34:14 +0200 Subject: [PATCH 24/27] setting max_stored_size to None to avoid slow pickle size checks --- imathics/kernel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/imathics/kernel.py b/imathics/kernel.py index 18f8714..e6fb93e 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -66,6 +66,9 @@ def __init__(self, kernel): super(KernelOutput, self).__init__(kernel.web_engine) self.kernel = kernel + def max_stored_size(self, settings): + return None + def out(self, out): self.kernel.out_callback(out) From 6cdae22c5916e175f1ba3d2b0d33118be8bfe708 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Sun, 25 Sep 2016 22:25:36 +0200 Subject: [PATCH 25/27] now defaults to Asana-Math font, which looks nicer --- imathics/kernel.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/imathics/kernel.py b/imathics/kernel.py index e6fb93e..6b1321e 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -200,6 +200,11 @@ def reconfigure_mathjax(self): allow: { fontsize: "all" } + }, + "HTML-CSS": { + availableFonts: [], /* force Web fonts */ + preferredFont: null, /* force Web fonts */ + webFont: "Asana-Math" } }); From 7f778b6294092c4039bfc105568600c667fa9669 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Mon, 26 Sep 2016 00:28:23 +0200 Subject: [PATCH 26/27] better MathJax rendering --- imathics/kernel.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 6b1321e..88ed995 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -193,6 +193,10 @@ def reconfigure_mathjax(self): safeModeJS = """ MathJax.Hub.Config({ + showMathMenu: false, + showProcessingMessages: false, + messageStyle: "normal", + displayAlign: "left", Safe: { safeProtocols: { data: true @@ -202,9 +206,13 @@ def reconfigure_mathjax(self): } }, "HTML-CSS": { - availableFonts: [], /* force Web fonts */ - preferredFont: null, /* force Web fonts */ - webFont: "Asana-Math" + availableFonts: [], /* force Web font */ + preferredFont: null, /* force Web font */ + webFont: "Asana-Math", + linebreaks: { + automatic: true, + width: "70%" + } } }); From 794928d9bfb201a20c9c1865da7bd4eaaccda166 Mon Sep 17 00:00:00 2001 From: Bernhard Liebl Date: Wed, 26 Oct 2016 12:01:24 +0200 Subject: [PATCH 27/27] added output of traceback --- imathics/kernel.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/imathics/kernel.py b/imathics/kernel.py index 88ed995..6ce328c 100755 --- a/imathics/kernel.py +++ b/imathics/kernel.py @@ -63,7 +63,10 @@ def parse_lines(lines, definitions): class KernelOutput(Output): def __init__(self, kernel): - super(KernelOutput, self).__init__(kernel.web_engine) + if getattr(Output, 'version', None) is None: + super(KernelOutput, self).__init__() + else: + super(KernelOutput, self).__init__(kernel.web_engine) self.kernel = kernel def max_stored_size(self, settings): @@ -154,12 +157,14 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, if result: self.result_callback(result) except Exception as exc: - self.out_callback(Print('An error occured: ' + str(exc))) + stack = traceback.format_exception(*sys.exc_info()) + + self.out_callback(Print('An error occured: ' + str(exc) + '\n\n' + '\n'.join(stack))) # internal error response['status'] = 'error' response['ename'] = 'System:exception' - response['traceback'] = traceback.format_exception(*sys.exc_info()) + response['traceback'] = stack else: response['status'] = 'ok'