From 0fcb3d0ae68e0919666ccc2a7c9ccbfd9cb4ba5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Sat, 8 Mar 2014 18:15:25 -0300 Subject: [PATCH 1/3] Simplifies the Blick Comment code and fixes the issues with line comments as prefix of bock comments and equal block comments delimiters --- src/editor/EditorCommandHandlers.js | 352 +++++++++++++----------- src/language/languages.json | 8 +- test/spec/EditorCommandHandlers-test.js | 188 ++++++++++++- test/spec/LanguageManager-test.js | 4 +- 4 files changed, 383 insertions(+), 169 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 1be80143d7b..afab03e73bd 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -46,16 +46,50 @@ define(function (require, exports, module) { var DIRECTION_DOWN = +1; + /** + * @private + * Creates special regular expressions that matches the line prefix but not the block prefix or suffix + * @param {!string} lineSyntax a line comment prefix + * @param {!string} blockSyntax a block comment prefix or suffix + * @return {RegExp} + */ + function _createSpecialLineExp(lineSyntax, blockSyntax) { + var i, character, + subExps = [], + prevChars = ""; + + for (i = lineSyntax.length; i < blockSyntax.length; i++) { + character = blockSyntax.charAt(i); + subExps.push(prevChars + "[^" + character + "]"); + if (prevChars) { + subExps.push(prevChars + "$"); + } + prevChars += character; + } + return new RegExp("^\\s*" + lineSyntax + "($|" + subExps.join("|") + ")"); + } + /** * @private * Creates regular expressions for multiple line comment prefixes - * @param {!Array.} prefixes - the line comment prefixes + * @param {!Array.} prefixes the line comment prefixes + * @param {string=} blockPrefix the block comment prefix + * @param {string=} blockSuffix the block comment suffix * @return {Array.} */ - function _createLineExpressions(prefixes) { - var lineExp = []; + function _createLineExpressions(prefixes, blockPrefix, blockSuffix) { + var lineExp = [], escapedPrefix, regExp; + prefixes.forEach(function (prefix) { - lineExp.push(new RegExp("^\\s*" + StringUtils.regexEscape(prefix))); + escapedPrefix = StringUtils.regexEscape(prefix); + if (blockPrefix && blockPrefix.indexOf(prefix) === 0) { + regExp = _createSpecialLineExp(prefix, blockPrefix); + } else if (blockSuffix && blockSuffix.indexOf(prefix) === 0) { + regExp = _createSpecialLineExp(prefix, blockSuffix); + } else { + regExp = new RegExp("^\\s*" + escapedPrefix); + } + lineExp.push(regExp); }); return lineExp; } @@ -76,11 +110,11 @@ define(function (require, exports, module) { /** * @private * Returns the line comment prefix that best matches the string. Since there might be line comment prefixes - * that are prefixes of other line comment prefixes, it searches throught all and returns the longest line + * that are prefixes of other line comment prefixes, it searches through all and returns the longest line * comment prefix that matches the string. - * @param {!string} string - where to look - * @param {!Array.} expressions - the line comment regular expressions - * @param {!Array.} prefixes - the line comment prefixes + * @param {!string} string where to look + * @param {!Array.} expressions the line comment regular expressions + * @param {!Array.} prefixes the line comment prefixes * @return {string} */ function _getLinePrefix(string, expressions, prefixes) { @@ -97,25 +131,24 @@ define(function (require, exports, module) { * @private * Searchs for an uncommented line between startLine and endLine * @param {!Editor} editor - * @param {!number} startLine - valid line inside the document - * @param {!number} endLine - valid line inside the document - * @param {!Array.} lineExp - an array of line comment prefixes regular expressions + * @param {!number} startLine valid line inside the document + * @param {!number} endLine valid line inside the document + * @param {!Array.} lineExp an array of line comment prefixes regular expressions * @return {boolean} true if there is at least one uncommented line */ - function _containsUncommented(editor, startLine, endLine, lineExp) { - var containsUncommented = false; - var i; - var line; + function _containsNotLineComment(editor, startLine, endLine, lineExp) { + var i, line, + containsNotLineComment = false; for (i = startLine; i <= endLine; i++) { line = editor.document.getLine(i); // A line is commented out if it starts with 0-N whitespace chars, then a line comment prefix if (line.match(/\S/) && !_matchExpressions(line, lineExp)) { - containsUncommented = true; + containsNotLineComment = true; break; } } - return containsUncommented; + return containsNotLineComment; } /** @@ -128,13 +161,15 @@ define(function (require, exports, module) { * * @param {!Editor} editor * @param {!Array.} prefixes, e.g. ["//"] + * @param {string=} blockPrefix, e.g. "" */ - function lineCommentPrefix(editor, prefixes) { + function lineCommentPrefix(editor, prefixes, blockPrefix, blockSuffix) { var doc = editor.document, sel = editor.getSelection(), startLine = sel.start.line, endLine = sel.end.line, - lineExp = _createLineExpressions(prefixes); + lineExp = _createLineExpressions(prefixes, blockPrefix, blockSuffix); // Is a range of text selected? (vs just an insertion pt) var hasSelection = (startLine !== endLine) || (sel.start.ch !== sel.end.ch); @@ -147,17 +182,13 @@ define(function (require, exports, module) { // Decide if we're commenting vs. un-commenting // Are there any non-blank lines that aren't commented out? (We ignore blank lines because // some editors like Sublime don't comment them out) - var containsUncommented = _containsUncommented(editor, startLine, endLine, lineExp); - var i; - var line; - var prefix; - var commentI; - var updateSelection = false; + var i, line, prefix, commentI, + containsNotLineComment = _containsNotLineComment(editor, startLine, endLine, lineExp), + updateSelection = false; // Make the edit doc.batchOperation(function () { - - if (containsUncommented) { + if (containsNotLineComment) { // Comment out - prepend the first prefix to each line for (i = startLine; i <= endLine; i++) { doc.replaceRange(prefixes[0], {line: i, ch: 0}); @@ -193,59 +224,46 @@ define(function (require, exports, module) { /** * @private - * Moves the token context to the token that starts the block-comment. Ctx starts in a block-comment. - * Returns the position of the prefix or null if gets to the start of the document and didn't found it. - * @param {!{editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}}} ctx - token context - * @param {!RegExp} prefixExp - a valid regular expression - * @return {?{line: number, ch: number}} + * Given a token context it will search backwards to determine if the given token is part of a block comment + * that doesn't start at the initial token. This is used to know if a line comment is part of a block comment + * or if a block delimiter is the prefix or suffix, by passing a token context at that position. Since the + * token context will be moved backwards a lot, it is better to pass a new context. + * + * @param {!{editor:{CodeMirror}, pos:{ch:{number}, line:{number}}, token:{object}}} ctx token context + * @param {!string} prefix the block comment prefix + * @param {!string} suffix the block comment suffix + * @param {!RegExp} prefixExp a block comment prefix regular expression + * @param {!RegExp} suffixExp a block comment suffix regular expression + * @param {!Array.} lineExp an array of line comment prefixes regular expressions + * @return {boolean} */ - function _findCommentStart(ctx, prefixExp) { - var result = true; + function _isPrevTokenABlockComment(ctx, prefix, suffix, prefixExp, suffixExp, lineExp) { + // Start searching from the previous token + var result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); - while (result && !ctx.token.string.match(prefixExp)) { + // Look backwards until we find a none line comment token + while (result && _matchExpressions(ctx.token.string, lineExp)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } - return result ? {line: ctx.pos.line, ch: ctx.token.start} : null; - } - - /** - * @private - * Moves the token context to the token that ends the block-comment. Ctx starts in a block-comment. - * Returns the position of the sufix or null if gets to the end of the document and didn't found it. - * @param {!{editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}}} ctx - token context - * @param {!RegExp} suffixExp - a valid regular expression - * @param {!number} suffixLen - length of the suffix - * @return {?{line: number, ch: number}} - */ - function _findCommentEnd(ctx, suffixExp, suffixLen) { - var result = true; - while (result && !ctx.token.string.match(suffixExp)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - } - return result ? {line: ctx.pos.line, ch: ctx.token.end - suffixLen} : null; - } - - /** - * @private - * Moves the token context to the next block-comment if there is one before end. - * @param {!{editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}}} ctx - token context - * @param {!{line: number, ch: number}} end - where to stop searching - * @param {!RegExp} prefixExp - a valid regular expression - * @return {boolean} - true if it found a block-comment - */ - function _findNextBlockComment(ctx, end, prefixExp) { - var index = ctx.editor.indexFromPos(end), - inside = ctx.editor.indexFromPos(ctx.pos) <= index, - result = true; - - while (result && inside && !ctx.token.string.match(prefixExp)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - inside = ctx.editor.indexFromPos(ctx.pos) <= index; + // If we are now in a block comment token + if (result && ctx.token.className === "comment") { + // If it doesnt matches either prefix or suffix, we know is a block comment + if (!ctx.token.string.match(prefixExp) && !ctx.token.string.match(suffixExp)) { + return true; + // We found a line with just a block comment delimiter, but we can't tell which one it is, so we will + // keep searching recursively and return the opposite value + } else if (prefix === suffix && ctx.token.string.length === prefix.length) { + return !_isPrevTokenABlockComment(ctx, prefix, suffix, prefixExp, suffixExp, lineExp); + // We can just now the result by checking if the string matches the prefix + } else { + return ctx.token.string.match(prefixExp); + } } - return result && inside && !!ctx.token.string.match(prefixExp); + return false; } + /** * Add or remove block-comment tokens to the selection, preserving selection * and cursor position. Applies to the currently focused Editor. @@ -255,9 +273,8 @@ define(function (require, exports, module) { * Commenting out adds the prefix before the selection and the suffix after. * Uncommenting removes them. * - * If slashComment is true and the start or end of the selection is inside a line-comment it - * will try to do a line uncomment if is not actually inside a bigger block comment and all - * the lines in the selection are line-commented. + * If a list of line comment prefixes is provided and all the lines inside the selection are line commented, + * it will try to do a line uncomment if is not actually inside a bigger block comment. * * @param {!Editor} editor * @param {!string} prefix, e.g. "