diff --git a/demo/editor.html b/demo/editor.html index c0bdfb1d..385a2b56 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -9,11 +9,24 @@ @@ -39,21 +52,51 @@ + + + + +
+ + +
+
diff --git a/jsTestDriver.conf b/jsTestDriver.conf index 0a1df7c2..48f4c221 100644 --- a/jsTestDriver.conf +++ b/jsTestDriver.conf @@ -6,4 +6,6 @@ load: - src/mode/Text.js - src/mode/*.js - src/*.js - - test/*.js \ No newline at end of file + + - test/*.js + - test/mode/*.js \ No newline at end of file diff --git a/src/BackgroundTokenizer.js b/src/BackgroundTokenizer.js index 4698b31c..161b8b5d 100644 --- a/src/BackgroundTokenizer.js +++ b/src/BackgroundTokenizer.js @@ -47,6 +47,13 @@ ace.BackgroundTokenizer = function(tokenizer, onUpdate, onComplete) { }; }; +ace.BackgroundTokenizer.prototype.setTokenizer = function(tokenizer) { + this.tokenizer = tokenizer; + this.lines = []; + + this.start(0); +}; + ace.BackgroundTokenizer.prototype.setLines = function(textLines) { this.textLines = textLines; this.lines = []; diff --git a/src/Editor.js b/src/Editor.js index af71ed83..6efd21db 100644 --- a/src/Editor.js +++ b/src/Editor.js @@ -4,8 +4,8 @@ ace.Editor = function(renderer, doc, mode) { var container = renderer.getContainerElement(); this.renderer = renderer; - this.setDocument(doc || new ace.TextDocument("")); this.setMode(mode || new ace.mode.Text()); + this.setDocument(doc || new ace.TextDocument("")); this.textInput = new ace.TextInput(container, this); new ace.KeyBinding(container, this); @@ -39,20 +39,23 @@ ace.Editor.prototype.setDocument = function(doc) { this.doc = doc; doc.addChangeListener(ace.bind(this.onDocumentChange, this)); this.renderer.setDocument(doc); + + this.bgTokenizer.setLines(this.doc.lines); }; ace.Editor.prototype.setMode = function(mode) { - // TODO: mode change is not yet supported - if (this.mode) { - throw new Error("TODO: mode change is not yet supported"); - } this.mode = mode; + var tokenizer = mode.getTokenizer(); - this.tokenizer = new ace.BackgroundTokenizer(mode.getTokenizer(), ace.bind(this.onTokenizerUpdate, this)); + if (!this.bgTokenizer) { + var onUpdate = ace.bind(this.onTokenizerUpdate, this); + this.bgTokenizer = new ace.BackgroundTokenizer(tokenizer, onUpdate); + } else { + this.bgTokenizer.setTokenizer(tokenizer); + } - this.tokenizer.setLines(this.doc.lines); - this.renderer.setTokenizer(this.tokenizer); + this.renderer.setTokenizer(this.bgTokenizer); }; ace.Editor.prototype.resize = function() @@ -109,7 +112,7 @@ ace.Editor.prototype.onBlur = function() { }; ace.Editor.prototype.onDocumentChange = function(startRow, endRow) { - this.tokenizer.start(startRow); + this.bgTokenizer.start(startRow); this.renderer.updateLines(startRow, endRow); }; @@ -259,35 +262,30 @@ ace.Editor.prototype.removeLine = function() { }; ace.Editor.prototype.blockIndent = function(indentString) { - if (!this.hasSelection()) { - return; - }; - - var range = this.getSelectionRange(); + if (!this.hasSelection()) return; var indentString = indentString || " "; - this.doc.indentRows(range, indentString); + var addedColumns = this.doc.indentRows(this.getSelectionRange(), indentString); - this.setSelectionAnchor(range.start.row, range.start.column + indentString.length); - this._moveSelection(function() { - this.moveCursorTo(range.end.row, range.end.column + indentString.length); - }); + this.shiftSelection(addedColumns); }; ace.Editor.prototype.blockOutdent = function(indentString) { - if (!this.hasSelection()) { - return; - }; - - var range = this.getSelectionRange(); + if (!this.hasSelection()) return; var indentString = indentString || " "; - var removedColumns = this.doc.outdentRows(range, indentString); + var addedColumns = this.doc.outdentRows(this.getSelectionRange(), indentString); - this.setSelectionAnchor(range.start.row, range.start.column - removedColumns); - this._moveSelection(function() { - this.moveCursorTo(range.end.row, range.end.column - removedColumns); - }); + this.shiftSelection(addedColumns); +}; + +ace.Editor.prototype.toggleCommentLines = function() { + if (!this.hasSelection()) return; + + var selection = this.getSelectionRange(); + var addedColumns = this.mode.toggleCommentLines(this.doc, selection); + + this.shiftSelection(addedColumns); }; ace.Editor.prototype.onCompositionStart = function() { @@ -591,6 +589,40 @@ ace.Editor.prototype.setSelectionAnchor = function(row, column) { this.selectionLead = null; }; +ace.Editor.prototype.getSelectionAnchor = function() { + if (this.selectionAnchor) { + return { + row: this.selectionAnchor.row, + column: this.selectionAnchor.column + }; + } else { + return null; + } +}; + +ace.Editor.prototype.getSelectionLead = function() { + if (this.selectionLead) { + return { + row: this.selectionLead.row, + column: this.selectionLead.column + }; + } else { + return null; + } +}; + +ace.Editor.prototype.shiftSelection = function(columns) { + if (!this.hasSelection()) return; + + var anchor = this.getSelectionAnchor(); + var lead = this.getSelectionLead(); + + this.setSelectionAnchor(anchor.row, anchor.column + columns); + this._moveSelection(function() { + this.moveCursorTo(lead.row, lead.column + columns); + }); +}; + ace.Editor.prototype.getSelectionRange = function() { var anchor = this.selectionAnchor; var lead = this.selectionLead; diff --git a/src/KeyBinding.js b/src/KeyBinding.js index f7dd006c..f53dfddd 100644 --- a/src/KeyBinding.js +++ b/src/KeyBinding.js @@ -16,7 +16,8 @@ var keys = { TAB : 9, A : 65, D: 68, - L: 76 + L: 76, + "7": 55 }; ace.KeyBinding = function(element, host) { @@ -48,6 +49,13 @@ ace.KeyBinding = function(element, host) { } break; + case keys["7"]: + if (e.metaKey) { + host.toggleCommentLines(); + return ace.stopEvent(e); + }; + break; + case keys.UP: if (e.metaKey && e.shiftKey) { host.selectFileStart(); diff --git a/src/TextDocument.js b/src/TextDocument.js index f8d26198..5934b835 100644 --- a/src/TextDocument.js +++ b/src/TextDocument.js @@ -89,6 +89,10 @@ ace.TextDocument.prototype.getTextRange = function(range) { } }; +ace.TextDocument.prototype.getLines = function(firstRow, lastRow) { + return this.lines.slice(firstRow, lastRow+1); +}; + ace.TextDocument.prototype.findMatchingBracket = function(position) { if (position.column == 0) return null; @@ -279,6 +283,7 @@ ace.TextDocument.prototype.indentRows = function(range, indentString) { this.lines[i] = indentString + this.getLine(i); } this.fireChangeEvent(range.start.row, range.end.row); + return indentString.length; }; ace.TextDocument.prototype.outdentRows = function(range, indentString) { @@ -295,5 +300,5 @@ ace.TextDocument.prototype.outdentRows = function(range, indentString) { } this.fireChangeEvent(range.start.row, range.end.row); - return outdentLength; + return -outdentLength; }; \ No newline at end of file diff --git a/src/mode/JavaScript.js b/src/mode/JavaScript.js index 0f6003a6..4dbab043 100644 --- a/src/mode/JavaScript.js +++ b/src/mode/JavaScript.js @@ -5,3 +5,10 @@ ace.mode.JavaScript = function() { }; ace.inherits(ace.mode.JavaScript, ace.mode.Text); +ace.mode.JavaScript.prototype.toggleCommentLines = function(doc, range) { + var addedRows = doc.outdentRows(range, "//"); + if (addedRows == 0) { + var addedRows = doc.indentRows(range, "//"); + }; + return addedRows; +}; \ No newline at end of file diff --git a/src/mode/Text.js b/src/mode/Text.js index c48f4cf1..5b35cfbc 100644 --- a/src/mode/Text.js +++ b/src/mode/Text.js @@ -1,9 +1,19 @@ ace.provide("ace.mode.Text"); ace.mode.Text = function() { - this.$tokenizer = new ace.Tokenizer({}); + var rules = { + "start" : [ { + token : "text", + regex : ".+" + } ] + }; + this.$tokenizer = new ace.Tokenizer(rules); }; ace.mode.Text.prototype.getTokenizer = function() { return this.$tokenizer; +}; + +ace.mode.Text.prototype.toggleCommentLines = function(doc, range) { + return 0; }; \ No newline at end of file diff --git a/src/mode/Xml.js b/src/mode/Xml.js index 23ccc4b1..dbef442c 100644 --- a/src/mode/Xml.js +++ b/src/mode/Xml.js @@ -1,6 +1,6 @@ ace.provide("ace.mode.Xml"); ace.mode.Xml = function() { - this.$tokenizer = new Tokenizer(new ace.mode.XmlHighlightRules().getRules()); + this.$tokenizer = new ace.Tokenizer(new ace.mode.XmlHighlightRules().getRules()); }; ace.inherits(ace.mode.Xml, ace.mode.Text); diff --git a/test/TextEditTest.js b/test/TextEditTest.js index 55f0ea4e..d2ab4796 100644 --- a/test/TextEditTest.js +++ b/test/TextEditTest.js @@ -73,5 +73,39 @@ var TextEditTest = TestCase("TextEditTest", var selection = editor.getSelectionRange(); assertPosition(0, 1, selection.start); assertPosition(2, 1, selection.end); + }, + + "test: comment lines should perserve selection" : function() { + var doc = new ace.TextDocument([" abc", "cde"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc, new ace.mode.JavaScript()); + + editor.moveCursorTo(0, 2); + editor.selectDown(); + + editor.toggleCommentLines(); + + assertEquals(["// abc", "//cde"].join("\n"), doc.toString()); + + var selection = editor.getSelectionRange(); + assertPosition(0, 4, selection.start); + assertPosition(1, 4, selection.end); + }, + + "test: uncomment lines should perserve selection" : function() { + var doc = new ace.TextDocument(["// abc", "//cde"].join("\n")); + var editor = new ace.Editor(new MockRenderer(), doc, new ace.mode.JavaScript()); + + editor.moveCursorTo(0, 1); + editor.selectDown(); + editor.selectRight(); + editor.selectRight(); + + editor.toggleCommentLines(); + + assertEquals([" abc", "cde"].join("\n"), doc.toString()); + + var selection = editor.getSelectionRange(); + assertPosition(0, 0, selection.start); + assertPosition(1, 1, selection.end); } }); \ No newline at end of file diff --git a/test/mode/JavaScriptTest.js b/test/mode/JavaScriptTest.js new file mode 100644 index 00000000..2a0ce050 --- /dev/null +++ b/test/mode/JavaScriptTest.js @@ -0,0 +1,51 @@ +var JavaScriptTest = new TestCase("mode.JavaScriptTest", { + + setUp : function() { + this.mode = new ace.mode.JavaScript(); + }, + + "test: getTokenizer() (smoke test)" : function() { + var tokenizer = this.mode.getTokenizer(); + + assertTrue(tokenizer instanceof ace.Tokenizer); + + var tokens = tokenizer.getLineTokens("'juhu'", "start").tokens; + assertEquals("string", tokens[0].type); + }, + + "test: toggle comment lines should prepend '//' to each line" : function() { + var doc = new ace.TextDocument([" abc", "cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(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.TextDocument(["// abc", "//cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(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.TextDocument(["// abc", "//cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 2, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range); + assertEquals(["//// abc", "////cde", "//fg"].join("\n"), doc.toString()); + } +}); \ No newline at end of file diff --git a/test/mode/XmlTest.js b/test/mode/XmlTest.js new file mode 100644 index 00000000..80e6e733 --- /dev/null +++ b/test/mode/XmlTest.js @@ -0,0 +1,27 @@ +var XmlTest = new TestCase("mode.XmlTest", { + + setUp : function() { + this.mode = new ace.mode.Xml(); + }, + + "test: getTokenizer() (smoke test)" : function() { + var tokenizer = this.mode.getTokenizer(); + + assertTrue(tokenizer instanceof ace.Tokenizer); + + var tokens = tokenizer.getLineTokens("", "start").tokens; + assertEquals("keyword", tokens[1].type); + }, + + "test: toggle comment lines should not do anything" : function() { + var doc = new ace.TextDocument([" abc", "cde", "fg"].join("\n")); + + var range = { + start: {row: 0, column: 3}, + end: {row: 1, column: 1} + }; + + var comment = this.mode.toggleCommentLines(doc, range); + assertEquals([" abc", "cde", "fg"].join("\n"), doc.toString()); + } +}); \ No newline at end of file