From 1c1d5cd332de0420a3e802d18997bc5fec93e6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Thu, 8 Nov 2012 05:30:04 -0300 Subject: [PATCH 1/9] Adding block-commenting --- src/command/Commands.js | 1 + src/command/Menus.js | 2 + src/editor/EditorCommandHandlers.js | 271 +++++++++++++++++++++++++++- src/nls/root/strings.js | 1 + 4 files changed, 265 insertions(+), 10 deletions(-) diff --git a/src/command/Commands.js b/src/command/Commands.js index 896af77e8a1..42188098360 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -66,6 +66,7 @@ define(function (require, exports, module) { exports.EDIT_DUPLICATE = "edit.duplicate"; exports.EDIT_DELETE_LINES = "edit.deletelines"; exports.EDIT_LINE_COMMENT = "edit.lineComment"; + exports.EDIT_BLOCK_COMMENT = "edit.blockComment"; exports.EDIT_LINE_UP = "edit.lineUp"; exports.EDIT_LINE_DOWN = "edit.lineDown"; diff --git a/src/command/Menus.js b/src/command/Menus.js index 6bc37e91e81..e7f8b172af2 100644 --- a/src/command/Menus.js +++ b/src/command/Menus.js @@ -916,6 +916,8 @@ define(function (require, exports, module) { platform: "mac"}]); menu.addMenuDivider(); menu.addMenuItem(Commands.EDIT_LINE_COMMENT, "Ctrl-/"); + menu.addMenuItem(Commands.EDIT_BLOCK_COMMENT, [{key: "Ctrl-Shift-/", platform: "win"}, + {key: "Ctrl-Alt-/", platform: "mac"}]); /* * View menu diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 7627f1d6480..642f1ea6370 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -35,7 +35,8 @@ define(function (require, exports, module) { var Commands = require("command/Commands"), Strings = require("strings"), CommandManager = require("command/CommandManager"), - EditorManager = require("editor/EditorManager"); + EditorManager = require("editor/EditorManager"), + StringUtils = require("utils/StringUtils"); /** @@ -44,6 +45,30 @@ define(function (require, exports, module) { var DIRECTION_UP = -1; var DIRECTION_DOWN = +1; + + /** + * @private + * Searchs for an uncomented line in between sartLine and endLine + * @param {!Editor} editor + * @param {!number} startLine - valid line inside the document + * @param {!number} endLine - valid line inside the document + * @return {boolean} true if there is at least one uncomented line + */ + function _containsUncommented(editor, startLine, endLine) { + var containsUncommented = false; + var i; + var line; + 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 "//" + if (!line.match(/^\s*\/\//) && line.match(/\S/)) { + containsUncommented = true; + break; + } + } + return containsUncommented; + } + /** * Add or remove line-comment tokens to all the lines in the selected range, preserving selection * and cursor position. Applies to currently focused Editor. @@ -70,17 +95,9 @@ 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 = false; + var containsUncommented = _containsUncommented(editor, startLine, endLine); var i; var line; - for (i = startLine; i <= endLine; i++) { - line = doc.getLine(i); - // A line is commented out if it starts with 0-N whitespace chars, then "//" - if (!line.match(/^\s*\/\//) && line.match(/\S/)) { - containsUncommented = true; - break; - } - } // Make the edit doc.batchOperation(function () { @@ -130,6 +147,239 @@ define(function (require, exports, module) { } + /** + * @private + * Returns the position of the block-comment starting prefix for the block-comment passed. + * @param {!Editor} editor + * @param {!{line: number, ch: number}} start - must be a position inside a block-comment + * @param {!RegExp} prefixExp - a valid regular expression + * @return {{line: number, ch: number}} + */ + function _findCommentStart(editor, start, prefixExp) { + var pos = {line: start.line, ch: start.ch}, + token = editor._codeMirror.getTokenAt(pos), + line = ""; + + while (!token.string.match(prefixExp)) { + pos.line--; + line = editor.document.getLine(pos.line); + pos.ch = line.length - 1; + token = editor._codeMirror.getTokenAt(pos); + } + return {line: pos.line, ch: token.start}; + } + + /** + * @private + * Returns the position of the block-comment ending suffix for the block-comment passed. + * Returns null if gets to the end of the document and didn't found it. + * @param {Editor} editor + * @param {!{line: number, ch: number}} start - must be a position inside a block-comment + * @param {!RegExp} suffixExp - a valid regular expression + * @param {!number} suffixLen - length of the suffix + * @return {?{line: number, ch: number}} + */ + function _findCommentEnd(editor, start, suffixExp, suffixLen) { + var pos = {line: start.line, ch: start.ch}, + token = editor._codeMirror.getTokenAt(pos), + total = editor.lineCount() - 1, + line = ""; + + while (pos.line < total && !token.string.match(suffixExp)) { + pos.line++; + line = editor.document.getLine(pos.line); + pos.ch = line.indexOf(line.trim().charAt(0)) + 1; + token = editor._codeMirror.getTokenAt(pos); + } + return (pos.line === total) ? null : {line: pos.line, ch: token.end - suffixLen}; + } + + /** + * @private + * Returns the position position of the next block-comment (the character position will be + * inside a block-comment and not necessarily at the start character). + * Returns null if there isn't one in between start and end. + * @param {!Editor} editor + * @param {!{line: number, ch: number}} start - from where to start searching + * @param {!{line: number, ch: number}} end - where to stop searching + * @param {!RegExp} prefixExp - a valid regular expression + * @return {?{line: number, ch: number}} + */ + function _findNextCommentToken(editor, start, end, prefixExp) { + var pos = {line: start.line, ch: start.ch + 1}, + token = editor._codeMirror.getTokenAt(pos), + line = editor.document.getLine(pos.line); + + while (token.className !== "comment" || token.string.match(/^\/\//)) { + pos.ch++; + if (pos.line === end.line && (pos.ch > end.ch || pos.ch >= line.length)) { + break; + } else if (pos.ch >= line.length) { + pos.line++; + line = editor.document.getLine(pos.line); + pos.ch = line.indexOf(line.trim().charAt(0)) + 1; + } + token = editor._codeMirror.getTokenAt(pos); + } + return token.className !== "comment" || token.string.match(/^\/\//) ? null : pos; + } + + /** + * Add or remove block-comment tokens to the selection, preserving selection + * and cursor position. Applies to the currently focused Editor. + * + * If the selection is inside a block-comment or one block-comment is inside or partially + * inside the selection we we uncomment; otherwise we comment out. + * 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 all the lines in the selection are line-commented and + * will do nothing if at least one line is not line-commented. + * + * @param {!Editor} editor + * @param {!String} prefix + * @param {!String} suffix + * @param {?boolean} slashComment - if the mode also supports "//" comments + */ + function blockCommentPrefixSuffix(editor, prefix, suffix, slashComment) { + + var doc = editor.document, + sel = editor.getSelection(), + lineCount = editor.lineCount(), + startToken = editor._codeMirror.getTokenAt(sel.start), + endToken = editor._codeMirror.getTokenAt(sel.end), + prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), + suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), + prefixPos = null, + suffixPos = null, + canComment = false; + + var i, pos, line, token, start; + + // Check if we should just do a line uncomment (if all lines in the selection are commented) + if (slashComment && (startToken.string.match(/^\/\//) || endToken.string.match(/^\/\//))) { + // Check if the line-comment is actually inside a block-comment + line = editor.document.getLine(sel.start.line - 1); + token = editor._codeMirror.getTokenAt({line: sel.start.line - 1, ch: line.length - 1}); + + if (!token.string.match(prefixExp)) { + if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { + lineCommentSlashSlash(editor); + return; + // If can't uncomment then let the user comment even if it will be an invalid block-comment + } else { + canComment = true; + } + } else { + start = line.indexOf(line.trim().charAt(0)) + 1; + prefixPos = _findCommentStart(editor, {line: sel.start.line - 1, ch: line.length - 1}, prefixExp); + suffixPos = _findCommentEnd(editor, {line: sel.start.line + 1, ch: start}, suffixExp, suffix.length); + } + + // If the start of the selection is inside a comment, find the start + } else if (startToken.className === "comment" && startToken.end > sel.start.ch) { + prefixPos = _findCommentStart(editor, sel.start, prefixExp); + suffixPos = _findCommentEnd(editor, sel.start, suffixExp, suffix.length); + + // If this is a one line selection and is before the text or in an "empty" line + } else if (sel.start.line === sel.end.line && endToken.className === null) { + // Find the first not empty line + i = sel.start.line; + do { + line = doc.getLine(i); + i--; + } while (line.trim().length === 0 && i >= 0); + + // Get the token at the first character after the spaces + pos = {line: i, ch: line.indexOf(line.trim().charAt(0)) + 1}; + token = editor._codeMirror.getTokenAt(pos); + + if (token.className === "comment") { + prefixPos = _findCommentStart(editor, pos, prefixExp); + suffixPos = _findCommentEnd(editor, pos, suffixExp, suffix.length); + } else { + canComment = true; + } + + // If not try to find the first comment inside the selection + } else { + pos = _findNextCommentToken(editor, sel.start, sel.end, prefixExp); + + // If nothing was found is ok to comment + if (pos === null) { + canComment = true; + } else { + token = editor._codeMirror.getTokenAt(pos); + + if (!token.string.match(prefixExp)) { + prefixPos = _findCommentStart(editor, pos, prefixExp); + } else { + prefixPos = {line: pos.line, ch: token.start}; + } + suffixPos = _findCommentEnd(editor, pos, suffixExp, suffix.length); + } + } + + // Search if there is another comment in the selection. Let the user comment if there is. + if (!canComment && suffixPos) { + start = {line: suffixPos.line, ch: suffixPos.ch + suffix.length + 1}; + if (editor.posWithinRange(start, sel.start, sel.end)) { + pos = _findNextCommentToken(editor, start, sel.end, prefixExp); + + if (pos !== null) { + canComment = true; + } + } + } + + // Make the edit + doc.batchOperation(function () { + + if (canComment) { + // Comment out - add the suffix to the start and the prefix to the end + doc.replaceRange(prefix + editor.getSelectedText() + suffix, sel.start, sel.end); + + // Correct the selection by prefix length + var newSelStart = {line: sel.start.line, ch: sel.start.ch + prefix.length}; + if (sel.start.line === sel.end.line) { + editor.setSelection(newSelStart, {line: sel.end.line, ch: sel.end.ch + prefix.length}); + } else { + editor.setSelection(newSelStart, {line: sel.end.line, ch: sel.end.ch}); + } + + } else { + // Uncomment - remove prefix and suffix + if (suffixPos) { + doc.replaceRange("", suffixPos, {line: suffixPos.line, ch: suffixPos.ch + suffix.length}); + } + doc.replaceRange("", prefixPos, {line: prefixPos.line, ch: prefixPos.ch + prefix.length}); + } + }); + } + + /** + * Invokes a language-specific block-comment/uncomment handler + * @param {?Editor} editor If unspecified, applies to the currently focused editor + */ + function blockComment(editor) { + editor = editor || EditorManager.getFocusedEditor(); + if (!editor) { + return; + } + + var mode = editor.getModeForSelection(); + + if (mode === "javascript" || mode === "less") { + blockCommentPrefixSuffix(editor, "/*", "*/", true); + } else if (mode === "css") { + blockCommentPrefixSuffix(editor, "/*", "*/"); + } else if (mode === "html") { + blockCommentPrefixSuffix(editor, ""); + } + } + + /** * Duplicates the selected text, or current line if no selection. The cursor/selection is left * on the second copy. @@ -317,6 +567,7 @@ define(function (require, exports, module) { CommandManager.register(Strings.CMD_INDENT, Commands.EDIT_INDENT, indentText); CommandManager.register(Strings.CMD_UNINDENT, Commands.EDIT_UNINDENT, unidentText); CommandManager.register(Strings.CMD_COMMENT, Commands.EDIT_LINE_COMMENT, lineComment); + CommandManager.register(Strings.CMD_BLOCK_COMMENT, Commands.EDIT_BLOCK_COMMENT, blockComment); CommandManager.register(Strings.CMD_DUPLICATE, Commands.EDIT_DUPLICATE, duplicateText); CommandManager.register(Strings.CMD_DELETE_LINES, Commands.EDIT_DELETE_LINES, deleteCurrentLines); CommandManager.register(Strings.CMD_LINE_UP, Commands.EDIT_LINE_UP, moveLineUp); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 19f6c918984..9bfd911703e 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -186,6 +186,7 @@ define({ "CMD_DUPLICATE" : "Duplicate", "CMD_DELETE_LINES" : "Delete Line", "CMD_COMMENT" : "Toggle Line Comment", + "CMD_BLOCK_COMMENT" : "Toggle Block Comment", "CMD_LINE_UP" : "Move Line Up", "CMD_LINE_DOWN" : "Move Line Down", From 32c88f363b449a02ac98823d85ea7045736296a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Thu, 8 Nov 2012 05:53:31 -0300 Subject: [PATCH 2/9] Description fix --- src/editor/EditorCommandHandlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 642f1ea6370..76d6e8509a9 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -230,7 +230,7 @@ define(function (require, exports, module) { * * If the selection is inside a block-comment or one block-comment is inside or partially * inside the selection we we uncomment; otherwise we comment out. - * Commenting out adds the prefix before the selection and the suffix after + * 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 From b1e356cc4bf03f7f0ac785468726148a3af74e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Thu, 8 Nov 2012 05:53:31 -0300 Subject: [PATCH 3/9] Description fix --- src/editor/EditorCommandHandlers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 642f1ea6370..6175c87a3fe 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -229,13 +229,13 @@ define(function (require, exports, module) { * and cursor position. Applies to the currently focused Editor. * * If the selection is inside a block-comment or one block-comment is inside or partially - * inside the selection we we uncomment; otherwise we comment out. - * Commenting out adds the prefix before the selection and the suffix after + * inside the selection we uncomment; otherwise we comment out. + * 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 all the lines in the selection are line-commented and - * will do nothing if at least one line is not line-commented. + * 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. * * @param {!Editor} editor * @param {!String} prefix From 2c2df20b1681bf3523af0493fe14d123793b07bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Fri, 9 Nov 2012 05:12:08 -0300 Subject: [PATCH 4/9] Code change to use TokenUtils --- src/editor/EditorCommandHandlers.js | 145 +++++++++++----------------- 1 file changed, 55 insertions(+), 90 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index f5525d40ddc..683a1620b6f 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -36,7 +36,8 @@ define(function (require, exports, module) { Strings = require("strings"), CommandManager = require("command/CommandManager"), EditorManager = require("editor/EditorManager"), - StringUtils = require("utils/StringUtils"); + StringUtils = require("utils/StringUtils"), + TokenUtils = require("utils/TokenUtils"); /** @@ -149,79 +150,54 @@ define(function (require, exports, module) { /** * @private - * Returns the position of the block-comment starting prefix for the block-comment passed. - * @param {!Editor} editor - * @param {!{line: number, ch: number}} start - must be a position inside a block-comment + * Moves ctx to the token that starts the block-comment. Ctx starts in a block-comment + * Returns the position of the prefix. + * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} ctx * @param {!RegExp} prefixExp - a valid regular expression * @return {{line: number, ch: number}} */ - function _findCommentStart(editor, start, prefixExp) { - var pos = {line: start.line, ch: start.ch}, - token = editor._codeMirror.getTokenAt(pos), - line = ""; - - while (!token.string.match(prefixExp)) { - pos.line--; - line = editor.document.getLine(pos.line); - pos.ch = line.length - 1; - token = editor._codeMirror.getTokenAt(pos); + function _findCommentStart(ctx, prefixExp) { + while (!ctx.token.string.match(prefixExp)) { + TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } - return {line: pos.line, ch: token.start}; + return {line: ctx.pos.line, ch: ctx.token.start}; } /** * @private - * Returns the position of the block-comment ending suffix for the block-comment passed. - * Returns null if gets to the end of the document and didn't found it. - * @param {Editor} editor - * @param {!{line: number, ch: number}} start - must be a position inside a block-comment + * Moves ctx 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 * @param {!RegExp} suffixExp - a valid regular expression * @param {!number} suffixLen - length of the suffix * @return {?{line: number, ch: number}} */ - function _findCommentEnd(editor, start, suffixExp, suffixLen) { - var pos = {line: start.line, ch: start.ch}, - token = editor._codeMirror.getTokenAt(pos), - total = editor.lineCount() - 1, - line = ""; + function _findCommentEnd(ctx, suffixExp, suffixLen) { + var result = true; - while (pos.line < total && !token.string.match(suffixExp)) { - pos.line++; - line = editor.document.getLine(pos.line); - pos.ch = line.indexOf(line.trim().charAt(0)) + 1; - token = editor._codeMirror.getTokenAt(pos); + while (result && !ctx.token.string.match(suffixExp)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); } - return (pos.line === total) ? null : {line: pos.line, ch: token.end - suffixLen}; + return !result ? null : {line: ctx.pos.line, ch: ctx.token.end - suffixLen}; } /** * @private - * Returns the position position of the next block-comment (the character position will be - * inside a block-comment and not necessarily at the start character). - * Returns null if there isn't one in between start and end. - * @param {!Editor} editor - * @param {!{line: number, ch: number}} start - from where to start searching + * Moves ctx to the next block-comment if there is one before end. + * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} ctx * @param {!{line: number, ch: number}} end - where to stop searching * @param {!RegExp} prefixExp - a valid regular expression - * @return {?{line: number, ch: number}} + * @return {boolean} - true if it found a block-comment */ - function _findNextCommentToken(editor, start, end, prefixExp) { - var pos = {line: start.line, ch: start.ch + 1}, - token = editor._codeMirror.getTokenAt(pos), - line = editor.document.getLine(pos.line); + function _findNextBlockComment(ctx, end, prefixExp) { + var index = ctx.editor.indexFromPos(end), + search = true; - while (token.className !== "comment" || token.string.match(/^\/\//)) { - pos.ch++; - if (pos.line === end.line && (pos.ch > end.ch || pos.ch >= line.length)) { - break; - } else if (pos.ch >= line.length) { - pos.line++; - line = editor.document.getLine(pos.line); - pos.ch = line.indexOf(line.trim().charAt(0)) + 1; - } - token = editor._codeMirror.getTokenAt(pos); + while (search && (ctx.token.className !== "comment" || ctx.token.string.match(/^\/\//))) { + TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + search = ctx.editor.indexFromPos(ctx.pos) <= index; } - return token.className !== "comment" || token.string.match(/^\/\//) ? null : pos; + return search && ctx.token.className === "comment" && !ctx.token.string.match(/^\/\//); } /** @@ -247,23 +223,24 @@ define(function (require, exports, module) { var doc = editor.document, sel = editor.getSelection(), lineCount = editor.lineCount(), - startToken = editor._codeMirror.getTokenAt(sel.start), - endToken = editor._codeMirror.getTokenAt(sel.end), + startCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.end.ch}), + endCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.end.line, ch: sel.end.ch}), prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), prefixPos = null, suffixPos = null, canComment = false; - var i, pos, line, token, start; + var result, start; // Check if we should just do a line uncomment (if all lines in the selection are commented) - if (slashComment && (startToken.string.match(/^\/\//) || endToken.string.match(/^\/\//))) { - // Check if the line-comment is actually inside a block-comment - line = editor.document.getLine(sel.start.line - 1); - token = editor._codeMirror.getTokenAt({line: sel.start.line - 1, ch: line.length - 1}); + if (slashComment && (startCtx.token.string.match(/^\/\//) || startCtx.token.string.match(/^\/\//))) { + // Find if we aren't actually inside a block-comment + while (startCtx.token.string.match(/^\/\//)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, startCtx); + } - if (!token.string.match(prefixExp)) { + if (!result || startCtx.token.className !== "comment") { if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { lineCommentSlashSlash(editor); return; @@ -272,52 +249,40 @@ define(function (require, exports, module) { canComment = true; } } else { - start = line.indexOf(line.trim().charAt(0)) + 1; - prefixPos = _findCommentStart(editor, {line: sel.start.line - 1, ch: line.length - 1}, prefixExp); - suffixPos = _findCommentEnd(editor, {line: sel.start.line + 1, ch: start}, suffixExp, suffix.length); + prefixPos = _findCommentStart(startCtx, prefixExp); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); } // If the start of the selection is inside a comment, find the start - } else if (startToken.className === "comment" && startToken.end > sel.start.ch) { - prefixPos = _findCommentStart(editor, sel.start, prefixExp); - suffixPos = _findCommentEnd(editor, sel.start, suffixExp, suffix.length); + } else if (startCtx.token.className === "comment" && startCtx.token.end > sel.start.ch) { + prefixPos = _findCommentStart(startCtx, prefixExp); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); // If this is a one line selection and is before the text or in an "empty" line - } else if (sel.start.line === sel.end.line && endToken.className === null) { - // Find the first not empty line - i = sel.start.line; - do { - line = doc.getLine(i); - i--; - } while (line.trim().length === 0 && i >= 0); - - // Get the token at the first character after the spaces - pos = {line: i, ch: line.indexOf(line.trim().charAt(0)) + 1}; - token = editor._codeMirror.getTokenAt(pos); + } else if (sel.start.line === sel.end.line && endCtx.token.className === null) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, startCtx); - if (token.className === "comment") { - prefixPos = _findCommentStart(editor, pos, prefixExp); - suffixPos = _findCommentEnd(editor, pos, suffixExp, suffix.length); + if (startCtx.token.className === "comment") { + prefixPos = _findCommentStart(startCtx, prefixExp); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); } else { canComment = true; } - + // If not try to find the first comment inside the selection } else { - pos = _findNextCommentToken(editor, sel.start, sel.end, prefixExp); + result = _findNextBlockComment(startCtx, sel.end, prefixExp); // If nothing was found is ok to comment - if (pos === null) { + if (!result) { canComment = true; } else { - token = editor._codeMirror.getTokenAt(pos); - - if (!token.string.match(prefixExp)) { - prefixPos = _findCommentStart(editor, pos, prefixExp); + if (!startCtx.token.string.match(prefixExp)) { + prefixPos = _findCommentStart(startCtx, prefixExp); } else { - prefixPos = {line: pos.line, ch: token.start}; + prefixPos = {line: startCtx.pos.line, ch: startCtx.token.start}; } - suffixPos = _findCommentEnd(editor, pos, suffixExp, suffix.length); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); } } @@ -325,9 +290,9 @@ define(function (require, exports, module) { if (!canComment && suffixPos) { start = {line: suffixPos.line, ch: suffixPos.ch + suffix.length + 1}; if (editor.posWithinRange(start, sel.start, sel.end)) { - pos = _findNextCommentToken(editor, start, sel.end, prefixExp); + result = _findNextBlockComment(startCtx, sel.end, prefixExp); - if (pos !== null) { + if (result) { canComment = true; } } From 7c926c470d5435f0b45632515f5ee082b76da68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Fri, 9 Nov 2012 06:49:56 -0300 Subject: [PATCH 5/9] Fixing a particular case --- src/editor/EditorCommandHandlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 683a1620b6f..de58b9270eb 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -240,7 +240,7 @@ define(function (require, exports, module) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, startCtx); } - if (!result || startCtx.token.className !== "comment") { + if (!result || startCtx.token.className !== "comment" || startCtx.token.string.match(suffixExp)) { if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { lineCommentSlashSlash(editor); return; From 6abb018bcc492976a6c27b662eb1dbe1dc6434a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Sat, 10 Nov 2012 21:32:08 -0300 Subject: [PATCH 6/9] More bugs fixed --- src/editor/EditorCommandHandlers.js | 120 ++++++++++++++++------------ 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index de58b9270eb..af7c4fab423 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -150,9 +150,9 @@ define(function (require, exports, module) { /** * @private - * Moves ctx to the token that starts the block-comment. Ctx starts in a block-comment + * Moves the token context to the token that starts the block-comment. Ctx starts in a block-comment * Returns the position of the prefix. - * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} ctx + * @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}} */ @@ -165,9 +165,9 @@ define(function (require, exports, module) { /** * @private - * Moves ctx to the token that ends the block-comment. Ctx starts in a block-comment. + * 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 + * @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}} @@ -183,21 +183,21 @@ define(function (require, exports, module) { /** * @private - * Moves ctx to the next block-comment if there is one before end. - * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} ctx + * Movesthe 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), - search = true; + search = ctx.editor.indexFromPos(ctx.pos) <= index; - while (search && (ctx.token.className !== "comment" || ctx.token.string.match(/^\/\//))) { + while (search && !ctx.token.string.match(prefixExp)) { TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); search = ctx.editor.indexFromPos(ctx.pos) <= index; } - return search && ctx.token.className === "comment" && !ctx.token.string.match(/^\/\//); + return search && !!ctx.token.string.match(prefixExp); } /** @@ -216,14 +216,13 @@ define(function (require, exports, module) { * @param {!Editor} editor * @param {!String} prefix * @param {!String} suffix - * @param {?boolean} slashComment - if the mode also supports "//" comments + * @param {?boolean} slashComment - true if the mode also supports "//" comments */ function blockCommentPrefixSuffix(editor, prefix, suffix, slashComment) { var doc = editor.document, sel = editor.getSelection(), - lineCount = editor.lineCount(), - startCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.end.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}), prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), @@ -231,66 +230,80 @@ define(function (require, exports, module) { suffixPos = null, canComment = false; - var result, start; + var result; - // Check if we should just do a line uncomment (if all lines in the selection are commented) - if (slashComment && (startCtx.token.string.match(/^\/\//) || startCtx.token.string.match(/^\/\//))) { - // Find if we aren't actually inside a block-comment - while (startCtx.token.string.match(/^\/\//)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, startCtx); - } - - if (!result || startCtx.token.className !== "comment" || startCtx.token.string.match(suffixExp)) { - if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { - lineCommentSlashSlash(editor); - return; - // If can't uncomment then let the user comment even if it will be an invalid block-comment - } else { - canComment = true; - } - } else { - prefixPos = _findCommentStart(startCtx, prefixExp); - suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); - } - - // If the start of the selection is inside a comment, find the start - } else if (startCtx.token.className === "comment" && startCtx.token.end > sel.start.ch) { - prefixPos = _findCommentStart(startCtx, prefixExp); - suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); - - // If this is a one line selection and is before the text or in an "empty" line - } else if (sel.start.line === sel.end.line && endCtx.token.className === null) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, startCtx); + // If we are in an empty selection, find the next non-empty token + if (startCtx.token.className === null && endCtx.token.className === null) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx); + // 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); + + if (!editor.posWithinRange(sel.start, prefixPos, suffixPos)) { + canComment = true; + } } else { canComment = true; } - - // If not try to find the first comment inside the selection } else { - result = _findNextBlockComment(startCtx, sel.end, prefixExp); + + // Move the context to the first non-empty token + if (startCtx.token.className === null) { + TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx); + } - // If nothing was found is ok to comment - if (!result) { - canComment = true; - } else { - if (!startCtx.token.string.match(prefixExp)) { - prefixPos = _findCommentStart(startCtx, prefixExp); + // Check if we should just do a line uncomment (if all lines in the selection are commented) + if (slashComment && (startCtx.token.string.match(/^\/\//) || endCtx.token.string.match(/^\/\//))) { + // Find if we aren't actually inside a block-comment + while (startCtx.token.string.match(/^\/\//)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, startCtx); + } + + if (!result || startCtx.token.className !== "comment" || startCtx.token.string.match(suffixExp)) { + if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { + lineCommentSlashSlash(editor); + return; + // If can't uncomment then let the user comment even if it will be an invalid block-comment + } else { + canComment = true; + } } else { - prefixPos = {line: startCtx.pos.line, ch: startCtx.token.start}; + prefixPos = _findCommentStart(startCtx, prefixExp); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); } + + // If the start is inside a comment, find the prefix and suffix positions + } else if (startCtx.token.className === "comment") { + prefixPos = _findCommentStart(startCtx, prefixExp); suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); + + // If not try to find the first comment inside the selection + } else { + result = _findNextBlockComment(startCtx, sel.end, prefixExp); + + // If nothing was found is ok to comment + if (!result) { + canComment = true; + } else { + if (!startCtx.token.string.match(prefixExp)) { + prefixPos = _findCommentStart(startCtx, prefixExp); + } else { + prefixPos = {line: startCtx.pos.line, ch: startCtx.token.start}; + } + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); + } } } // Search if there is another comment in the selection. Let the user comment if there is. if (!canComment && suffixPos) { - start = {line: suffixPos.line, ch: suffixPos.ch + suffix.length + 1}; + var start = {line: suffixPos.line, ch: suffixPos.ch + suffix.length + 1}; if (editor.posWithinRange(start, sel.start, sel.end)) { - result = _findNextBlockComment(startCtx, sel.end, prefixExp); + // Start searching at the next token + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx); + result = !result || _findNextBlockComment(startCtx, sel.end, prefixExp); if (result) { canComment = true; @@ -298,6 +311,7 @@ define(function (require, exports, module) { } } + // Make the edit doc.batchOperation(function () { From cd2f5c5ff72f959fdae08c5b0b153a0421390a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Sun, 11 Nov 2012 20:24:09 -0300 Subject: [PATCH 7/9] Fixes after initial review --- src/command/Menus.js | 37 +++-- src/editor/EditorCommandHandlers.js | 237 +++++++++++++++++----------- 2 files changed, 160 insertions(+), 114 deletions(-) diff --git a/src/command/Menus.js b/src/command/Menus.js index 7c620ffe554..d6ef71029f2 100644 --- a/src/command/Menus.js +++ b/src/command/Menus.js @@ -904,22 +904,21 @@ define(function (require, exports, module) { menu.addMenuItem(Commands.EDIT_REPLACE, [{key: "Ctrl-H", platform: "win"}, {key: "Cmd-Alt-F", platform: "mac"}]); menu.addMenuDivider(); - menu.addMenuItem(Commands.EDIT_INDENT, [{key: "Indent", displayKey: "Tab"}]); - menu.addMenuItem(Commands.EDIT_UNINDENT, [{key: "Unindent", displayKey: "Shift-Tab"}]); - menu.addMenuItem(Commands.EDIT_DUPLICATE, "Ctrl-D"); - menu.addMenuItem(Commands.EDIT_DELETE_LINES, "Ctrl-Shift-D"); - menu.addMenuItem(Commands.EDIT_LINE_UP, [{key: "Ctrl-Shift-Up", displayKey: "Ctrl-Shift-\u2191", - platform: "win"}, - {key: "Cmd-Ctrl-Up", displayKey: "Cmd-Ctrl-\u2191", - platform: "mac"}]); - menu.addMenuItem(Commands.EDIT_LINE_DOWN, [{key: "Ctrl-Shift-Down", displayKey: "Ctrl-Shift-\u2193", - platform: "win"}, - {key: "Cmd-Ctrl-Down", displayKey: "Cmd-Ctrl-\u2193", - platform: "mac"}]); + menu.addMenuItem(Commands.EDIT_INDENT, [{key: "Indent", displayKey: "Tab"}]); + menu.addMenuItem(Commands.EDIT_UNINDENT, [{key: "Unindent", displayKey: "Shift-Tab"}]); + menu.addMenuItem(Commands.EDIT_DUPLICATE, "Ctrl-D"); + menu.addMenuItem(Commands.EDIT_DELETE_LINES, "Ctrl-Shift-D"); + menu.addMenuItem(Commands.EDIT_LINE_UP, [{key: "Ctrl-Shift-Up", displayKey: "Ctrl-Shift-\u2191", + platform: "win"}, + {key: "Cmd-Ctrl-Up", displayKey: "Cmd-Ctrl-\u2191", + platform: "mac"}]); + menu.addMenuItem(Commands.EDIT_LINE_DOWN, [{key: "Ctrl-Shift-Down", displayKey: "Ctrl-Shift-\u2193", + platform: "win"}, + {key: "Cmd-Ctrl-Down", displayKey: "Cmd-Ctrl-\u2193", + platform: "mac"}]); menu.addMenuDivider(); - menu.addMenuItem(Commands.EDIT_LINE_COMMENT, "Ctrl-/"); - menu.addMenuItem(Commands.EDIT_BLOCK_COMMENT, [{key: "Ctrl-Shift-/", platform: "win"}, - {key: "Ctrl-Alt-/", platform: "mac"}]); + menu.addMenuItem(Commands.EDIT_LINE_COMMENT, "Ctrl-/"); + menu.addMenuItem(Commands.EDIT_BLOCK_COMMENT, "Ctrl-Shift-/"); /* * View menu @@ -927,10 +926,10 @@ define(function (require, exports, module) { menu = addMenu(Strings.VIEW_MENU, AppMenuBar.VIEW_MENU); menu.addMenuItem(Commands.VIEW_HIDE_SIDEBAR, "Ctrl-Shift-H"); menu.addMenuDivider(); - menu.addMenuItem(Commands.VIEW_INCREASE_FONT_SIZE, [{key: "Ctrl-=", displayKey: "Ctrl-+"}, - {key: "Ctrl-+", displayKey: "Ctrl-+"}]); - menu.addMenuItem(Commands.VIEW_DECREASE_FONT_SIZE, [{key: "Ctrl--", displayKey: "Ctrl-\u2212"}]); - menu.addMenuItem(Commands.VIEW_RESTORE_FONT_SIZE, "Ctrl-0"); + menu.addMenuItem(Commands.VIEW_INCREASE_FONT_SIZE, [{key: "Ctrl-=", displayKey: "Ctrl-+"}, + {key: "Ctrl-+", displayKey: "Ctrl-+"}]); + menu.addMenuItem(Commands.VIEW_DECREASE_FONT_SIZE, [{key: "Ctrl--", displayKey: "Ctrl-\u2212"}]); + menu.addMenuItem(Commands.VIEW_RESTORE_FONT_SIZE, "Ctrl-0"); menu.addMenuDivider(); menu.addMenuItem(Commands.TOGGLE_JSLINT); diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index af7c4fab423..1d9aaed84d8 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -49,7 +49,7 @@ define(function (require, exports, module) { /** * @private - * Searchs for an uncomented line in between sartLine and endLine + * Searchs for an uncommented line in between startLine and endLine * @param {!Editor} editor * @param {!number} startLine - valid line inside the document * @param {!number} endLine - valid line inside the document @@ -150,17 +150,19 @@ 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. + * 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}} + * @return {?{line: number, ch: number}} */ function _findCommentStart(ctx, prefixExp) { - while (!ctx.token.string.match(prefixExp)) { - TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); + var result = true; + + while (result && !ctx.token.string.match(prefixExp)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } - return {line: ctx.pos.line, ch: ctx.token.start}; + return !result ? null : {line: ctx.pos.line, ch: ctx.token.start}; } /** @@ -183,7 +185,7 @@ define(function (require, exports, module) { /** * @private - * Movesthe token context to the next block-comment if there is one before end. + * 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 @@ -191,13 +193,14 @@ define(function (require, exports, module) { */ function _findNextBlockComment(ctx, end, prefixExp) { var index = ctx.editor.indexFromPos(end), - search = ctx.editor.indexFromPos(ctx.pos) <= index; + inside = ctx.editor.indexFromPos(ctx.pos) <= index, + result = true; - while (search && !ctx.token.string.match(prefixExp)) { - TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); - search = ctx.editor.indexFromPos(ctx.pos) <= index; + while (result && inside && !ctx.token.string.match(prefixExp)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + inside = ctx.editor.indexFromPos(ctx.pos) <= index; } - return search && !!ctx.token.string.match(prefixExp); + return result && inside && !!ctx.token.string.match(prefixExp); } /** @@ -216,94 +219,99 @@ define(function (require, exports, module) { * @param {!Editor} editor * @param {!String} prefix * @param {!String} suffix - * @param {?boolean} slashComment - true if the mode also supports "//" comments + * @param {boolean=} slashComment - true if the mode also supports "//" comments */ function blockCommentPrefixSuffix(editor, prefix, suffix, slashComment) { - var doc = editor.document, - sel = editor.getSelection(), - 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}), - prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), - suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), - prefixPos = null, - suffixPos = null, - canComment = false; + 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}), + prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), + suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), + lineExp = new RegExp("^\/\/"), + prefixPos = null, + suffixPos = null, + canComment = false, + lineUncomment = false; - var result; + var result, text, line; - // If we are in an empty selection, find the next non-empty token - if (startCtx.token.className === null && endCtx.token.className === null) { + // Move the context to the first non-empty token. + if (ctx.token.className === null) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + } + + // Check if we should just do a line uncomment (if all lines in the selection are commented). + if (slashComment && (ctx.token.string.match(lineExp) || endCtx.token.string.match(lineExp))) { + // Find if we aren't actually inside a block-comment + result = true; + while (result && ctx.token.string.match(lineExp)) { + result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); + } + + if (!result || ctx.token.className !== "comment" || ctx.token.string.match(suffixExp)) { + // We aren't in an block-comment. Find if all the lines are line-commented. + if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { + lineUncomment = true; + + // If can't uncomment then let the user comment even if it will be an invalid block-comment. + } else { + canComment = true; + } + } else { + prefixPos = _findCommentStart(startCtx, prefixExp); + suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); + } + + // 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)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx); - // We found a comment, find the start and end and check if the selection is inside the block-comment + // 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); - if (!editor.posWithinRange(sel.start, prefixPos, suffixPos)) { + if (prefixPos !== null && suffix !== null && !editor.posWithinRange(sel.start, prefixPos, suffixPos)) { canComment = true; } } else { canComment = true; } - } else { - // Move the context to the first non-empty token - if (startCtx.token.className === null) { - TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx); - } + // 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); - // Check if we should just do a line uncomment (if all lines in the selection are commented) - if (slashComment && (startCtx.token.string.match(/^\/\//) || endCtx.token.string.match(/^\/\//))) { - // Find if we aren't actually inside a block-comment - while (startCtx.token.string.match(/^\/\//)) { - result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, startCtx); - } - - if (!result || startCtx.token.className !== "comment" || startCtx.token.string.match(suffixExp)) { - if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { - lineCommentSlashSlash(editor); - return; - // If can't uncomment then let the user comment even if it will be an invalid block-comment - } else { - canComment = true; - } - } else { - prefixPos = _findCommentStart(startCtx, prefixExp); - suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); - } + // If not try to find the first comment inside the selection. + } else { + result = _findNextBlockComment(ctx, sel.end, prefixExp); - // If the start is inside a comment, find the prefix and suffix positions - } else if (startCtx.token.className === "comment") { - prefixPos = _findCommentStart(startCtx, prefixExp); - suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); - - // If not try to find the first comment inside the selection + // If nothing was found is ok to comment. + if (!result) { + canComment = true; } else { - result = _findNextBlockComment(startCtx, sel.end, prefixExp); - - // If nothing was found is ok to comment - if (!result) { - canComment = true; + if (!ctx.token.string.match(prefixExp)) { + prefixPos = _findCommentStart(ctx, prefixExp); } else { - if (!startCtx.token.string.match(prefixExp)) { - prefixPos = _findCommentStart(startCtx, prefixExp); - } else { - prefixPos = {line: startCtx.pos.line, ch: startCtx.token.start}; - } - suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length); + prefixPos = {line: ctx.pos.line, ch: ctx.token.start}; } + suffixPos = _findCommentEnd(ctx, suffixExp, suffix.length); } } - // Search if there is another comment in the selection. Let the user comment if there is. - if (!canComment && suffixPos) { + // Search if there is another comment in the selection. Let the user comment if there is one. + if (!canComment && !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 - result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx); - result = !result || _findNextBlockComment(startCtx, sel.end, prefixExp); + // Start searching at the next token, if there is one. + result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); + result = !result || _findNextBlockComment(ctx, sel.end, prefixExp); if (result) { canComment = true; @@ -313,28 +321,67 @@ define(function (require, exports, module) { // Make the edit - doc.batchOperation(function () { - - if (canComment) { - // Comment out - add the suffix to the start and the prefix to the end - doc.replaceRange(prefix + editor.getSelectedText() + suffix, sel.start, sel.end); + if (lineUncomment) { + lineCommentSlashSlash(editor); + + } else { + doc.batchOperation(function () { + + if (canComment) { + // Get the text with the prefix and suffix added. + var completeLineSel = sel.start.ch === 0 && sel.end.ch === 0 && sel.start.line < sel.end.line; + if (completeLineSel) { + text = prefix + "\n" + editor.getSelectedText() + suffix + "\n"; + } else { + text = prefix + editor.getSelectedText() + suffix; + } + + // Comment out - add the suffix to the start and the prefix to the end. + doc.replaceRange(text, sel.start, sel.end); + + // Correct the selection. + if (completeLineSel) { + editor.setSelection({line: sel.start.line + 1, ch: 0}, {line: sel.end.line + 1, ch: 0}); + } else { + var newSelStart = {line: sel.start.line, ch: sel.start.ch + prefix.length}; + if (sel.start.line === sel.end.line) { + editor.setSelection(newSelStart, {line: sel.end.line, ch: sel.end.ch + prefix.length}); + } else { + editor.setSelection(newSelStart, {line: sel.end.line, ch: sel.end.ch}); + } + } - // Correct the selection by prefix length - var newSelStart = {line: sel.start.line, ch: sel.start.ch + prefix.length}; - if (sel.start.line === sel.end.line) { - editor.setSelection(newSelStart, {line: sel.end.line, ch: sel.end.ch + prefix.length}); + // Uncomment - remove prefix and suffix. } else { - editor.setSelection(newSelStart, {line: sel.end.line, ch: sel.end.ch}); - } - - } else { - // Uncomment - remove prefix and suffix - if (suffixPos) { - doc.replaceRange("", suffixPos, {line: suffixPos.line, ch: suffixPos.ch + suffix.length}); + // 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(); + prefixAtStart = prefixPos.ch === 0 && prefix.length === line.length; + if (suffixPos) { + line = doc.getLine(suffixPos.line).trim(); + suffixAtStart = suffixPos.ch === 0 && suffix.length === line.length; + } + + // Remove the suffix if there is one + if (suffixPos) { + if (prefixAtStart && suffixAtStart) { + doc.replaceRange("", suffixPos, {line: suffixPos.line + 1, ch: 0}); + } else { + doc.replaceRange("", suffixPos, {line: suffixPos.line, ch: suffixPos.ch + suffix.length}); + } + } + + // Remove the prefix + if (prefixAtStart && suffixAtStart) { + doc.replaceRange("", prefixPos, {line: prefixPos.line + 1, ch: 0}); + } else { + doc.replaceRange("", prefixPos, {line: prefixPos.line, ch: prefixPos.ch + prefix.length}); + } } - doc.replaceRange("", prefixPos, {line: prefixPos.line, ch: prefixPos.ch + prefix.length}); - } - }); + }); + } } /** @@ -352,9 +399,9 @@ define(function (require, exports, module) { if (mode === "javascript" || mode === "less") { blockCommentPrefixSuffix(editor, "/*", "*/", true); } else if (mode === "css") { - blockCommentPrefixSuffix(editor, "/*", "*/"); + blockCommentPrefixSuffix(editor, "/*", "*/", false); } else if (mode === "html") { - blockCommentPrefixSuffix(editor, ""); + blockCommentPrefixSuffix(editor, "", false); } } From 9bbf542162b3fede9c33681cad448849ed77a0ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Sun, 11 Nov 2012 22:17:30 -0300 Subject: [PATCH 8/9] Typo and return fixed --- src/editor/EditorCommandHandlers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index 1d9aaed84d8..fd7062f2289 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -49,11 +49,11 @@ define(function (require, exports, module) { /** * @private - * Searchs for an uncommented line in between startLine and endLine + * 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 - * @return {boolean} true if there is at least one uncomented line + * @return {boolean} true if there is at least one uncommented line */ function _containsUncommented(editor, startLine, endLine) { var containsUncommented = false; @@ -162,7 +162,7 @@ define(function (require, exports, module) { while (result && !ctx.token.string.match(prefixExp)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx); } - return !result ? null : {line: ctx.pos.line, ch: ctx.token.start}; + return result ? {line: ctx.pos.line, ch: ctx.token.start} : null; } /** @@ -180,7 +180,7 @@ define(function (require, exports, module) { while (result && !ctx.token.string.match(suffixExp)) { result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx); } - return !result ? null : {line: ctx.pos.line, ch: ctx.token.end - suffixLen}; + return result ? {line: ctx.pos.line, ch: ctx.token.end - suffixLen} : null; } /** From 8928049a277c4116e1087e52ede7bcf0a954107d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Mon, 12 Nov 2012 03:59:10 -0300 Subject: [PATCH 9/9] Do nothing on invalid selections --- src/editor/EditorCommandHandlers.js | 40 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/editor/EditorCommandHandlers.js b/src/editor/EditorCommandHandlers.js index fd7062f2289..e5182ce8caf 100644 --- a/src/editor/EditorCommandHandlers.js +++ b/src/editor/EditorCommandHandlers.js @@ -223,18 +223,19 @@ define(function (require, exports, module) { */ function blockCommentPrefixSuffix(editor, prefix, suffix, slashComment) { - 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}), - prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), - suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), - lineExp = new RegExp("^\/\/"), - prefixPos = null, - suffixPos = null, - canComment = false, - lineUncomment = false; + 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}), + prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"), + suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"), + lineExp = new RegExp("^\/\/"), + prefixPos = null, + suffixPos = null, + canComment = false, + invalidComment = false, + lineUncomment = false; var result, text, line; @@ -256,9 +257,9 @@ define(function (require, exports, module) { if (!_containsUncommented(editor, sel.start.line, sel.end.line)) { lineUncomment = true; - // If can't uncomment then let the user comment even if it will be an invalid block-comment. + // If can't uncomment then do nothing, since it would create an invalid comment. } else { - canComment = true; + invalidComment = true; } } else { prefixPos = _findCommentStart(startCtx, prefixExp); @@ -305,8 +306,8 @@ define(function (require, exports, module) { } } - // Search if there is another comment in the selection. Let the user comment if there is one. - if (!canComment && !lineUncomment && suffixPos) { + // 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. @@ -314,14 +315,17 @@ define(function (require, exports, module) { result = !result || _findNextBlockComment(ctx, sel.end, prefixExp); if (result) { - canComment = true; + invalidComment = true; } } } // Make the edit - if (lineUncomment) { + if (invalidComment) { + return; + + } else if (lineUncomment) { lineCommentSlashSlash(editor); } else {