/* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2010, Ajax.org B.V. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Ajax.org B.V. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ define(function(require, exports, module) { "use strict"; var oop = require("../../lib/oop"); var Behaviour = require("../behaviour").Behaviour; var TokenIterator = require("../../token_iterator").TokenIterator; var lang = require("../../lib/lang"); var SAFE_INSERT_IN_TOKENS = ["text", "paren.rparen", "punctuation.operator"]; var SAFE_INSERT_BEFORE_TOKENS = ["text", "paren.rparen", "punctuation.operator", "comment"]; var autoInsertedBrackets = 0; var autoInsertedRow = -1; var autoInsertedLineEnd = ""; var maybeInsertedBrackets = 0; var maybeInsertedRow = -1; var maybeInsertedLineStart = ""; var maybeInsertedLineEnd = ""; var CstyleBehaviour = function () { CstyleBehaviour.isSaneInsertion = function(editor, session) { var cursor = editor.getCursorPosition(); var iterator = new TokenIterator(session, cursor.row, cursor.column); // Don't insert in the middle of a keyword/identifier/lexical if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) { // Look ahead in case we're at the end of a token var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1); if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) return false; } // Only insert in front of whitespace/comments iterator.stepForward(); return iterator.getCurrentTokenRow() !== cursor.row || this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS); }; CstyleBehaviour.$matchTokenType = function(token, types) { return types.indexOf(token.type || token) > -1; }; CstyleBehaviour.recordAutoInsert = function(editor, session, bracket) { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); // Reset previous state if text or context changed too much if (!this.isAutoInsertedClosing(cursor, line, autoInsertedLineEnd[0])) autoInsertedBrackets = 0; autoInsertedRow = cursor.row; autoInsertedLineEnd = bracket + line.substr(cursor.column); autoInsertedBrackets++; }; CstyleBehaviour.recordMaybeInsert = function(editor, session, bracket) { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); if (!this.isMaybeInsertedClosing(cursor, line)) maybeInsertedBrackets = 0; maybeInsertedRow = cursor.row; maybeInsertedLineStart = line.substr(0, cursor.column) + bracket; maybeInsertedLineEnd = line.substr(cursor.column); maybeInsertedBrackets++; }; CstyleBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) { return autoInsertedBrackets > 0 && cursor.row === autoInsertedRow && bracket === autoInsertedLineEnd[0] && line.substr(cursor.column) === autoInsertedLineEnd; }; CstyleBehaviour.isMaybeInsertedClosing = function(cursor, line) { return maybeInsertedBrackets > 0 && cursor.row === maybeInsertedRow && line.substr(cursor.column) === maybeInsertedLineEnd && line.substr(0, cursor.column) == maybeInsertedLineStart; }; CstyleBehaviour.popAutoInsertedClosing = function() { autoInsertedLineEnd = autoInsertedLineEnd.substr(1); autoInsertedBrackets--; }; CstyleBehaviour.clearMaybeInsertedClosing = function() { maybeInsertedBrackets = 0; maybeInsertedRow = -1; }; this.add("braces", "insertion", function (state, action, editor, session, text) { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); if (text == '{') { var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) { return { text: '{' + selected + '}', selection: false }; } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { if (/[\]\}\)]/.test(line[cursor.column])) { CstyleBehaviour.recordAutoInsert(editor, session, "}"); return { text: '{}', selection: [1, 1] }; } else { CstyleBehaviour.recordMaybeInsert(editor, session, "{"); return { text: '{', selection: [1, 1] }; } } } else if (text == '}') { var rightChar = line.substring(cursor.column, cursor.column + 1); if (rightChar == '}') { var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row}); if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { CstyleBehaviour.popAutoInsertedClosing(); return { text: '', selection: [1, 1] }; } } } else if (text == "\n" || text == "\r\n") { var closing = ""; if (CstyleBehaviour.isMaybeInsertedClosing(cursor, line)) { closing = lang.stringRepeat("}", maybeInsertedBrackets); CstyleBehaviour.clearMaybeInsertedClosing(); } var rightChar = line.substring(cursor.column, cursor.column + 1); if (rightChar == '}' || closing !== "") { var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column+1}, '}'); if (!openBracePos) return null; var indent = this.getNextLineIndent(state, line.substring(0, cursor.column), session.getTabString()); var next_indent = this.$getIndent(line); return { text: '\n' + indent + '\n' + next_indent + closing, selection: [1, indent.length, 1, indent.length] }; } } }); this.add("braces", "deletion", function (state, action, editor, session, range) { var selected = session.doc.getTextRange(range); if (!range.isMultiLine() && selected == '{') { var line = session.doc.getLine(range.start.row); var rightChar = line.substring(range.end.column, range.end.column + 1); if (rightChar == '}') { range.end.column++; return range; } else { maybeInsertedBrackets--; } } }); this.add("parens", "insertion", function (state, action, editor, session, text) { if (text == '(') { var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && editor.getWrapBehavioursEnabled()) { return { text: '(' + selected + ')', selection: false }; } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { CstyleBehaviour.recordAutoInsert(editor, session, ")"); return { text: '()', selection: [1, 1] }; } } else if (text == ')') { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); var rightChar = line.substring(cursor.column, cursor.column + 1); if (rightChar == ')') { var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row}); if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { CstyleBehaviour.popAutoInsertedClosing(); return { text: '', selection: [1, 1] }; } } } }); this.add("parens", "deletion", function (state, action, editor, session, range) { var selected = session.doc.getTextRange(range); if (!range.isMultiLine() && selected == '(') { var line = session.doc.getLine(range.start.row); var rightChar = line.substring(range.start.column + 1, range.start.column + 2); if (rightChar == ')') { range.end.column++; return range; } } }); this.add("brackets", "insertion", function (state, action, editor, session, text) { if (text == '[') { var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && editor.getWrapBehavioursEnabled()) { return { text: '[' + selected + ']', selection: false }; } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { CstyleBehaviour.recordAutoInsert(editor, session, "]"); return { text: '[]', selection: [1, 1] }; } } else if (text == ']') { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); var rightChar = line.substring(cursor.column, cursor.column + 1); if (rightChar == ']') { var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row}); if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) { CstyleBehaviour.popAutoInsertedClosing(); return { text: '', selection: [1, 1] }; } } } }); this.add("brackets", "deletion", function (state, action, editor, session, range) { var selected = session.doc.getTextRange(range); if (!range.isMultiLine() && selected == '[') { var line = session.doc.getLine(range.start.row); var rightChar = line.substring(range.start.column + 1, range.start.column + 2); if (rightChar == ']') { range.end.column++; return range; } } }); this.add("string_dquotes", "insertion", function (state, action, editor, session, text) { if (text == '"' || text == "'") { var quote = text; var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) { return { text: quote + selected + quote, selection: false }; } else { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); var leftChar = line.substring(cursor.column-1, cursor.column); // We're escaped. if (leftChar == '\\') { return null; } // Find what token we're inside. var tokens = session.getTokens(selection.start.row); var col = 0, token; var quotepos = -1; // Track whether we're inside an open quote. for (var x = 0; x < tokens.length; x++) { token = tokens[x]; if (token.type == "string") { quotepos = -1; } else if (quotepos < 0) { quotepos = token.value.indexOf(quote); } if ((token.value.length + col) > selection.start.column) { break; } col += tokens[x].value.length; } // Try and be smart about when we auto insert. if (!token || (quotepos < 0 && token.type !== "comment" && (token.type !== "string" || ((selection.start.column !== token.value.length+col-1) && token.value.lastIndexOf(quote) === token.value.length-1)))) { if (!CstyleBehaviour.isSaneInsertion(editor, session)) return; return { text: quote + quote, selection: [1,1] }; } else if (token && token.type === "string") { // Ignore input and move right one if we're typing over the closing quote. var rightChar = line.substring(cursor.column, cursor.column + 1); if (rightChar == quote) { return { text: '', selection: [1, 1] }; } } } } }); this.add("string_dquotes", "deletion", function (state, action, editor, session, range) { var selected = session.doc.getTextRange(range); if (!range.isMultiLine() && (selected == '"' || selected == "'")) { var line = session.doc.getLine(range.start.row); var rightChar = line.substring(range.start.column + 1, range.start.column + 2); if (rightChar == selected) { range.end.column++; return range; } } }); }; oop.inherits(CstyleBehaviour, Behaviour); exports.CstyleBehaviour = CstyleBehaviour; });