From 2d6039c0b06acf7b41660b265ac7b808f96ed6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Wed, 6 Mar 2013 23:10:45 -0300 Subject: [PATCH 01/10] Fixing issues when open and close delimiters are the same --- src/editor/EditorCommandHandlers.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 5f181c9c7c1..fa56fad160a 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -159,15 +159,24 @@ define(function (require, exports, module) { * @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 + * @param {?{line: number, ch: number}} prefixPos - the recently found prefix position * @return {?{line: number, ch: number}} */ - function _findCommentEnd(ctx, suffixExp, suffixLen) { + function _findCommentEnd(ctx, suffixExp, suffixLen, prefixPos) { 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; + var pos = {line: ctx.pos.line, ch: ctx.token.end - suffixLen}; + + // If the position found is the same as the prefix one, start again from the next token + if (result && prefixPos && prefixPos.line === pos.line && prefixPos.ch === pos.ch) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + return result ? _findCommentEnd(ctx, suffixExp, suffixLen) : null; + } else { + return result ? pos : null; + } } /** @@ -233,7 +242,8 @@ define(function (require, exports, module) { } // Check if we should just do a line uncomment (if all lines in the selection are commented). - if (lineExp && (ctx.token.string.match(lineExp) || endCtx.token.string.match(lineExp))) { + if (lineExp && (ctx.token.string.match(lineExp) || endCtx.token.string.match(lineExp)) && + !ctx.token.string.match(prefixExp)) { var startCtxIndex = editor.indexFromPos({line: ctx.pos.line, ch: ctx.token.start}); var endCtxIndex = editor.indexFromPos({line: endCtx.pos.line, ch: endCtx.token.start + endCtx.token.string.length}); @@ -264,7 +274,7 @@ define(function (require, exports, module) { } } else { prefixPos = _findCommentStart(startCtx, prefixExp); - suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length, prefixPos); } // If we are in a selection starting and ending in invalid tokens and with no content (not considering spaces), @@ -276,9 +286,9 @@ define(function (require, exports, module) { // We found a comment, find the start and end and check if the selection is inside the block-comment. if (startCtx.token.className === "comment") { prefixPos = _findCommentStart(startCtx, prefixExp); - suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length, prefixPos); - if (prefixPos !== null && suffix !== null && !editor.posWithinRange(sel.start, prefixPos, suffixPos)) { + if (prefixPos !== null && suffixPos !== null && !editor.posWithinRange(sel.start, prefixPos, suffixPos)) { canComment = true; } } else { @@ -288,7 +298,7 @@ define(function (require, exports, module) { // If the start is inside a comment, find the prefix and suffix positions. } else if (ctx.token.className === "comment") { prefixPos = _findCommentStart(ctx, prefixExp); - suffixPos = _findCommentEnd(ctx, suffixExp, suffix.length); + suffixPos = _findCommentEnd(ctx, suffixExp, suffix.length, prefixPos); // If not try to find the first comment inside the selection. } else { @@ -303,7 +313,7 @@ define(function (require, exports, module) { } else { prefixPos = {line: ctx.pos.line, ch: ctx.token.start}; } - suffixPos = _findCommentEnd(ctx, suffixExp, suffix.length); + suffixPos = _findCommentEnd(ctx, suffixExp, suffix.length, prefixPos); } } From 29d68d085097dcf1852cc72a2b7725c61f39f726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Tue, 19 Mar 2013 05:47:57 -0300 Subject: [PATCH 02/10] Improving the Block and Line comment fix --- src/editor/EditorCommandHandlers.js | 154 ++++++++++++++++++---------- 1 file changed, 101 insertions(+), 53 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 6eaf5b35949..c68b9e1c975 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -60,6 +60,23 @@ define(function (require, exports, module) { return lineExp; } + /** + * @private + * Creates regular expressions for the block comment prefix and suffix + * @param {!string} prefixes - the block comment prefix + * @param {!string} prefixes - the block comment suffix + * @return {Array.} + */ + function _createBlockExpressions(prefix, suffix) { + if (prefix) { + return [ + new RegExp("^" + StringUtils.regexEscape(prefix), "g"), + new RegExp(StringUtils.regexEscape(suffix) + "$", "g") + ]; + } + return []; + } + /** * @private * Returns true if any regular expression matches the given string @@ -99,10 +116,11 @@ define(function (require, exports, module) { * @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 {!Array.} lineExp - an array of line comment prefixes regular expressions + * @param {!Array.} blockExp - a prefix and a suffix block comment regular expressions * @return {boolean} true if there is at least one uncommented line */ - function _containsUncommented(editor, startLine, endLine, lineExp) { + function _containsUncommented(editor, startLine, endLine, lineExp, blockExp) { var containsUncommented = false; var i; var line; @@ -110,7 +128,7 @@ define(function (require, exports, module) { 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)) { + if ((line.match(/\S/) && !_matchExpressions(line, lineExp)) || _matchExpressions(line.trim(), blockExp)) { containsUncommented = true; break; } @@ -128,13 +146,16 @@ 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), + blockExp = _createBlockExpressions(blockPrefix, blockSuffix); // Is a range of text selected? (vs just an insertion pt) var hasSelection = (startLine !== endLine) || (sel.start.ch !== sel.end.ch); @@ -147,7 +168,7 @@ 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 containsUncommented = _containsUncommented(editor, startLine, endLine, lineExp, blockExp); var i; var line; var prefix; @@ -191,19 +212,34 @@ define(function (require, exports, module) { } + /** + * @private + * Returns true if the token is a Block Comment + * @param {!{editor:{CodeMirror}, pos:{ch:{number}, line:{number}}, token:{object}}} ctx - token context + * @param {!Array.} blockExp - a prefix and a suffix block comment regular expressions + * @param {!Array.} lineExp - an array of line comment prefixes regular expressions + * @return {boolean} + */ + function _isBlockComment(ctx, blockExp, lineExp) { + return ctx.token.className === "comment" && + (!blockExp.length || _matchExpressions(ctx.token.string, blockExp) || + !_matchExpressions(ctx.token.string, lineExp)); + } + /** * @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 {!{editor:{CodeMirror}, pos:{ch:{number}, line:{number}}, token:{object}}} ctx - token context * @param {!RegExp} prefixExp - a valid regular expression * @return {?{line: number, ch: number}} */ - function _findCommentStart(ctx, prefixExp) { - var result = true; + function _findCommentStart(ctx, blockExp, lineExp) { + var result = _isBlockComment(ctx, blockExp, lineExp); - while (result && !ctx.token.string.match(prefixExp)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); + while (result && !ctx.token.string.match(blockExp[0])) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx) && + _isBlockComment(ctx, blockExp, lineExp); } return result ? {line: ctx.pos.line, ch: ctx.token.start} : null; } @@ -212,33 +248,25 @@ define(function (require, exports, module) { * @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 {!{editor:{CodeMirror}, pos:{ch:{number}, line:{number}}, token:{object}}} ctx - token context * @param {!RegExp} suffixExp - a valid regular expression * @param {!number} suffixLen - length of the suffix - * @param {?{line: number, ch: number}} prefixPos - the recently found prefix position * @return {?{line: number, ch: number}} */ - function _findCommentEnd(ctx, suffixExp, suffixLen, prefixPos) { - var result = true; - - while (result && !ctx.token.string.match(suffixExp)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - } - var pos = {line: ctx.pos.line, ch: ctx.token.end - suffixLen}; + function _findCommentEnd(ctx, blockExp, suffixLen, lineExp) { + var result = _isBlockComment(ctx, blockExp, lineExp); - // If the position found is the same as the prefix one, start again from the next token - if (result && prefixPos && prefixPos.line === pos.line && prefixPos.ch === pos.ch) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - return result ? _findCommentEnd(ctx, suffixExp, suffixLen) : null; - } else { - return result ? pos : null; + while (result && !ctx.token.string.match(blockExp[1])) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && + _isBlockComment(ctx, blockExp, lineExp); } + 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 {!{editor:{CodeMirror}, pos:{ch:{number}, 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 @@ -264,9 +292,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 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. "" + * @param {string=} blockPrefix, e.g. "" */ function lineCommentPrefix(editor, prefixes, blockPrefix, blockSuffix) { var doc = editor.document, @@ -226,56 +229,42 @@ 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 start of the prefix or null if gets to the start of the document and didn't found it. - * @param {!{editor:{CodeMirror}, pos:{ch:{number}, line:{number}}, token:{object}}} ctx - token context - * @param {!RegExp} blockExp - a block comment prefix regular expression - * @return {?{line: number, ch: number}} - */ - function _findCommentPrefix(ctx, prefixExp) { - var result = true; - - while (result && !ctx.token.string.match(prefixExp)) { - 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 start of the suffix or null if it gets to the end of the document and didn't found it. + * 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 {!number} suffixLen - length of the suffix - * @return {?{line: number, ch: number}} + * @param {!Array.} lineExp - an array of line comment prefixes regular expressions + * @return {boolean} */ - function _findCommentSuffix(ctx, suffixExp, suffixLen) { - 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(suffixExp)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + // 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.end - suffixLen} : null; - } - - /** - * @private - * Moves the token context to the next block-comment if there is one before the end. - * @param {!{editor:{CodeMirror}, pos:{ch:{number}, 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), - result = ctx.editor.indexFromPos(ctx.pos) <= index; - while (result && !ctx.token.string.match(prefixExp)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && - 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 && !!ctx.token.string.match(prefixExp); + return false; } /** @@ -296,140 +285,130 @@ define(function (require, exports, module) { * @param {!Array.} linePrefixes, e.g. ["//"] */ function blockCommentPrefixSuffix(editor, prefix, suffix, linePrefixes) { - var doc = editor.document, sel = editor.getSelection(), ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.start.ch}), - startCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.start.ch}), - endCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.end.line, ch: sel.end.ch}), + line = doc.getLine(sel.start.line), + selEndIndex = editor.indexFromPos(sel.end), lineExp = _createLineExpressions(linePrefixes, prefix, suffix), prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), prefixPos = null, suffixPos = null, + commentAtStart = true, + isBlockComment = false, canComment = false, invalidComment = false, - lineUncomment = false, - newSelection; + lineUncomment = false; + + var searchCtx, atSuffix, newSelection, result = true; - var result, text, line; + // If the selected text is just white-spaces, lets just move to the first none white-space token + if (line.trim() === "") { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - /** - * Intenal function that looks for the prefix and suffix position - */ - function findPrefixSuffix() { - prefixPos = _findCommentPrefix(ctx, prefixExp); - suffixPos = _findCommentSuffix(ctx, suffixExp, suffix.length); + // Find the first comment inside the selection (if we start in a comment, it will not enter the loop and just continue) + } else { + // First move the context to the first none white-space token + if (!ctx.token.className && ctx.token.string.trim().length === 0) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + } - // There are some languages like CoffeeScript where the block comment prefix and suffix are the same, so - // when the prefix or suffix is alone in one line, both prefixPos and suffixPos might be the same. - // In those cases we need to find the real prefix or suffix - if (prefixPos && suffixPos && prefixPos.line === suffixPos.line && prefixPos.ch === suffixPos.ch) { - // Lets create a new Token context to look for the prefix so that ctx stays at the suffix position - var newCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: prefixPos.line, ch: prefixPos.ch}); - var isLineCmt = true; - result = true; - - // Look backwards for a line that is a comment and not a line comment - while (result && isLineCmt) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, newCtx); - isLineCmt = newCtx.token.className === "comment" && _matchExpressions(newCtx.token.string, lineExp); + // Next, loop util we find a comment inside the selection + while (result && ctx.token.className !== "comment") { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && + editor.indexFromPos(ctx.pos) <= selEndIndex; + commentAtStart = false; + } + } + + // We are now in a comment, lets check if it is a block or a line comment + if (result && ctx.token.className === "comment") { + line = doc.getLine(ctx.pos.line); + + // This is a line comment, lets check if the whole line is a line comment or just part + if (_matchExpressions(ctx.token.string, lineExp)) { + if (_matchExpressions(line, lineExp)) { + // If we found this comment at the start of the selection, we need to search backwards until we get a result + if (commentAtStart) { + searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); + isBlockComment = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); + // If not, we already know that is a line comment + } else { + isBlockComment = false; + } + // If it was not a whole line comment, then we can just comment + } else { + canComment = true; } + // If it was not a line comment, it has to be a block comment + } else { + isBlockComment = true; - // The previous token is a block comment, so we found the suffix, we need to find the prefix - if (result && newCtx.token.className === "comment" && !newCtx.token.string.match(prefixExp)) { - newCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: prefixPos.line, ch: prefixPos.ch}); - TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, newCtx); - prefixPos = _findCommentPrefix(newCtx, prefixExp); - - // We found the prefix, we move to the next token and try to find the suffix - } else { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - suffixPos = result && _findCommentSuffix(ctx, suffixExp, suffix.length); + // If we are in a line that only has a prefix or suffix and the prefix and suffix are the same string, + // Lets find first if this is a prefix or suffix and move the token to the inside of the block comment + if (ctx.token.string.match(prefixExp) && prefix === suffix && ctx.token.string.length === prefix.length) { + searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); + atSuffix = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); + if (atSuffix) { + TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); + } else { + TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + } } } + } else { + canComment = true; } - - // Move the context to the first non-empty token. - if (!ctx.token.className && ctx.token.string.trim().length === 0) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - } - - // Check if we should just do a line uncomment (if all lines in the selection are commented). - if (lineExp.length && (_matchExpressions(ctx.token.string, lineExp) || _matchExpressions(endCtx.token.string, lineExp))) { - // Try to find first a prefix and suffix - findPrefixSuffix(); - - // If we found a prefix, check if the block comment found is partially inside the selection - if (!prefixPos || !suffixPos || (!editor.posWithinRange(sel.start, prefixPos, suffixPos) && - !editor.posWithinRange(sel.end, prefixPos, suffixPos))) { - // Is a range of text selected? (vs just an insertion pt) - var hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch); + if (!canComment) { + if (isBlockComment) { + // Save the initial position to start searching for the suffix from here + var initialPos = $.extend({}, ctx.pos); - // In full-line selection, cursor pos is start of next line - but don't want to modify that line - var endLine = sel.end.line; - if (sel.end.ch === 0 && hasSelection) { - endLine--; + // Find the position of the start of the prefix + result = true; + while (result && !ctx.token.string.match(prefixExp)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } + prefixPos = result && {line: ctx.pos.line, ch: ctx.token.start}; - // Find if all the lines are line-commented. - if (!_containsNotLineComment(editor, sel.start.line, endLine, lineExp)) { - lineUncomment = true; + // Restore the context at the initial position and find the position of the start of the suffix + ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: initialPos.line, ch: initialPos.ch}); - // Block-comment in all the other cases - } else { - canComment = true; + while (result && !ctx.token.string.match(suffixExp)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); } - } - - // If we are in a selection starting and ending in invalid tokens and with no content (not considering spaces), - // find if we are inside a block-comment. - } else if (startCtx.token.className === null && endCtx.token.className === null && - !editor.posWithinRange(ctx.pos, startCtx.pos, endCtx.pos)) { - - // We found a comment, find the start and end and check if the selection is inside the block-comment. - if (ctx.token.className === "comment") { - findPrefixSuffix(); + suffixPos = result && {line: ctx.pos.line, ch: ctx.token.end - suffix.length}; + + // Lets check that there are not more comments in the selection, we do nothing if there is one + do { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && + editor.indexFromPos(ctx.pos) <= selEndIndex; + } while (result && !ctx.token.string.match(prefixExp)); + invalidComment = result && !!ctx.token.string.match(prefixExp); - if (prefixPos !== null && !editor.posWithinRange(sel.start, prefixPos, suffixPos)) { + // We moved the token at the start when it was in a whitespace, but maybe we shouldn't have done it + if (prefixPos && editor.indexFromPos(sel.end) < editor.indexFromPos(prefixPos)) { canComment = true; } - } else { - canComment = true; - } - - // If the start is inside a comment, find the prefix and suffix positions. - } else if (ctx.token.className === "comment") { - findPrefixSuffix(); - - // If not try to find the first comment inside the selection. - } else { - result = _findNextBlockComment(ctx, sel.end, prefixExp); - // If nothing was found is ok to comment. - if (!result) { - canComment = true; } else { - findPrefixSuffix(); - } - } - - // Search if there is another comment in the selection. Do nothing if there is one. - if (!canComment && !invalidComment && !lineUncomment && suffixPos) { - var start = {line: suffixPos.line, ch: suffixPos.ch + suffix.length + 1}; - if (editor.posWithinRange(start, sel.start, sel.end)) { - // Start searching at the next token, if there is one. - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && - _findNextBlockComment(ctx, sel.end, prefixExp); + // In full-line selection, cursor pos is start of next line - but don't want to modify that line + var endLine = sel.end.line; + if (sel.end.ch === 0 && editor.hasSelection()) { + endLine--; + } - if (result) { - invalidComment = true; + // Find if all the lines are line-commented. + if (!_containsNotLineComment(editor, sel.start.line, endLine, lineExp)) { + lineUncomment = true; + } else { + canComment = true; } } } - // Make the edit if (invalidComment) { return; @@ -519,6 +498,7 @@ define(function (require, exports, module) { var sel = editor.getSelection(), selStart = sel.start, selEnd = sel.end, + selEndIndex = editor.indexFromPos(sel.end), prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), isLineSelection = sel.start.ch === 0 && sel.end.ch === 0 && sel.start.line !== sel.end.line, isMultipleLine = sel.start.line !== sel.end.line, @@ -542,10 +522,17 @@ define(function (require, exports, module) { var ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: selStart.line, ch: selStart.ch}); var result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); var className = ctx.token.className; - result = result && _findNextBlockComment(ctx, selEnd, prefixExp); + + // Search for the first block comment inside the selection + while (result && !ctx.token.string.match(prefixExp)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && + editor.indexFromPos(ctx.pos) <= selEndIndex; + } + result = result && !!ctx.token.string.match(prefixExp); if (className === "comment" || result || isLineSelection) { blockCommentPrefixSuffix(editor, prefix, suffix, []); + } else { // Set the new selection and comment it editor.setSelection(selStart, selEnd); From 8c11ca76738f5226c6a453f4cd0c6b5dc63a0d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Mon, 1 Apr 2013 04:12:33 -0300 Subject: [PATCH 09/10] Simplify the first step --- src/editor/EditorCommandHandlers.js | 30 ++++++++++++----------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index af411d4981f..631cfaa2dfd 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -303,33 +303,27 @@ define(function (require, exports, module) { var searchCtx, atSuffix, newSelection, result = true; - // If the selected text is just white-spaces, lets just move to the first none white-space token - if (line.trim() === "") { + // First move the context to the first none white-space token + if (!ctx.token.className && ctx.token.string.trim().length === 0) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + } - // Find the first comment inside the selection (if we start in a comment, it will not enter the loop and just continue) - } else { - // First move the context to the first none white-space token - if (!ctx.token.className && ctx.token.string.trim().length === 0) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - } - - // Next, loop util we find a comment inside the selection - while (result && ctx.token.className !== "comment") { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && - editor.indexFromPos(ctx.pos) <= selEndIndex; - commentAtStart = false; - } + // Next, loop util we find a comment inside the selection + while (result && ctx.token.className !== "comment") { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && + editor.indexFromPos(ctx.pos) <= selEndIndex; + commentAtStart = false; } // We are now in a comment, lets check if it is a block or a line comment if (result && ctx.token.className === "comment") { line = doc.getLine(ctx.pos.line); - // This is a line comment, lets check if the whole line is a line comment or just part if (_matchExpressions(ctx.token.string, lineExp)) { + // This is a line comment, lets check if the whole line is a line comment too if (_matchExpressions(line, lineExp)) { - // If we found this comment at the start of the selection, we need to search backwards until we get a result + // If we found this comment at the start of the selection, we need to search backwards until we get can tell + // if we are in a block or a line comment if (commentAtStart) { searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); isBlockComment = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); @@ -381,7 +375,7 @@ define(function (require, exports, module) { } suffixPos = result && {line: ctx.pos.line, ch: ctx.token.end - suffix.length}; - // Lets check that there are not more comments in the selection, we do nothing if there is one + // Lets check if there are more comments in the selection. We do nothing if there is one do { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && editor.indexFromPos(ctx.pos) <= selEndIndex; From 851cfea435429247fbb3dc97332cd6750507c291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Sat, 13 Apr 2013 23:24:34 -0300 Subject: [PATCH 10/10] Updates and minor refactor after CodeMirror update --- src/editor/EditorCommandHandlers.js | 72 +++++++++++++---------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 631cfaa2dfd..95578297f7b 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -288,7 +288,6 @@ define(function (require, exports, module) { var doc = editor.document, sel = editor.getSelection(), ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.start.ch}), - line = doc.getLine(sel.start.line), selEndIndex = editor.indexFromPos(sel.end), lineExp = _createLineExpressions(linePrefixes, prefix, suffix), prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), @@ -299,16 +298,17 @@ define(function (require, exports, module) { isBlockComment = false, canComment = false, invalidComment = false, - lineUncomment = false; + lineUncomment = false, + result = true; - var searchCtx, atSuffix, newSelection, result = true; + var searchCtx, atSuffix, newSelection, initialPos, endLine; // First move the context to the first none white-space token if (!ctx.token.className && ctx.token.string.trim().length === 0) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); } - // Next, loop util we find a comment inside the selection + // Next, move forwards until we find a comment inside the selection while (result && ctx.token.className !== "comment") { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && editor.indexFromPos(ctx.pos) <= selEndIndex; @@ -317,31 +317,28 @@ define(function (require, exports, module) { // We are now in a comment, lets check if it is a block or a line comment if (result && ctx.token.className === "comment") { - line = doc.getLine(ctx.pos.line); - + // This token might be at a line comment, but we can't be sure yet if (_matchExpressions(ctx.token.string, lineExp)) { - // This is a line comment, lets check if the whole line is a line comment too - if (_matchExpressions(line, lineExp)) { - // If we found this comment at the start of the selection, we need to search backwards until we get can tell - // if we are in a block or a line comment - if (commentAtStart) { - searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); - isBlockComment = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); - // If not, we already know that is a line comment - } else { - isBlockComment = false; - } - // If it was not a whole line comment, then we can just comment + // If the token starts at ch 0 with no starting white spaces, then this might be a block comment or a line + // comment over the whole line, and if we found this comment at the start of the selection, we need to search + // backwards until we get can tell if we are in a block or a line comment + if (ctx.token.start === 0 && !ctx.token.string.match(/^\\s*/) && commentAtStart) { + searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); + isBlockComment = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); + + // If not, we already know that is a line comment } else { - canComment = true; + isBlockComment = false; } + // If it was not a line comment, it has to be a block comment } else { isBlockComment = true; - // If we are in a line that only has a prefix or suffix and the prefix and suffix are the same string, - // Lets find first if this is a prefix or suffix and move the token to the inside of the block comment - if (ctx.token.string.match(prefixExp) && prefix === suffix && ctx.token.string.length === prefix.length) { + // If we are in a line that only has a prefix or a suffix and the prefix and suffix are the same string, + // lets find first if this is a prefix or suffix and move the token to the inside of the block comment. + // This is required so that later we can find the prefix by moving backwards and the suffix by moving forwards. + if (ctx.token.string === prefix && prefix === suffix) { searchCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: ctx.pos.line, ch: ctx.token.start}); atSuffix = _isPrevTokenABlockComment(searchCtx, prefix, suffix, prefixExp, suffixExp, lineExp); if (atSuffix) { @@ -351,14 +348,10 @@ define(function (require, exports, module) { } } } - } else { - canComment = true; - } - - if (!canComment) { + if (isBlockComment) { // Save the initial position to start searching for the suffix from here - var initialPos = $.extend({}, ctx.pos); + initialPos = $.extend({}, ctx.pos); // Find the position of the start of the prefix result = true; @@ -386,10 +379,10 @@ define(function (require, exports, module) { if (prefixPos && editor.indexFromPos(sel.end) < editor.indexFromPos(prefixPos)) { canComment = true; } - + } else { - // In full-line selection, cursor pos is start of next line - but don't want to modify that line - var endLine = sel.end.line; + // In full-line selection, cursor pos is at the start of next line - but don't want to modify that line + endLine = sel.end.line; if (sel.end.ch === 0 && editor.hasSelection()) { endLine--; } @@ -401,20 +394,21 @@ define(function (require, exports, module) { canComment = true; } } + // If not, we can comment + } else { + canComment = true; } - // Make the edit - if (invalidComment) { - return; - } else if (lineUncomment) { + // Make the edit + if (lineUncomment) { lineCommentPrefix(editor, linePrefixes, prefix, suffix); - } else { + } else if (!invalidComment) { doc.batchOperation(function () { + // Comment out - add the suffix to the start and the prefix to the end of the selection. if (canComment) { - // Comment out - add the suffix to the start and the prefix to the end. var completeLineSel = sel.start.ch === 0 && sel.end.ch === 0 && sel.start.line < sel.end.line; if (completeLineSel) { doc.replaceRange(suffix + "\n", sel.end); @@ -436,13 +430,13 @@ define(function (require, exports, module) { } } - // Uncomment - remove prefix and suffix. + // Uncomment - remove prefix and suffix found. } else { // Find if the prefix and suffix are at the ch 0 and if they are the only thing in the line. // If both are found we assume that a complete line selection comment added new lines, so we remove them. var prefixAtStart = false, suffixAtStart = false; - line = doc.getLine(prefixPos.line).trim(); + var line = doc.getLine(prefixPos.line).trim(); prefixAtStart = prefixPos.ch === 0 && prefix.length === line.length; if (suffixPos) { line = doc.getLine(suffixPos.line).trim();