From d87b8b98d99bbb705d7694c3966c987a98aa23b2 Mon Sep 17 00:00:00 2001 From: Andrew Gibiansky Date: Sun, 18 May 2014 16:44:33 -0700 Subject: [PATCH] fixing issues with linting ordering --- ihaskell-display/ihaskell-parsec/widget.html | 1 - ihaskell-display/ihaskell-parsec/widget.js | 210 ++++++++++++++++++- notebooks/Test.ipynb | 138 ++++++++++-- 3 files changed, 324 insertions(+), 25 deletions(-) diff --git a/ihaskell-display/ihaskell-parsec/widget.html b/ihaskell-display/ihaskell-parsec/widget.html index 718371d7..2036c0c4 100644 --- a/ihaskell-display/ihaskell-parsec/widget.html +++ b/ihaskell-display/ihaskell-parsec/widget.html @@ -1,6 +1,5 @@ -
diff --git a/ihaskell-display/ihaskell-parsec/widget.js b/ihaskell-display/ihaskell-parsec/widget.js index 2cca17b1..15f07dd2 100644 --- a/ihaskell-display/ihaskell-parsec/widget.js +++ b/ihaskell-display/ihaskell-parsec/widget.js @@ -7,6 +7,212 @@ if (initialized && window.parsecWidgetRegistered === undefined) { // Do not load this script again. window.parsecWidgetRegistered = true; +// Codemirror lint.js +// Must be included here, otherwise linting cannot happen the first time the widget is loaded. +(function() { + "use strict"; + var GUTTER_ID = "CodeMirror-lint-markers"; + var SEVERITIES = /^(?:error|warning)$/; + + function showTooltip(e, content) { + var tt = document.createElement("div"); + tt.className = "CodeMirror-lint-tooltip"; + tt.appendChild(content.cloneNode(true)); + document.body.appendChild(tt); + + function position(e) { + if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position); + tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px"; + tt.style.left = (e.clientX + 5) + "px"; + } + CodeMirror.on(document, "mousemove", position); + position(e); + if (tt.style.opacity != null) tt.style.opacity = 1; + return tt; + } + function rm(elt) { + if (elt.parentNode) elt.parentNode.removeChild(elt); + } + function hideTooltip(tt) { + if (!tt.parentNode) return; + if (tt.style.opacity == null) rm(tt); + tt.style.opacity = 0; + setTimeout(function() { rm(tt); }, 600); + } + + function showTooltipFor(e, content, node) { + var tooltip = showTooltip(e, content); + function hide() { + CodeMirror.off(node, "mouseout", hide); + if (tooltip) { hideTooltip(tooltip); tooltip = null; } + } + var poll = setInterval(function() { + if (tooltip) for (var n = node;; n = n.parentNode) { + if (n == document.body) return; + if (!n) { hide(); break; } + } + if (!tooltip) return clearInterval(poll); + }, 400); + CodeMirror.on(node, "mouseout", hide); + } + + function LintState(cm, options, hasGutter) { + this.marked = []; + this.options = options; + this.timeout = null; + this.hasGutter = hasGutter; + this.onMouseOver = function(e) { onMouseOver(cm, e); }; + } + + function parseOptions(cm, options) { + if (options instanceof Function) return {getAnnotations: options}; + if (!options || options === true) options = {}; + if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint"); + if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)"); + return options; + } + + function clearMarks(cm) { + var state = cm.state.lint; + if (state.hasGutter) cm.clearGutter(GUTTER_ID); + for (var i = 0; i < state.marked.length; ++i) + state.marked[i].clear(); + state.marked.length = 0; + } + + function makeMarker(labels, severity, multiple, tooltips) { + var marker = document.createElement("div"), inner = marker; + marker.className = "CodeMirror-lint-marker-" + severity; + if (multiple) { + inner = marker.appendChild(document.createElement("div")); + inner.className = "CodeMirror-lint-marker-multiple"; + } + + if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) { + showTooltipFor(e, labels, inner); + }); + + return marker; + } + + function getMaxSeverity(a, b) { + if (a == "error") return a; + else return b; + } + + function groupByLine(annotations) { + var lines = []; + for (var i = 0; i < annotations.length; ++i) { + var ann = annotations[i], line = ann.from.line; + (lines[line] || (lines[line] = [])).push(ann); + } + return lines; + } + + function annotationTooltip(ann) { + var severity = ann.severity; + if (!SEVERITIES.test(severity)) severity = "error"; + var tip = document.createElement("div"); + tip.className = "CodeMirror-lint-message-" + severity; + tip.appendChild(document.createTextNode(ann.message)); + return tip; + } + + function startLinting(cm) { + var state = cm.state.lint, options = state.options; + if (options.async) + options.getAnnotations(cm, updateLinting, options); + else + updateLinting(cm, options.getAnnotations(cm.getValue(), options.options)); + } + + function updateLinting(cm, annotationsNotSorted) { + clearMarks(cm); + var state = cm.state.lint, options = state.options; + + var annotations = groupByLine(annotationsNotSorted); + + for (var line = 0; line < annotations.length; ++line) { + var anns = annotations[line]; + if (!anns) continue; + + var maxSeverity = null; + var tipLabel = state.hasGutter && document.createDocumentFragment(); + + for (var i = 0; i < anns.length; ++i) { + var ann = anns[i]; + var severity = ann.severity; + if (!SEVERITIES.test(severity)) severity = "error"; + maxSeverity = getMaxSeverity(maxSeverity, severity); + + if (options.formatAnnotation) ann = options.formatAnnotation(ann); + if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann)); + + if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, { + className: "CodeMirror-lint-mark-" + severity, + __annotation: ann + })); + } + + if (state.hasGutter) + cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1, + state.options.tooltips)); + } + if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm); + } + + function onChange(cm) { + var state = cm.state.lint; + clearTimeout(state.timeout); + state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500); + } + + function popupSpanTooltip(ann, e) { + var target = e.target || e.srcElement; + showTooltipFor(e, annotationTooltip(ann), target); + } + + // When the mouseover fires, the cursor might not actually be over + // the character itself yet. These pairs of x,y offsets are used to + // probe a few nearby points when no suitable marked range is found. + var nearby = [0, 0, 0, 5, 0, -5, 5, 0, -5, 0]; + + function onMouseOver(cm, e) { + if (!/\bCodeMirror-lint-mark-/.test((e.target || e.srcElement).className)) return; + for (var i = 0; i < nearby.length; i += 2) { + var spans = cm.findMarksAt(cm.coordsChar({left: e.clientX + nearby[i], + top: e.clientY + nearby[i + 1]})); + for (var j = 0; j < spans.length; ++j) { + var span = spans[j], ann = span.__annotation; + if (ann) return popupSpanTooltip(ann, e); + } + } + } + + function optionHandler(cm, val, old) { + if (old && old != CodeMirror.Init) { + clearMarks(cm); + cm.off("change", onChange); + CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver); + delete cm.state.lint; + } + + if (val) { + var gutters = cm.getOption("gutters"), hasLintGutter = false; + for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true; + var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter); + cm.on("change", onChange); + if (state.options.tooltips != false) + CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); + + startLinting(cm); + } + } + + CodeMirror.defineOption("lintWith", false, optionHandler); // deprecated + CodeMirror.defineOption("lint", false, optionHandler); // deprecated +})(); + var parsecWidgetCounter = 0; // Register the comm target. @@ -66,8 +272,8 @@ var ParsecWidget = function (comm) { ParsecWidget.prototype.handler = function(msg) { var data = msg.content.data; - this.hasError = data["status"] == "error"; - console.log('handler', msg); + this.hasError = (data["status"] == "error"); + console.log(this.hasError); if (this.hasError) { this.output.innerHTML = data["msg"]; this.error = data; diff --git a/notebooks/Test.ipynb b/notebooks/Test.ipynb index 099eee8d..c2150c15 100644 --- a/notebooks/Test.ipynb +++ b/notebooks/Test.ipynb @@ -42,9 +42,7 @@ " blah |]" ], "language": "python", - "metadata": { - "hidden": false - }, + "metadata": {}, "outputs": [ { "html": [ @@ -73,11 +71,11 @@ "metadata": {}, "output_type": "display_data", "text": [ - "/Users/silver" + "/Users" ] } ], - "prompt_number": 3 + "prompt_number": 1 }, { "cell_type": "code", @@ -116,33 +114,129 @@ " return (read (value ++ \".\" ++ after) :: Float)\n", " \n", " -- Parse any whitespace\n", - " whitespace = many $ oneOf \" \\t\"\n", - " \n", + " whitespace = many $ oneOf \" \\t\"" + ], + "language": "python", + "metadata": { + "hidden": false + }, + "outputs": [], + "prompt_number": 1 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ "parser" ], "language": "python", "metadata": {}, "outputs": [ { - "html": [ - "
Unshowable:Parser ListNo instance for (Show (Parser List)) arising from a use of `print'
Possible fix: add an instance declaration for (Show (Parser List))
In a stmt of an interactive GHCi command: print it
" + "javascript": [ + "// Only load this script once.\n", + "var kernel = IPython.notebook.kernel;\n", + "var initialized = kernel !== undefined && kernel != null;\n", + "console.log(\"Initialized\", initialized);\n", + "if (initialized && window.parsecWidgetRegistered === undefined) {\n", + "\n", + "// Do not load this script again.\n", + "window.parsecWidgetRegistered = true;\n", + "\n", + "var parsecWidgetCounter = 0;\n", + "\n", + "// Register the comm target.\n", + "var ParsecWidget = function (comm) {\n", + " // Load linting.\n", + " require([\"/static/components/codemirror/addon/lint/lint.js\"]);\n", + "\n", + " this.comm = comm;\n", + " this.comm.on_msg($.proxy(this.handler, this));\n", + "\n", + " // Get the cell that was probably executed.\n", + " // The msg_id:cell mapping will make this possible without guessing.\n", + " this.cell = IPython.notebook.get_cell(IPython.notebook.get_selected_index()-1);\n", + "\n", + " // Store this widget so we can use it from callbacks.\n", + " var widget = this;\n", + "\n", + " // Editor options.\n", + " var options = {\n", + " lineNumbers: true,\n", + " // Show parsec errors as lint errors.\n", + " gutters: [\"CodeMirror-lint-markers\"],\n", + " lintWith: {\n", + " \"getAnnotations\": function(cm, update, opts) {\n", + " var errs = [];\n", + " if (widget.hasError) {\n", + " var col = widget.error[\"col\"];\n", + " var line = widget.error[\"line\"];\n", + " errs = [{\n", + " from: CodeMirror.Pos(line - 1, col - 1),\n", + " to: CodeMirror.Pos(line - 1, col),\n", + " message: widget.error[\"msg\"],\n", + " severity: \"error\"\n", + " }];\n", + " }\n", + " update(cm, errs);\n", + " },\n", + " \"async\": true,\n", + " }\n", + " };\n", + "\n", + " // Create the editor.\n", + " var out = this.cell.output_area.element;\n", + " this.textarea = out.find(\"#parsec-editor\")[0];\n", + " this.output = out.find(\"#parsec-output\")[0];\n", + " // Give the elements a different name.\n", + " this.textarea.id += parsecWidgetCounter;\n", + " this.output.id += parsecWidgetCounter;\n", + " parsecWidgetCounter++;\n", + "\n", + " var editor = CodeMirror.fromTextArea(this.textarea, options);\n", + " var editor = editor;\n", + "\n", + " // Update every key press.\n", + " editor.on(\"keyup\", function() {\n", + " var text = editor.getDoc().getValue();\n", + " comm.send({\"text\": text});\n", + " });\n", + "};\n", + "\n", + "ParsecWidget.prototype.handler = function(msg) {\n", + " var data = msg.content.data;\n", + " this.hasError = (data[\"status\"] == \"error\");\n", + " console.log(this.hasError);\n", + " if (this.hasError) {\n", + " this.output.innerHTML = data[\"msg\"];\n", + " this.error = data;\n", + " } else {\n", + " this.output.innerHTML = data[\"result\"];\n", + " }\n", + "};\n", + "\n", + "// Register this widget.\n", + "IPython.notebook.kernel.comm_manager.register_target('parsec', IPython.utils.always_new(ParsecWidget));\n", + "console.log(\"Registering Parsec widget.\");\n", + "}\n" ], "metadata": {}, - "output_type": "display_data", - "text": [ - "No instance for (Show (Parser List)) arising from a use of `print'\n", - "Possible fix: add an instance declaration for (Show (Parser List))\n", - "In a stmt of an interactive GHCi command: print it" - ] + "output_type": "display_data" + }, + { + "html": [ + "\n", + "\n", + "\n", + "\n", + "
\n", + "
\n"
+       ],
+       "metadata": {},
+       "output_type": "display_data"
       }
      ],
-     "prompt_number": 1
+     "prompt_number": 2
     },
     {
      "cell_type": "code",