Brian Gough 10 лет назад
Родитель
Сommit
fe866e54fc

+ 83 - 14
services/web/public/js/ace-1.2.5/mode-latex.js

@@ -205,35 +205,88 @@ var LatexFoldMode = require("./folding/latex").FoldMode;
 var Range = require("../range").Range;
 var WorkerClient = require("ace/worker/worker_client").WorkerClient;
 
-   
+
 var Mode = function() {
     this.HighlightRules = LatexHighlightRules;
     this.foldingRules = new LatexFoldMode();
     this.createWorker = function(session) {
-	var worker = new WorkerClient(["ace"], "ace/mode/latex_worker", "LatexWorker");
+	var doc = session.getDocument();
+	var selection = session.getSelection();
+
 	var savedRange = {};
+	var suppressions = [];
+	var hints = [];
+	var changeHandler = null;
 
-	worker.attachToDocument(session.getDocument());
+	var worker = new WorkerClient(["ace"], "ace/mode/latex_worker", "LatexWorker");
+	worker.attachToDocument(doc);
 
-	worker.on("lint", function(results) {
+	doc.on("change", function () {
+	    if(changeHandler) {
+		clearTimeout(changeHandler);
+		changeHandler = null;
+	    }
+	});
+
+	selection.on("changeCursor", function () {
+	    if(suppressions.length > 0) {
+		changeHandler = setTimeout(function () {
+		    updateMarkers();
+		    suppressions = [];
+		    changeHandler = null;
+		}, 100);
+	    }
+	});
+
+	var updateMarkers = function () {
+	    var annotations = [];
 	    var newRange = {};
-	    for (var i = 0; i<results.data.length; i++) {
-		var start_row = results.data[i].start_row;
-		var end_row = results.data[i].end_row;
-		var key = start_row + ":" + end_row;
-		newRange[key] = results.data[i];
+	    var cursor = selection.getCursor();
+	    suppressions = [];
+
+	    for (var i = 0; i<hints.length; i++) {
+		var data = hints[i];
+		var start_row = data.start_row;
+		var start_col = data.start_col;
+		var end_row = data.end_row;
+		var end_col = data.end_col;
+		if (data.suppressIfEditing &&
+		    ((cursor.row === start_row && cursor.column == start_col+1)
+		     || (cursor.row === end_row && (cursor.column+1) == end_col))) {
+		    suppressions.push([start_row, start_col, end_row, end_col]);
+		    continue;
+		}
+		var suppress = false;
+		for (var j = 0; j < suppressions.length; j++) {
+		    var e=suppressions[j];
+		    var fromRow=e[0], fromCol=e[1], toRow=e[2], toCol=e[3];
+		    if (start_row == fromRow && start_col >= fromCol && start_row === toRow  && start_col <= toCol) {
+			suppress = true;
+			break;
+		    }
+		}
+		if(suppress) { continue; };
+
+		var key = "(" + start_row + "," + start_col + ")" + ":" + "(" + end_row + "," + end_col + ")";
+		newRange[key] = data;
+		annotations.push(data);
 	    }
 
 	    var newKeys = Object.keys(newRange);
 	    var oldKeys = Object.keys(savedRange);
-	    
+	    var changes = 0;
 	    for (i = 0; i < newKeys.length; i++) {
 		key = newKeys[i];
 		if (!savedRange[key]) {
 		    var new_range = newRange[key];
-		    var range = new Range(new_range.start_row, 0, new_range.end_row, Infinity);
-		    range.id = session.addMarker(range, "ace_error-marker", "fullLine");
+		    var a = doc.createAnchor(new_range.start_row, new_range.start_col);
+		    var b = doc.createAnchor(new_range.end_row, new_range.end_col);
+		    var range = new Range();
+		    range.start = a;
+		    range.end = b;
+		    range.id = session.addMarker(range, "ace_error-marker", "text");
 		    savedRange[key] = range;
+		    changes++;
 		}
 	    }
 
@@ -241,11 +294,27 @@ var Mode = function() {
 		key = oldKeys[i];
 		if (!newRange[key]) {
 		    range = savedRange[key];
+		    range.start.detach();
+		    range.end.detach();
 		    session.removeMarker(range.id);
 		    delete savedRange[key];
+		    changes++;
 		}
 	    }
+
+	    if (changes>0) {
+		session.setAnnotations(annotations);
+	    };
+	};
+
+	worker.on("lint", function(results) {
+	    hints = results.data;
+	    if (hints.length > 100) {
+		hints = hints.slice(0, 100); // limit to 100 errors
+	    };
+	    updateMarkers();
 	});
+
 	worker.on("terminate", function() {
 	    var oldKeys = Object.keys(savedRange);
 	    for (var i = 0; i < oldKeys.length; i++) {
@@ -254,7 +323,7 @@ var Mode = function() {
 		session.removeMarker(range.id);
 		delete savedRange[key];
 	    }
-	    
+
 	});
 	return worker;
     };
@@ -263,7 +332,7 @@ oop.inherits(Mode, TextMode);
 
 (function() {
     this.type = "text";
-    
+
     this.lineCommentStart = "%";
 
     this.$id = "ace/mode/latex";

+ 388 - 43
services/web/public/js/ace-1.2.5/worker-latex.js

@@ -1414,67 +1414,412 @@ var Mirror = require("../worker/mirror").Mirror;
 
 var LatexWorker = exports.LatexWorker = function(sender) {
     Mirror.call(this, sender);
-    this.setTimeout(500);
+    this.setTimeout(250);
 };
 
 oop.inherits(LatexWorker, Mirror);
 
 var Parse = function (text) {
-    var lines = text.split('\n');
-    var state = [], errors = [];
-    var i;
-    var regex = /^\s*\\(begin|end)\{(\w+\*?)\}/; // keep the regex outside the loop for performance
-    for (i = 0; i < lines.length; i++) {
-	var line = lines[i];
-	var result;
-	var error = null;
-	regex.lastIndex = 0;
-	if ((result = regex.exec(line))) {
-	    var type = result[1];
-	    var env = result[2];
-	    if (type == "begin") {
-		state.push({"env":env, "row": i});
-	    } else if (type == "end") {
-		var last_open = state.pop();
-		if (last_open &&  last_open.env == env) {
-		} else if (last_open && last_open.env != env) {
-		    error = {"type":"info", "text": "end " + env + "with begin " + last_open.env, "start_row": last_open.row, "end_row":i };
-		} else if (!last_open) {
-		    error = {"type":"info", "text": "end without begin " + type, "start_row": 0, "end_row": i};
+    var errors = [];
+    var Comments = [];
+    var Tokens = [];
+    var Environments = [];
+    var pos = -1;
+    var SPECIAL = /[\\\{\}\$\&\#\^\_\~\%]/g;
+    var CS = /[^a-zA-Z]/g;
+    var idx = 0;
+    var lineNumber = 0;
+    var linePosition = [];
+    linePosition[0] = 0;
+
+    var TokenError = function (token, message) {
+	var line = token[0], type = token[1], start = token[2], end = token[3], seq = token[4];
+	var start_col = start - linePosition[line];
+	var end_col = end - linePosition[line] + 1;
+	errors.push({row: line,
+		     column: start_col,
+		     start_row:line,
+		     start_col: start_col,
+		     end_row:line,
+		     end_col: end_col,
+		     type:"error",
+		     text:message,
+		     suppressIfEditing:true});
+    };
+
+    var TokenErrorFromTo = function (fromToken, toToken, message) {
+	var fromLine = fromToken[0], fromStart = fromToken[2], fromEnd = fromToken[3];
+	var toLine = toToken[0], toStart = toToken[2], toEnd = toToken[3];
+	if (!toEnd) { toEnd = toStart + 1;};
+	var start_col = fromStart - linePosition[fromLine];
+	var end_col = toEnd - linePosition[toLine] + 1;
+
+	errors.push({row: line,
+		     column: start_col,
+		     start_row: fromLine,
+		     start_col: start_col,
+		     end_row: toLine,
+		     end_col: end_col,
+		     type:"error",
+		     text:message,
+		     suppressIfEditing:true});
+    };
+
+
+    var EnvErrorFromTo = function (fromEnv, toEnv, message, options) {
+	if(!options) { options = {} ; };
+	var fromToken = fromEnv.token, toToken = toEnv.closeToken || toEnv.token;
+	var fromLine = fromToken[0], fromStart = fromToken[2], fromEnd = fromToken[3], fromSeq = fromToken[4];
+	if (!toToken) {toToken = fromToken;};
+	var toLine = toToken[0], toStart = toToken[2], toEnd = toToken[3], toSeq = toToken[4];
+	if (!toEnd) { toEnd = toStart + 1;};
+	var start_col = fromStart - linePosition[fromLine];
+	var end_col = toEnd - linePosition[toLine] + 1;
+	errors.push({row:toLine,
+		     column:end_col,
+		     start_row:fromLine,
+		     start_col: start_col,
+		     end_row:toLine,
+		     end_col: end_col,
+		     type:"error",
+		     text:message,
+		     suppressIfEditing:options.suppressIfEditing});
+    };
+
+    var EnvErrorTo = function (toEnv, message) {
+	var token = toEnv.closeToken || toEnv.token;
+	var line = token[0], type = token[1], start = token[2], end = token[3], seq = token[4];
+	if (!end) { end = start + 1; };
+	var end_col = end - linePosition[line] + 1;
+	var err = {row: line,
+		   column: end_col,
+		   start_row:0,
+		   start_col: 0,
+		   end_row: line,
+		   end_col: end_col,
+		   type:"error",
+		   text:message};
+	errors.push(err);
+    };
+
+    var EnvErrorFrom = function (env, message) {
+	var token = env.token;
+	var line = token[0], type = token[1], start = token[2], end = token[3], seq = token[4];
+	var start_col = start - linePosition[line];
+	var end_col = Infinity;
+	errors.push({row: line,
+		     column: start_col,
+		     start_row:line,
+		     start_col: start_col,
+		     end_row: lineNumber,
+		     end_col: end_col,
+		     type:"error",
+		     text:message});
+    };
+
+    var checkingDisabled = false;
+
+    while (true) {
+	var result = SPECIAL.exec(text);
+	if (result == null) {
+	    if (idx < text.length) {
+		Tokens.push([lineNumber, "Text", idx, text.length]);
+	    }
+	    break;
+	}
+	if (result && result.index <= pos) {
+	    break;
+	};
+	pos = result.index;
+	var newIdx = SPECIAL.lastIndex;
+	if (pos > idx) {
+	    Tokens.push([lineNumber, "Text", idx, pos]);
+	}
+	for (var i = idx; i < pos; i++) {
+	    if (text[i] === "\n") {
+		lineNumber++;
+		linePosition[lineNumber] = i+1;
+	    }
+	}
+	idx = newIdx;
+	var code = result[0];
+	if (code === "%") {
+	    var newLinePos = text.indexOf("\n", idx);
+	    if (newLinePos === -1) {
+		newLinePos = text.length;
+	    };
+	    var commentString = text.substring(idx, newLinePos);
+	    if (commentString.indexOf("%novalidate") === 0) {
+		return [];
+	    } else if(!checkingDisabled && commentString.indexOf("%begin novalidate") === 0) {
+		checkingDisabled = true;
+	    } else if (checkingDisabled && commentString.indexOf("%end novalidate") === 0) {
+		checkingDisabled = false;
+	    };
+	    idx = SPECIAL.lastIndex = newLinePos + 1;
+	    Comments.push([lineNumber, idx, newLinePos]);
+	    lineNumber++;
+	    linePosition[lineNumber] = idx;
+	} else if (checkingDisabled) {
+	    continue;
+	} else if (code === '\\') {
+	    CS.lastIndex = idx;
+	    var controlSequence = CS.exec(text);
+	    var nextSpecialPos = controlSequence === null ? idx : controlSequence.index;
+	    if (nextSpecialPos === idx) {
+		Tokens.push([lineNumber, code, pos, idx + 1, text[idx]]);
+		idx = SPECIAL.lastIndex = idx + 1;
+		char = text[nextSpecialPos];
+		if (char === '\n') { lineNumber++; linePosition[lineNumber] = nextSpecialPos;};
+	    } else {
+		Tokens.push([lineNumber, code, pos, nextSpecialPos, text.slice(idx, nextSpecialPos)]);
+		var char;
+		while ((char = text[nextSpecialPos]) === ' ' || char === '\t' || char  === '\r' || char === '\n') {
+		    nextSpecialPos++;
+		    if (char === '\n') { lineNumber++; linePosition[lineNumber] = nextSpecialPos;};
 		}
+		idx = SPECIAL.lastIndex = nextSpecialPos;
+	    }
+	} else if (code === "{") {
+	    Tokens.push([lineNumber, code, pos]);
+	} else if (code === "}") {
+	    Tokens.push([lineNumber, code, pos]);
+	} else if (code === "$") {
+	    if (text[idx] === "$") {
+		idx = SPECIAL.lastIndex = idx + 1;
+		Tokens.push([lineNumber, "$$", pos]);
+	    } else {
+		Tokens.push([lineNumber, code, pos]);
+	    }
+	} else if (code === "&") {
+	    Tokens.push([lineNumber, code, pos]);
+	} else if (code === "#") {
+	    Tokens.push([lineNumber, code, pos]);
+	} else if (code === "^") {
+	    Tokens.push([lineNumber, code, pos]);
+	} else if (code === "_") {
+	    Tokens.push([lineNumber, code, pos]);
+	} else if (code === "~") {
+	    Tokens.push([lineNumber, code, pos]);
+	} else {
+	    throw "unrecognised character " + code;
+	}
+    }
+
+    var read1arg = function (k) {
+	var open = Tokens[k+1];
+	var env = Tokens[k+2];
+	var close = Tokens[k+3];
+	var envName;
+
+	if(open && open[1] === "\\") {
+	    envName = open[4];
+	    return k + 1;
+	} else if(open && open[1] === "{" && env && env[1] === "\\" && close && close[1] === "}") {
+	    envName = env[4];
+	    return k + 3;
+	} else {
+	    return null;
+	}
+    };
+
+    var read1name = function (k) {
+	var open = Tokens[k+1];
+	var env = Tokens[k+2];
+	var close = Tokens[k+3];
+
+	if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") {
+	    var envName = text.substring(env[2], env[3]);
+	    return k + 3;
+	} else {
+	    return null;
+	}
+    };
+
+
+
+    var readOptionalParams = function(k) {
+	var params = Tokens[k+1];
+
+	if(params && params[1] === "Text") {
+	    var paramNum = text.substring(params[2], params[3]);
+	    if (paramNum.match(/^\[\d+\]$/)) {
+		return k + 1;
 	    };
 	};
-	if (error) {
-	    errors.push(error);
+	return null;
+    };
+
+    var readDefinition = function(k) {
+	k = k + 1;
+	var count = 0;
+	var nextToken = Tokens[k];
+	while (nextToken && nextToken[1] === "Text") {
+	    var start = nextToken[2], end = nextToken[3];
+	    for (i = start; i < end; i++) {
+		var char = text[i];
+		if (char === ' ' || char === '\t' || char  === '\r' || char === '\n') { continue; }
+		return null;
+	    }
+	    k++;
+	    nextToken = Tokens[k];
+	}
+	if (nextToken && nextToken[1] === "{") {
+	    count++;
+	    while (count>0) {
+		k++;
+		nextToken = Tokens[k];
+		if(!nextToken) { break; };
+		if (nextToken[1] === "}") { count--; }
+		if (nextToken[1] === "{") { count++; }
+	    }
+	    return k;
+	}
+	return null;
+    };
+
+    for (var _j = 0, _len = Tokens.length; _j < _len; _j++) {
+	var token = Tokens[_j];
+	var line = token[0], type = token[1], start = token[2], end = token[3], seq = token[4];
+	if (type === "\\") {
+	    if (seq === "begin" || seq === "end") {
+		var open = Tokens[_j+1];
+		var env = Tokens[_j+2];
+		var close = Tokens[_j+3];
+		if(open && open[1] === "{" && env && env[1] === "Text" && close && close[1] === "}") {
+		    var envName = text.substring(env[2], env[3]);
+		    Environments.push({command: seq, name: envName, token: token, closeToken: close});
+		    _j = _j + 3; // advance past these tokens
+		} else {
+		    var endToken = null;
+		    if (open && open[1] === "{") {
+			endToken = open;
+
+			if (env && env[1] === "Text") {
+			    endToken = env.slice();
+			    start = endToken[2]; end = endToken[3];
+			    for (i = start; i < end; i++) {
+				char = text[i];
+				if (char === ' ' || char === '\t' || char  === '\r' || char === '\n') { break; }
+			    }
+			    endToken[3] = i;
+			};
+		    };
+
+		    if (endToken) {
+			TokenErrorFromTo(token, endToken, "invalid environment command" + text.substring(token[2], endToken[3] || endToken[2]));
+		    } else {
+			TokenError(token, "invalid environment command");
+		    };
+		}
+	    } else if (seq === "newcommand" || seq === "renewcommand" || seq === "def" || seq === "DeclareRobustCommand") {
+
+		var newPos = read1arg(_j);
+		if (newPos === null) { continue; } else {_j = newPos;};
+
+		newPos = readOptionalParams(_j);
+		if (newPos === null) { /* do nothing */ } else {_j = newPos;};
+
+		newPos = readDefinition(_j);
+		if (newPos === null) { /* do nothing */ } else {_j = newPos;};
+
+	    } else if (seq === "newenvironment") {
+
+		newPos = read1name(_j);
+		if (newPos === null) { continue; } else {_j = newPos;};
+
+		newPos = readOptionalParams(_j);
+		if (newPos === null) { /* do nothing */ } else {_j = newPos;};
+
+		newPos = readDefinition(_j);
+		if (newPos === null) { /* do nothing */ } else {_j = newPos;};
+
+		newPos = readDefinition(_j);
+		if (newPos === null) { /* do nothing */ } else {_j = newPos;};
+	    }
+	} else if (type === "{") {
+	    Environments.push({command:"{", token:token});
+	} else if (type === "}") {
+	    Environments.push({command:"}", token:token});
 	};
-    };
-    if (state.length > 0) {
-	for (i = 0; i < state.length; i++) {
-	    var unclosed = state[i];
-	    error = {"type":"info", "text": "begin without end " + unclosed.type, "start_row": unclosed.row, "end_row": lines.length-1};
-	    errors.push(error);
+
+	if ((start != null) && (end != null) && (seq != null)) {
+	} else if ((start != null) && (end != null)) {
+	} else if (start != null) {
+	} else {
+	}
+    }
+    var state = [];
+    for (i = 0; i < Environments.length; i++) {
+	var thisEnv = Environments[i];
+	if(thisEnv.command === "begin" || thisEnv.command === "{") {
+	    state.push(thisEnv);
+	} else if (thisEnv.command === "end" || thisEnv.command === "}") {
+	    var lastEnv = state.pop();
+	    if (!lastEnv) {
+		if (thisEnv.command === "}") {
+		    EnvErrorTo(thisEnv, "unexpected end group }");
+		} else if (thisEnv.command === "end") {
+		    EnvErrorTo(thisEnv, "unexpected \\end{" + thisEnv.name + "}");
+		}
+	    } else if (lastEnv.command === "{" && thisEnv.command === "}") {
+		continue; // closed group correctly
+	    } else if (lastEnv.name === thisEnv.name) {
+		continue; // closed environment correctly
+	    } else if (thisEnv.command === "}") {
+		EnvErrorFromTo(lastEnv, thisEnv, "unexpected end group } after \\begin{" + lastEnv.name +"}");
+		state.push(lastEnv);
+	    } else if (lastEnv.command === "{" && thisEnv.command === "end") {
+		EnvErrorFromTo(lastEnv, thisEnv, "unexpected \\end{" + thisEnv.name + "} inside group {", {suppressIfEditing:true});
+		i--;
+	    } else if (lastEnv.command === "begin" && thisEnv.command === "end") {
+		EnvErrorFromTo(lastEnv, thisEnv, "unexpected \\end{" + thisEnv.name + "} after \\begin{" + lastEnv.name + "}");
+		for (var j = i + 1; j < Environments.length; j++) {
+		    var futureEnv = Environments[j];
+		    if (futureEnv.command === "end" && futureEnv.name === lastEnv.name) {
+			state.push(lastEnv);
+			continue;
+		    }
+		}
+		lastEnv = state.pop();
+		if(lastEnv) {
+		    if (thisEnv.name === lastEnv.name) {
+			continue;
+		    } else {
+			state.push(lastEnv);
+		    }
+		}
+
+	    }
+	}
+    }
+    while (state.length > 0) {
+	thisEnv = state.pop();
+	if (thisEnv.command === "{") {
+	    EnvErrorFrom(thisEnv, "unclosed group {");
+	} else if (thisEnv.command === "begin") {
+	    EnvErrorFrom(thisEnv, "unclosed environment \\begin{" + thisEnv.name + "}");
 	};
-    };
-    if (errors.length) {
-	return [errors[0]];
-    }; 
+    }
     return errors;
 };
 
-    
 (function() {
+    var disabled = false;
 
     this.onUpdate = function() {
-        var value = this.doc.getValue();
-        var errors = [];
+	if (disabled) { return ; };
 
+	var value = this.doc.getValue();
+	var errors = [];
 	try {
-            if (value)
-                errors = Parse(value);
-        } catch (e) {
-	    console.log("latex worker error",e);
-        }
-        this.sender.emit("lint", errors);
+	    if (value)
+		errors = Parse(value);
+	} catch (e) {
+	    disabled = true;
+	    errors = [];
+	}
+	this.sender.emit("lint", errors);
     };
 
 }).call(LatexWorker.prototype);