fixing issues with linting ordering

This commit is contained in:
Andrew Gibiansky 2014-05-18 16:44:33 -07:00
parent 5ed129a079
commit d87b8b98d9
3 changed files with 324 additions and 25 deletions

View File

@ -1,6 +1,5 @@
<!-- CodeMirror component -->
<link rel="stylesheet" href="/static/components/codemirror/addon/lint/lint.css">
<script src="/static/components/codemirror/addon/lint/lint.js" charset="utf-8"></script>
<!-- Parsec widget DOM -->
<form><textarea id="parsec-editor">Insert parser text here...</textarea></form>

View File

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

View File

@ -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": [
"<div class='collapse-group'><span class='btn' href='#' id='unshowable'>Unshowable:<span class='show-type'>Parser List</span></span><span class='err-msg collapse'>No instance for (Show (Parser List)) arising from a use of `print'<br/>Possible fix: add an instance declaration for (Show (Parser List))<br/>In a stmt of an interactive GHCi command: print it</span></div><script>$('#unshowable').on('click', function(e) {\n",
" e.preventDefault();\n",
" var $this = $(this);\n",
" var $collapse = $this.closest('.collapse-group').find('.err-msg');\n",
" $collapse.collapse('toggle');\n",
"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",
"</script>"
"};\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": [
"<!-- CodeMirror component -->\n",
"<link rel=\"stylesheet\" href=\"/static/components/codemirror/addon/lint/lint.css\">\n",
"\n",
"<!-- Parsec widget DOM -->\n",
"<form><textarea id=\"parsec-editor\">Insert parser text here...</textarea></form>\n",
"<pre id=\"parsec-output\"></pre>\n"
],
"metadata": {},
"output_type": "display_data"
}
],
"prompt_number": 1
"prompt_number": 2
},
{
"cell_type": "code",