diff --git a/demo/editor.html b/demo/editor.html
index 9ea770af..bb947174 100644
--- a/demo/editor.html
+++ b/demo/editor.html
@@ -52,6 +52,7 @@
+
diff --git a/src/ace/Document.js b/src/ace/Document.js
index 1efce9bd..144ef517 100644
--- a/src/ace/Document.js
+++ b/src/ace/Document.js
@@ -12,7 +12,11 @@ ace.Document = function(text, mode) {
this.setMode(mode);
}
- this.insert({row: 0, column: 0}, text);
+ if (ace.isArray(text)) {
+ this.$insertLines(0, text);
+ } else {
+ this.$insert({row: 0, column: 0}, text);
+ }
};
(function() {
@@ -149,6 +153,10 @@ ace.Document = function(text, mode) {
return this.lines[row] || "";
};
+ this.getLines = function(firstRow, lastRow) {
+ return this.lines.slice(firstRow, lastRow+1);
+ };
+
this.getLength = function() {
return this.lines.length;
};
@@ -167,10 +175,6 @@ ace.Document = function(text, mode) {
}
};
- this.getLines = function(firstRow, lastRow) {
- return this.lines.slice(firstRow, lastRow+1);
- };
-
this.findMatchingBracket = function(position) {
if (position.column == 0) return null;
diff --git a/src/ace/Editor.js b/src/ace/Editor.js
index 671ff918..e856688f 100644
--- a/src/ace/Editor.js
+++ b/src/ace/Editor.js
@@ -285,23 +285,25 @@ ace.Editor = function(renderer, doc) {
return;
var cursor = this.getCursorPosition();
-
text = text.replace("\t", this.doc.getTabString());
if (!this.selection.isEmpty()) {
- var end = this.doc.replace(this.getSelectionRange(), text);
+ var cursor = this.doc.remove(this.getSelectionRange());
this.clearSelection();
}
- else {
- var end = this.doc.insert(cursor, text);
- }
+
+ var lineState = this.bgTokenizer.getState(cursor.row-1);
+ var shouldOutdent = this.mode.checkOutdent(lineState, this.doc.getLine(cursor.row), text);
+
+ var end = this.doc.insert(cursor, text);
+
+ var row = cursor.row;
+ var line = this.doc.getLine(row);
+ var lineState = this.bgTokenizer.getState(row);
// multi line insert
- var row = cursor.row;
if (row !== end.row) {
- var line = this.doc.getLine(row);
- var lineState = this.bgTokenizer.getState(row);
- var indent = this.mode.getNextLineIndent(line, lineState, this.doc.getTabString());
+ var indent = this.mode.getNextLineIndent(lineState, line, this.doc.getTabString());
if (indent) {
var indentRange = {
start: {
@@ -312,6 +314,10 @@ ace.Editor = function(renderer, doc) {
};
end.column += this.doc.indentRows(indentRange, indent);
}
+ } else {
+ if (shouldOutdent) {
+ end.column += this.mode.autoOutdent(lineState, this.doc, row);
+ }
}
this.moveCursorToPosition(end);
@@ -424,7 +430,7 @@ ace.Editor = function(renderer, doc) {
}
};
var state = this.bgTokenizer.getState(this.getCursorPosition().row);
- var addedColumns = this.mode.toggleCommentLines(this.doc, range, state);
+ var addedColumns = this.mode.toggleCommentLines(state, this.doc, range);
this.selection.shiftSelection(addedColumns);
};
diff --git a/src/ace/ace.js b/src/ace/ace.js
index f05f9416..214ea027 100644
--- a/src/ace/ace.js
+++ b/src/ace/ace.js
@@ -171,6 +171,10 @@ else {
};
}
+ace.isArray = function(value) {
+ return Object.prototype.toString.call(value) == "[object Array]";
+};
+
ace.bind = function(fcn, context) {
return function() {
return fcn.apply(context, arguments);
diff --git a/src/ace/mode/Css.js b/src/ace/mode/Css.js
index d07720ee..886f10e2 100644
--- a/src/ace/mode/Css.js
+++ b/src/ace/mode/Css.js
@@ -2,12 +2,13 @@ ace.provide("ace.mode.Css");
ace.mode.Css = function() {
this.$tokenizer = new ace.Tokenizer(new ace.mode.CssHighlightRules().getRules());
+ this.$outdent = new ace.mode.MatchingBraceOutdent();
};
ace.inherits(ace.mode.Css, ace.mode.Text);
(function() {
- this.getNextLineIndent = function(line, state, tab) {
+ this.getNextLineIndent = function(state, line, tab) {
var indent = this.$getIndent(line);
// ignore braces in comments
@@ -24,4 +25,12 @@ ace.inherits(ace.mode.Css, ace.mode.Text);
return indent;
};
+ this.checkOutdent = function(state, line, input) {
+ return this.$outdent.checkOutdent(line, input);
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ return this.$outdent.autoOutdent(doc, row);
+ };
+
}).call(ace.mode.Css.prototype);
\ No newline at end of file
diff --git a/src/ace/mode/Html.js b/src/ace/mode/Html.js
index a1fdc32d..cbac2e2f 100644
--- a/src/ace/mode/Html.js
+++ b/src/ace/mode/Html.js
@@ -10,32 +10,44 @@ ace.inherits(ace.mode.Html, ace.mode.Text);
(function() {
- this.toggleCommentLines = function(doc, range, state) {
- var split = state.split("js-");
- if (!split[0] && split[1]) {
- return this.$js.toggleCommentLines(doc, range, state);
- }
-
- var split = state.split("css-");
- if (!split[0] && split[1]) {
- return this.$css.toggleCommentLines(doc, range, state);
- }
-
- return 0;
+ this.toggleCommentLines = function(state, doc, range) {
+ return this.$delegate("toggleCommentLines", arguments, function() {
+ return 0;
+ });
};
- this.getNextLineIndent = function(line, state, tab) {
+ this.getNextLineIndent = function(state, line, tab) {
+ return this.$delegate("getNextLineIndent", arguments, function() {
+ return "";
+ });
+ };
+
+ this.checkOutdent = function(state, line, input) {
+ return this.$delegate("checkOutdent", arguments, function() {
+ return false;
+ });
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ return this.$delegate("autoOutdent", arguments);
+ };
+
+ this.$delegate = function(method, args, defaultHandler) {
+ var state = args[0];
var split = state.split("js-");
+
if (!split[0] && split[1]) {
- return this.$js.getNextLineIndent(line, split[1], tab);
+ args[0] = split[1];
+ return this.$js[method].apply(this.$js, args);
}
var split = state.split("css-");
if (!split[0] && split[1]) {
- return this.$css.getNextLineIndent(line, split[1], tab);
+ args[0] = split[1];
+ return this.$css[method].apply(this.$css, args);
}
- return "";
+ return defaultHandler ? defaultHandler() : undefined;
};
}).call(ace.mode.Html.prototype);
\ No newline at end of file
diff --git a/src/ace/mode/JavaScript.js b/src/ace/mode/JavaScript.js
index cb0cee7a..35329f03 100644
--- a/src/ace/mode/JavaScript.js
+++ b/src/ace/mode/JavaScript.js
@@ -2,12 +2,13 @@ ace.provide("ace.mode.JavaScript");
ace.mode.JavaScript = function() {
this.$tokenizer = new ace.Tokenizer(new ace.mode.JavaScriptHighlightRules().getRules());
+ this.$outdent = new ace.mode.MatchingBraceOutdent();
};
ace.inherits(ace.mode.JavaScript, ace.mode.Text);
(function() {
- this.toggleCommentLines = function(doc, range, state) {
+ this.toggleCommentLines = function(state, doc, range) {
var addedRows = doc.outdentRows(range, "//");
if (addedRows == 0) {
var addedRows = doc.indentRows(range, "//");
@@ -15,7 +16,7 @@ ace.inherits(ace.mode.JavaScript, ace.mode.Text);
return addedRows;
};
- this.getNextLineIndent = function(line, state, tab) {
+ this.getNextLineIndent = function(state, line, tab) {
var indent = this.$getIndent(line);
var tokenizedLine = this.$tokenizer.getLineTokens(line, state);
@@ -47,4 +48,12 @@ ace.inherits(ace.mode.JavaScript, ace.mode.Text);
return indent;
};
+ this.checkOutdent = function(state, line, input) {
+ return this.$outdent.checkOutdent(line, input);
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ return this.$outdent.autoOutdent(doc, row);
+ };
+
}).call(ace.mode.JavaScript.prototype);
\ No newline at end of file
diff --git a/src/ace/mode/MatchingBraceOutdent.js b/src/ace/mode/MatchingBraceOutdent.js
new file mode 100644
index 00000000..5c6e5d0b
--- /dev/null
+++ b/src/ace/mode/MatchingBraceOutdent.js
@@ -0,0 +1,51 @@
+ace.provide("ace.mode.MatchingBraceOutdent");
+
+ace.mode.MatchingBraceOutdent = function() {};
+
+(function() {
+
+ this.checkOutdent = function(line, input) {
+ if (! /^\s+$/.test(line))
+ return false;
+
+ return /^\s*\}/.test(input);
+ };
+
+ this.autoOutdent = function(doc, row) {
+ var line = doc.getLine(row);
+ var match = line.match(/^(\s*\})/);
+
+ if (!match) return 0;
+
+ var column = match[1].length;
+ var openBracePos = doc.findMatchingBracket({row: row, column: column});
+
+ if (!openBracePos || openBracePos.row == row) return 0;
+
+ var indent = this.$getIndent(doc.getLine(openBracePos.row));
+
+ var range = {
+ start: {
+ row: row,
+ column: 0
+ },
+ end: {
+ row: row,
+ column: column-1
+ }
+ };
+ doc.replace(range, indent);
+
+ return indent.length - (column-1);
+ };
+
+ this.$getIndent = function(line) {
+ var match = line.match(/^(\s+)/);
+ if (match) {
+ return match[1];
+ }
+
+ return "";
+ };
+
+}).call(ace.mode.MatchingBraceOutdent.prototype);
\ No newline at end of file
diff --git a/src/ace/mode/Text.js b/src/ace/mode/Text.js
index 013d24b5..e7ed42a0 100644
--- a/src/ace/mode/Text.js
+++ b/src/ace/mode/Text.js
@@ -10,14 +10,21 @@ ace.mode.Text = function() {
return this.$tokenizer;
};
- this.toggleCommentLines = function(doc, range, state) {
+ this.toggleCommentLines = function(state, doc, range) {
return 0;
};
- this.getNextLineIndent = function(line, state, tab) {
+ this.getNextLineIndent = function(state, line, tab) {
return "";
};
+ this.checkOutdent = function(state, line, input) {
+ return false;
+ };
+
+ this.autoOutdent = function(state, doc, row) {
+ };
+
this.$getIndent = function(line) {
var match = line.match(/^(\s+)/);
if (match) {
diff --git a/src/test/DocumentTest.js b/src/test/DocumentTest.js
index ad66f640..d1002ede 100644
--- a/src/test/DocumentTest.js
+++ b/src/test/DocumentTest.js
@@ -1,7 +1,7 @@
var TextDocumentTest = new TestCase("TextDocumentTest", {
"test: find matching opening bracket" : function() {
- var doc = new ace.Document(["(()(", "())))"].join("\n"));
+ var doc = new ace.Document(["(()(", "())))"]);
assertPosition(0, 1, doc.findMatchingBracket({row: 0, column: 3}));
assertPosition(1, 0, doc.findMatchingBracket({row: 1, column: 2}));
@@ -11,7 +11,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", {
},
"test: find matching closing bracket" : function() {
- var doc = new ace.Document(["(()(", "())))"].join("\n"));
+ var doc = new ace.Document(["(()(", "())))"]);
assertPosition(1, 1, doc.findMatchingBracket({row: 1, column: 1}));
assertPosition(1, 1, doc.findMatchingBracket({row: 1, column: 1}));
@@ -22,7 +22,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", {
},
"test: match different bracket types" : function() {
- var doc = new ace.Document(["({[", ")]}"].join("\n"));
+ var doc = new ace.Document(["({[", ")]}"]);
assertPosition(1, 0, doc.findMatchingBracket({row: 0, column: 1}));
assertPosition(1, 2, doc.findMatchingBracket({row: 0, column: 2}));
@@ -34,7 +34,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", {
},
"test: move lines down" : function() {
- var doc = new ace.Document(["1", "2", "3", "4"].join("\n"));
+ var doc = new ace.Document(["1", "2", "3", "4"]);
doc.moveLinesDown(0, 1);
assertEquals(["3", "1", "2", "4"].join("\n"), doc.toString());
@@ -50,7 +50,7 @@ var TextDocumentTest = new TestCase("TextDocumentTest", {
},
"test: move lines up" : function() {
- var doc = new ace.Document(["1", "2", "3", "4"].join("\n"));
+ var doc = new ace.Document(["1", "2", "3", "4"]);
doc.moveLinesUp(2, 3);
assertEquals(["1", "3", "4", "2"].join("\n"), doc.toString());
@@ -66,28 +66,28 @@ var TextDocumentTest = new TestCase("TextDocumentTest", {
},
"test: duplicate lines" : function() {
- var doc = new ace.Document(["1", "2", "3", "4"].join("\n"));
+ var doc = new ace.Document(["1", "2", "3", "4"]);
doc.duplicateLines(1, 2);
assertEquals(["1", "2", "3", "2", "3", "4"].join("\n"), doc.toString());
},
"test: duplicate last line" : function() {
- var doc = new ace.Document(["1", "2", "3"].join("\n"));
+ var doc = new ace.Document(["1", "2", "3"]);
doc.duplicateLines(2, 2);
assertEquals(["1", "2", "3", "3"].join("\n"), doc.toString());
},
"test: duplicate first line" : function() {
- var doc = new ace.Document(["1", "2", "3"].join("\n"));
+ var doc = new ace.Document(["1", "2", "3"]);
doc.duplicateLines(0, 0);
assertEquals(["1", "1", "2", "3"].join("\n"), doc.toString());
},
"test: should handle unix style new lines" : function() {
- var doc = new ace.Document(["1", "2", "3"].join("\n"));
+ var doc = new ace.Document(["1", "2", "3"]);
assertEquals(["1", "2", "3"].join("\n"), doc.toString());
},
diff --git a/src/test/mode/CssTest.js b/src/test/mode/CssTest.js
index 56eec460..19e8d781 100644
--- a/src/test/mode/CssTest.js
+++ b/src/test/mode/CssTest.js
@@ -12,23 +12,23 @@ var CssTest = new TestCase("mode.CssTest", {
end: {row: 1, column: 1}
};
- var comment = this.mode.toggleCommentLines(doc, range, "start");
+ var comment = this.mode.toggleCommentLines("start", doc, range);
assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString());
},
"test: lines should keep indentation" : function() {
- assertEquals(" ", this.mode.getNextLineIndent(" abc", "start", " "));
- assertEquals("\t", this.mode.getNextLineIndent("\tabc", "start", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", " abc", " "));
+ assertEquals("\t", this.mode.getNextLineIndent("start", "\tabc", " "));
},
"test: new line after { should increase indent" : function() {
- assertEquals(" ", this.mode.getNextLineIndent(" abc{", "start", " "));
- assertEquals("\t ", this.mode.getNextLineIndent("\tabc { ", "start", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", " abc{", " "));
+ assertEquals("\t ", this.mode.getNextLineIndent("start", "\tabc { ", " "));
},
"test: no indent increase after { in a comment" : function() {
- assertEquals(" ", this.mode.getNextLineIndent(" /*{", "start", " "));
- assertEquals(" ", this.mode.getNextLineIndent(" /*{ ", "start", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", " /*{", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", " /*{ ", " "));
}
});
\ No newline at end of file
diff --git a/src/test/mode/JavaScriptTest.js b/src/test/mode/JavaScriptTest.js
index e4eb1f94..fb5ad112 100644
--- a/src/test/mode/JavaScriptTest.js
+++ b/src/test/mode/JavaScriptTest.js
@@ -14,74 +14,90 @@ var JavaScriptTest = new TestCase("mode.JavaScriptTest", {
},
"test: toggle comment lines should prepend '//' to each line" : function() {
- var doc = new ace.Document([" abc", "cde", "fg"].join("\n"));
+ var doc = new ace.Document([" abc", "cde", "fg"]);
var range = {
start: {row: 0, column: 3},
end: {row: 1, column: 1}
};
- var comment = this.mode.toggleCommentLines(doc, range, "start");
+ var comment = this.mode.toggleCommentLines("start", doc, range);
assertEquals(["// abc", "//cde", "fg"].join("\n"), doc.toString());
},
"test: toggle comment on commented lines should remove leading '//' chars" : function() {
- var doc = new ace.Document(["// abc", "//cde", "fg"].join("\n"));
+ var doc = new ace.Document(["// abc", "//cde", "fg"]);
var range = {
start: {row: 0, column: 3},
end: {row: 1, column: 1}
};
- var comment = this.mode.toggleCommentLines(doc, range, "start");
+ var comment = this.mode.toggleCommentLines("start", doc, range);
assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString());
},
"test: toggle comment on multiple lines with one commented line prepend '//' to each line" : function() {
- var doc = new ace.Document(["// abc", "//cde", "fg"].join("\n"));
+ var doc = new ace.Document(["// abc", "//cde", "fg"]);
var range = {
start: {row: 0, column: 3},
end: {row: 2, column: 1}
};
- var comment = this.mode.toggleCommentLines(doc, range, "start");
+ var comment = this.mode.toggleCommentLines("start", doc, range);
assertEquals(["//// abc", "////cde", "//fg"].join("\n"), doc.toString());
},
"test: auto indent after opening brace" : function() {
- assertEquals(" ", this.mode.getNextLineIndent("if () {", "start", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", "if () {", " "));
},
"test: no auto indent after opening brace in multi line comment" : function() {
- assertEquals("", this.mode.getNextLineIndent("/*if () {", "start", " "));
- assertEquals(" ", this.mode.getNextLineIndent(" abcd", "comment", " "));
+ assertEquals("", this.mode.getNextLineIndent("start", "/*if () {", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("comment", " abcd", " "));
},
"test: no auto indent after opening brace in single line comment" : function() {
- assertEquals("", this.mode.getNextLineIndent("//if () {", "start", " "));
- assertEquals(" ", this.mode.getNextLineIndent(" //if () {", "start", " "));
+ assertEquals("", this.mode.getNextLineIndent("start", "//if () {", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", " //if () {", " "));
},
"test: no auto indent should add to existing indent" : function() {
- assertEquals(" ", this.mode.getNextLineIndent(" if () {", "start", " "));
- assertEquals(" ", this.mode.getNextLineIndent(" cde", "start", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", " if () {", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("start", " cde", " "));
},
"test: special indent in doc comments" : function() {
- assertEquals(" * ", this.mode.getNextLineIndent("/**", "doc-start", " "));
- assertEquals(" * ", this.mode.getNextLineIndent(" /**", "doc-start", " "));
- assertEquals(" * ", this.mode.getNextLineIndent(" *", "doc-start", " "));
- assertEquals(" * ", this.mode.getNextLineIndent(" *", "doc-start", " "));
- assertEquals(" ", this.mode.getNextLineIndent(" abc", "doc-start", " "));
+ assertEquals(" * ", this.mode.getNextLineIndent("doc-start", "/**", " "));
+ assertEquals(" * ", this.mode.getNextLineIndent("doc-start", " /**", " "));
+ assertEquals(" * ", this.mode.getNextLineIndent("doc-start", " *", " "));
+ assertEquals(" * ", this.mode.getNextLineIndent("doc-start", " *", " "));
+ assertEquals(" ", this.mode.getNextLineIndent("doc-start", " abc", " "));
},
"test: no indent after doc comments" : function() {
- assertEquals("", this.mode.getNextLineIndent(" */", "doc-start", " "));
+ assertEquals("", this.mode.getNextLineIndent("doc-start", " */", " "));
+ },
+
+ "test: trigger outdent if line is space and new text starts with closing brace" : function() {
+ assertTrue(this.mode.checkOutdent("start", " ", " }"));
+ assertFalse(this.mode.checkOutdent("start", " a ", " }"));
+ assertFalse(this.mode.checkOutdent("start", "", "}"));
+ assertFalse(this.mode.checkOutdent("start", " ", "a }"));
+ assertFalse(this.mode.checkOutdent("start", " }", "}"));
+ },
+
+ "test: auto outdent should indent the line with the same indent as the line with the matching opening brace" : function() {
+ var doc = new ace.Document([" function foo() {", " bla", " }"]);
+ this.mode.autoOutdent("start", doc, 2);
+ assertEquals(" }", doc.getLine(2));
+ },
+
+ "test: no auto outdent if no matching brace is found" : function() {
+ var doc = new ace.Document([" function foo()", " bla", " }"]);
+ this.mode.autoOutdent("start", doc, 2);
+ assertEquals(" }", doc.getLine(2));
}
-// "test: outdent if first non WS character in line is a closing brace" : function() {
-// assertEquals("", this.mode.getNextLineIndent(")", "start", " "));
-// assertEquals(" ", this.mode.getNextLineIndent(" )", "start", " "));
-// }
});
\ No newline at end of file
diff --git a/src/test/mode/TextTest.js b/src/test/mode/TextTest.js
index 2d53207b..4150c796 100644
--- a/src/test/mode/TextTest.js
+++ b/src/test/mode/TextTest.js
@@ -5,19 +5,19 @@ var TextTest = new TestCase("mode.TextTest", {
},
"test: toggle comment lines should not do anything" : function() {
- var doc = new ace.Document([" abc", "cde", "fg"].join("\n"));
+ var doc = new ace.Document([" abc", "cde", "fg"]);
var range = {
start: {row: 0, column: 3},
end: {row: 1, column: 1}
};
- var comment = this.mode.toggleCommentLines(doc, range, "start");
+ var comment = this.mode.toggleCommentLines("start", doc, range);
assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString());
},
"text: lines should not be indented" : function() {
- assertEquals("", this.mode.getNextLineIndent(" abc", " "));
+ assertEquals("", this.mode.getNextLineIndent("start", " abc", " "));
}
});
\ No newline at end of file
diff --git a/src/test/mode/XmlTest.js b/src/test/mode/XmlTest.js
index 0b928c98..df938a67 100644
--- a/src/test/mode/XmlTest.js
+++ b/src/test/mode/XmlTest.js
@@ -14,14 +14,14 @@ var XmlTest = new TestCase("mode.XmlTest", {
},
"test: toggle comment lines should not do anything" : function() {
- var doc = new ace.Document([" abc", "cde", "fg"].join("\n"));
+ var doc = new ace.Document([" abc", "cde", "fg"]);
var range = {
start: {row: 0, column: 3},
end: {row: 1, column: 1}
};
- var comment = this.mode.toggleCommentLines(doc, range, "start");
+ var comment = this.mode.toggleCommentLines("start", doc, range);
assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString());
}
});
\ No newline at end of file