Properly parse compiler errors for fiddles and mark them in the editor.

You can also click on the errors in the parsed output to jump directly to the offending location in the editor.

BUG=skia:
NOTRY=true

Review URL: https://codereview.chromium.org/660573003
This commit is contained in:
humper 2014-10-16 09:18:16 -07:00 committed by Commit bot
parent 420d7e9a79
commit ae8ab62966
5 changed files with 133 additions and 26 deletions

View File

@ -24,7 +24,7 @@
var embedButton = document.getElementById('embedButton');
var code = document.getElementById('code');
var output = document.getElementById('output');
var stdout = document.getElementById('stdout');
var outputWrapper = document.getElementById('output-wrapper');
var gpu = document.getElementById('use-gpu');
var img = document.getElementById('img');
var imageWidth = document.getElementById('image-width');
@ -163,6 +163,14 @@
editor.setSize(editor.defaultCharWidth() * code.cols,
null);
editor.on('beforeChange', function(instance, changeObj) {
var startLine = changeObj.from.line;
var endLine = changeObj.to.line;
for (var i = startLine ; i <= endLine ; i++) {
editor.removeLineClass( i, "wrap", "error" );
}
});
/**
* Callback when there's an XHR error.
@ -175,11 +183,7 @@
function clearOutput() {
output.textContent = "";
output.style.display='none';
if (stdout) {
stdout.textContent = "";
stdout.style.display='none';
}
outputWrapper.style.display='none';
if (embed) {
embed.style.display='none';
}
@ -236,6 +240,19 @@
tryHistory.querySelector('.tries').addEventListener('click', historyClick, true);
}
/**
* Callback for when the user clicks on a compile error message
*
*/
function errorClick() {
var line = this.getAttribute('data-line');
var col = this.getAttribute('data-col');
editor.setCursor(line-1,col-1);
editor.focus();
}
/**
* Callback for when the XHR returns after attempting to run the code.
@ -252,14 +269,39 @@
// image to display.
endWait();
body = JSON.parse(e.target.response);
output.textContent = body.message;
if (body.message) {
output.style.display = 'block';
}
if (stdout) {
stdout.textContent = body.stdout;
if (body.stdout) {
stdout.style.display = 'block';
if (body.compileErrors.length) {
html = "";
for (i = 0 ; i < body.compileErrors.length ; i++) {
compileError = body.compileErrors[i];
err = document.createElement("div");
err.className = "compile-error";
loc = document.createElement("span");
loc.className = "error-location";
loc.innerHTML = "Line " + compileError.line + ", col " + compileError.column + ": ";
errorMessage = document.createElement("span");
errorMessage.className = "error-mesage";
errorMessage.innerHTML = compileError.error;
err.appendChild(loc);
err.appendChild(errorMessage);
err.setAttribute('data-line', compileError.line);
err.setAttribute('data-col', compileError.column);
output.appendChild(err);
err.addEventListener('click', errorClick);
editor.addLineClass( parseInt( compileError.line ) - 1, "wrap", "error" );
}
outputWrapper.style.display = 'block';
} else {
output.textContent = body.message;
if (body.message) {
outputWrapper.style.display = 'block';
}
}
if (body.hasOwnProperty('img')) {

File diff suppressed because one or more lines are too long

View File

@ -135,12 +135,20 @@ code {
font-size: 70%;
}
#output, #stdout {
#output-wrapper {
display: none;
}
.compile-error {
cursor: pointer;
}
/* CodeMirror customization */
.CodeMirror .error {
background: #f88;
}
.CodeMirror-lines,
.CodeMirror-scroll
{

View File

@ -56,9 +56,9 @@
</p>
<p id='zoomHex'></p>
<h2>Warnings and Errors</h2>
<pre id="output"></pre>
<h2>Runtime output</h2>
<pre id="stdout"></pre>
<div id="output-wrapper">
<h2>Build Issues (click errors to jump to that line)</h2>
<pre id="output"></pre>
</div>
</section>

View File

@ -22,6 +22,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"text/template"
"time"
@ -86,6 +87,9 @@ var (
// workspaceLink is the regex that matches URLs paths for workspaces.
workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
// errorRE is ther regex that matches compiler errors and extracts the line / column information.
errorRE = regexp.MustCompile("^.*.cpp:(\\d+):(\\d+):\\s*(.*)")
// workspaceNameAdj is a list of adjectives for building workspace names.
workspaceNameAdj = []string{
"autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
@ -405,10 +409,10 @@ func expandGyp(hash string) error {
// response is serialized to JSON as a response to POSTs.
type response struct {
Message string `json:"message"`
StdOut string `json:"stdout"`
Img string `json:"img"`
Hash string `json:"hash"`
Message string `json:"message"`
CompileErrors []compileError `json:"compileErrors"`
Img string `json:"img"`
Hash string `json:"hash"`
}
// doCmd executes the given command line string; the command being
@ -447,6 +451,23 @@ func reportTryError(w http.ResponseWriter, r *http.Request, err error, message,
}
glog.Errorf("%s\n%s", message, err)
resp, err := json.Marshal(m)
if err != nil {
http.Error(w, "Failed to serialize a response", 500)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Write(resp)
}
func reportCompileError(w http.ResponseWriter, r *http.Request, compileErrors []compileError, hash string) {
m := response{
CompileErrors: compileErrors,
Hash: hash,
}
resp, err := json.Marshal(m)
if err != nil {
http.Error(w, "Failed to serialize a response", 500)
return
@ -794,6 +815,12 @@ func cleanCompileOutput(s, hash string) string {
return strings.Replace(s, old, "usercode.cpp:", -1)
}
type compileError struct {
Line int `json:"line"`
Column int `json:"column"`
Error string `json:"error"`
}
// mainHandler handles the GET and POST of the main page.
func mainHandler(w http.ResponseWriter, r *http.Request) {
glog.Infof("Main Handler: %q\n", r.URL.Path)
@ -869,10 +896,40 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
}
message, err := doCmd(cmd)
outputLines := strings.Split(message, "\n")
errorLines := []compileError{}
for _, line := range outputLines {
match := errorRE.FindStringSubmatch(line)
if len(match) > 0 {
lineNumber, parseError := strconv.Atoi(match[1])
if parseError != nil {
glog.Errorf("ERROR: Couldn't parse line number from %s\n", match[1])
continue
}
columnNumber, parseError := strconv.Atoi(match[2])
if parseError != nil {
glog.Errorf("ERROR: Couldn't parse column number from %s\n", match[2])
continue
}
errorLines = append(errorLines,
compileError{
Line: lineNumber,
Column: columnNumber,
Error: match[3],
})
}
}
if err != nil {
reportTryError(w, r, err, "Failed to run the code:\n"+message, hash)
if len(errorLines) > 0 {
reportCompileError(w, r, errorLines, hash)
} else {
reportTryError(w, r, err, "Failed to run the code:\n"+message, hash)
}
return
}
png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
if err != nil {
reportTryError(w, r, err, "Failed to open the generated PNG.", hash)