diff --git a/ChangeLog.txt b/ChangeLog.txt index e9c41e4c..3077d171 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,5 +1,16 @@ +Version 1.2.0-pre + +* New Features + - Indented soft wrap (danyaPostfactum) + +* API Changes + - unified delta types `{start, end, action, lines}` (Alden Daniels https://github.com/ajaxorg/ace/pull/1745) + - "change" event listeners on session and editor get delta objects directly + 2015.04.03 Version 1.1.9 + - Small Enhancements and Bugfixes + 2014.11.08 Version 1.1.8 * API Changes diff --git a/build b/build index e3ccd2c6..a4e495d8 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit e3ccd2c654cf45ee41ffb09d0e7fa5b40cf91a8f +Subproject commit a4e495d8901876c6bafe3870a35cb8e32c827e97 diff --git a/demo/autoresize.html b/demo/autoresize.html index 73a87599..b0464ecd 100644 --- a/demo/autoresize.html +++ b/demo/autoresize.html @@ -7,14 +7,13 @@ @@ -24,6 +23,8 @@
minHeight = 2 lines
+

+

 
 
@@ -46,6 +47,13 @@ require(["ace/ace"], function(ace) {
     editor2.setOption("maxLines", 30);
     editor2.setOption("minLines", 2);
 
+    var editor = ace.edit("editor3");
+    editor.setOptions({
+        autoScrollEditorIntoView: true,
+        maxLines: 8
+    });
+    editor.renderer.setScrollMargin(10, 10, 10, 10);
+    
     var editor = ace.edit("editor");
     editor.setTheme("ace/theme/tomorrow");
     editor.session.setMode("ace/mode/html");
diff --git a/demo/kitchen-sink/dev_util.js b/demo/kitchen-sink/dev_util.js
index 8dab857c..f466285d 100644
--- a/demo/kitchen-sink/dev_util.js
+++ b/demo/kitchen-sink/dev_util.js
@@ -39,7 +39,7 @@ function warn() {
     s.shift(); // remove the getter
     s = s.join("\n");
     // allow easy access to ace in console, but not in ace code
-    if (!/at Object.InjectedScript.|@debugger eval|snippets:\/{3}/.test(s)) {
+    if (!/at Object.InjectedScript.|@debugger eval|snippets:\/{3}|\(:\d+:\d+\)/.test(s)) {
         console.error("trying to access to global variable");
     }
 }
diff --git a/demo/kitchen-sink/docs/sqlserver.sqlserver b/demo/kitchen-sink/docs/sqlserver.sqlserver
index 329930af..7efd2b7e 100644
--- a/demo/kitchen-sink/docs/sqlserver.sqlserver
+++ b/demo/kitchen-sink/docs/sqlserver.sqlserver
@@ -1,9 +1,9 @@
 -- =============================================
 -- Author:		Morgan Yarbrough
 -- Create date: 4/27/2015
--- Description:	Test Procedure that shows off language features.
--- 				Includes non-standard folding using region comments using either
--- 				line comments or block comments (both are demonstrated below)
+-- Description:	Test procedure that shows off language features.
+-- 				Includes non-standard folding with region comments using either
+-- 				line comments or block comments (both are demonstrated below).
 --				This mode imitates SSMS and it designed to be used with SQL Server theme.
 -- =============================================
 CREATE PROCEDURE dbo.TestProcedure
@@ -11,7 +11,7 @@ CREATE PROCEDURE dbo.TestProcedure
 --#region parameters
 	@vint INT = 1
 	,@vdate DATE = NULL
-	,@vdatetime DATETIME = DATEADD (dd, 1, GETDATE())
+	,@vdatetime DATETIME = DATEADD(dd, 1, GETDATE())
 	,@vvarchar VARCHAR(MAX) = ''
 --#endregion
 
@@ -38,8 +38,8 @@ BEGIN
 	-- another folding demonstration
 	IF @vint = 1 
 	BEGIN
-		SET @vvarchar='one'
-		SET @vint = DATEDIFFT(dd, @vdate, @vdatetime)
+		SET @vvarchar = 'one'
+		SET @vint = DATEDIFF(dd, @vdate, @vdatetime)
 	END
 	
 	-- this mode handles strings properly
@@ -47,7 +47,7 @@ BEGIN
 									FROM Orders
 									WHERE @OrderDate > GETDATE()'
 								
-	-- this mode is aware of build in stored procedures 
+	-- this mode is aware of built in stored procedures 
 	EXECUTE sp_executesql @sql
 	
 	-- demonstrating some syntax highlighting
@@ -60,4 +60,13 @@ BEGIN
 	WHERE CompanyName NOT LIKE '%something'
 		OR CompanyName IS NULL
 		OR CompanyName IN ('bla', 'nothing')
+		
+	-- this mode includes snippets
+	-- place your cusor at the end of the line below and trigger auto complete (Ctrl+Space)
+	createpr
+	
+	-- SQL Server allows using keywords as object names (not recommended) as long as they are wrapped in brackets
+	DATABASE -- keyword
+	[DATABASE] -- not a keyword
+	
 END
diff --git a/demo/static-highlighter/server.js b/demo/static-highlighter/server.js
index 0878fec2..ea8361d4 100644
--- a/demo/static-highlighter/server.js
+++ b/demo/static-highlighter/server.js
@@ -2,15 +2,13 @@
  * Simple node.js server, which generates the synax highlighted version of itself 
  * using the Ace modes and themes on the server and serving a static web page.
  */
-// $'
+
 // include ace search path and modules
 require("amd-loader");
 
-// load jsdom, which is required by Ace
-require("../../lib/ace/test/mockdom");
-
 var http = require("http");
 var fs = require("fs");
+var resolve = require("path").resolve;
 
 // load the highlighter and the desired mode and theme
 var highlighter = require("../../lib/ace/ext/static_highlight");
@@ -20,15 +18,22 @@ var theme = require("../../lib/ace/theme/twilight");
 var port = process.env.PORT || 2222;
 
 http.createServer(function(req, res) {
+    var url = req.url;
+    var path = /[^#?\x00]*/.exec(url)[0];
+    var root = resolve(__dirname + "/../../").replace(/\\/g, "/");
+    path = resolve(root + "/" + path).replace(/\\/g, "/");
+    if (path.indexOf(root + "/") != 0)
+        path = __filename;
     res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
-    fs.readFile(__dirname + "/../../build/src/ace.js", "utf8", function(err, data) {
+    fs.readFile(path, "utf8", function(err, data) {
+        if (err) data = err.message;
         var highlighted = highlighter.render(data, new JavaScriptMode(), theme);
         res.end(
             '\n' +
                 '\n' + 
-                highlighted.html +            
+                highlighted.html +
             ''
         );
     });
diff --git a/experiments/debug_mem_leak.html b/experiments/debug_mem_leak.html
new file mode 100644
index 00000000..b8ee8726
--- /dev/null
+++ b/experiments/debug_mem_leak.html
@@ -0,0 +1,59 @@
+
+
+    
+        
+        
+        
+    
+    
+        

+ +
+ + + \ No newline at end of file diff --git a/lib/ace/anchor.js b/lib/ace/anchor.js index 9f5e159d..39b78e0f 100644 --- a/lib/ace/anchor.js +++ b/lib/ace/anchor.js @@ -99,72 +99,54 @@ var Anchor = exports.Anchor = function(doc, row, column) { * - `value`: An object describing the new Anchor position * **/ - this.onChange = function(e) { - var delta = e.data; - var range = delta.range; - - if (range.start.row == range.end.row && range.start.row != this.row) + this.onChange = function(delta) { + if (delta.start.row == delta.end.row && delta.start.row != this.row) return; - if (range.start.row > this.row) + if (delta.start.row > this.row) return; - - if (range.start.row == this.row && range.start.column > this.column) - return; - - var row = this.row; - var column = this.column; - var start = range.start; - var end = range.end; - - if (delta.action === "insertText") { - if (start.row === row && start.column <= column) { - if (start.column === column && this.$insertRight) { - // do nothing - } else if (start.row === end.row) { - column += end.column - start.column; - } else { - column -= start.column; - row += end.row - start.row; - } - } else if (start.row !== end.row && start.row < row) { - row += end.row - start.row; - } - } else if (delta.action === "insertLines") { - if (start.row === row && column === 0 && this.$insertRight) { - // do nothing - } - else if (start.row <= row) { - row += end.row - start.row; - } - } else if (delta.action === "removeText") { - if (start.row === row && start.column < column) { - if (end.column >= column) - column = start.column; - else - column = Math.max(0, column - (end.column - start.column)); - - } else if (start.row !== end.row && start.row < row) { - if (end.row === row) - column = Math.max(0, column - end.column) + start.column; - row -= (end.row - start.row); - } else if (end.row === row) { - row -= end.row - start.row; - column = Math.max(0, column - end.column) + start.column; - } - } else if (delta.action == "removeLines") { - if (start.row <= row) { - if (end.row <= row) - row -= end.row - start.row; - else { - row = start.row; - column = 0; - } - } - } - - this.setPosition(row, column, true); + + var point = $getTransformedPoint(delta, {row: this.row, column: this.column}, this.$insertRight); + this.setPosition(point.row, point.column, true); }; + + function $pointsInOrder(point1, point2, equalPointsInOrder) { + var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column; + return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter); + } + + function $getTransformedPoint(delta, point, moveIfEqual) { + // Get delta info. + var deltaIsInsert = delta.action == "insert"; + var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row); + var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column); + var deltaStart = delta.start; + var deltaEnd = deltaIsInsert ? deltaStart : delta.end; // Collapse insert range. + + // DELTA AFTER POINT: No change needed. + if ($pointsInOrder(point, deltaStart, moveIfEqual)) { + return { + row: point.row, + column: point.column + }; + } + + // DELTA BEFORE POINT: Move point by delta shift. + if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) { + return { + row: point.row + deltaRowShift, + column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0) + }; + } + + // DELTA ENVELOPS POINT (delete only): Move point to delta start. + // TODO warn if delta.action != "remove" ? + + return { + row: deltaStart.row, + column: deltaStart.column + }; + } /** * Sets the anchor position to the specified row and column. If `noClip` is `true`, the position is not clipped. diff --git a/lib/ace/anchor_test.js b/lib/ace/anchor_test.js index b5c62941..c0b97273 100644 --- a/lib/ace/anchor_test.js +++ b/lib/ace/anchor_test.js @@ -71,7 +71,7 @@ module.exports = { var doc = new Document("juhu\nkinners"); var anchor = new Anchor(doc, 1, 4); - doc.insertLines(1, ["123", "456"]); + doc.insertFullLines(1, ["123", "456"]); assert.position(anchor.getPosition(), 3, 4); }, @@ -105,7 +105,7 @@ module.exports = { var doc = new Document("juhu\nkinners"); var anchor = new Anchor(doc, 1, 4); - doc.insertNewLine({row: 0, column: 0}); + doc.insertMergedLines({row: 0, column: 0}, ['', '']); assert.position(anchor.getPosition(), 2, 4); }, @@ -113,7 +113,7 @@ module.exports = { var doc = new Document("juhu\nkinners"); var anchor = new Anchor(doc, 1, 4); - doc.insertNewLine({row: 1, column: 2}); + doc.insertMergedLines({row: 1, column: 2}, ['', '']); assert.position(anchor.getPosition(), 2, 2); }, @@ -145,7 +145,7 @@ module.exports = { var doc = new Document("juhu\n1\n2\nkinners"); var anchor = new Anchor(doc, 3, 4); - doc.removeLines(1, 2); + doc.removeFullLines(1, 2); assert.position(anchor.getPosition(), 1, 4); }, @@ -169,7 +169,7 @@ module.exports = { var doc = new Document("juhu\nkinners\n123"); var anchor = new Anchor(doc, 1, 5); - doc.removeLines(1, 1); + doc.removeFullLines(1, 1); assert.position(anchor.getPosition(), 1, 0); }, @@ -208,9 +208,9 @@ module.exports = { var doc = new Document("juhu\nkinners\n123"); var anchor = new Anchor(doc, 2, 4); - doc.removeLines(0, 3); + doc.removeFullLines(0, 3); assert.position(anchor.getPosition(), 0, 0); - doc.insertLines(0, ["a", "b", "c"]); + doc.insertFullLines(0, ["a", "b", "c"]); assert.position(anchor.getPosition(), 3, 0); assert.equal(doc.getValue(), "a\nb\nc\n"); } diff --git a/lib/ace/apply_delta.js b/lib/ace/apply_delta.js new file mode 100644 index 00000000..98bfe148 --- /dev/null +++ b/lib/ace/apply_delta.js @@ -0,0 +1,108 @@ +/* ***** 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"; + +function throwDeltaError(delta, errorText){ + console.log("Invalid Delta:", delta); + throw "Invalid Delta: " + errorText; +} + +function positionInDocument(docLines, position) { + return position.row >= 0 && position.row < docLines.length && + position.column >= 0 && position.column <= docLines[position.row].length; +} + +function validateDelta(docLines, delta) { + // Validate action string. + if (delta.action != "insert" && delta.action != "remove") + throwDeltaError(delta, "delta.action must be 'insert' or 'remove'"); + + // Validate lines type. + if (!(delta.lines instanceof Array)) + throwDeltaError(delta, "delta.lines must be an Array"); + + // Validate range type. + if (!delta.start || !delta.end) + throwDeltaError(delta, "delta.start/end must be an present"); + + // Validate that the start point is contained in the document. + var start = delta.start; + if (!positionInDocument(docLines, delta.start)) + throwDeltaError(delta, "delta.start must be contained in document"); + + // Validate that the end point is contained in the document (remove deltas only). + var end = delta.end; + if (delta.action == "remove" && !positionInDocument(docLines, end)) + throwDeltaError(delta, "delta.end must contained in document for 'remove' actions"); + + // Validate that the .range size matches the .lines size. + var numRangeRows = end.row - start.row; + var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0)); + if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars) + throwDeltaError(delta, "delta.range must match delta lines"); +} + +exports.applyDelta = function(docLines, delta, doNotValidate) { + // disabled validation since it breaks autocompletion popup + // if (!doNotValidate) + // validateDelta(docLines, delta); + + var row = delta.start.row; + var startColumn = delta.start.column; + var line = docLines[row] || ""; + switch (delta.action) { + case "insert": + var lines = delta.lines; + if (lines.length === 1) { + docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn); + } else { + var args = [row, 1].concat(delta.lines); + docLines.splice.apply(docLines, args); + docLines[row] = line.substring(0, startColumn) + docLines[row]; + docLines[row + delta.lines.length - 1] += line.substring(startColumn); + } + break; + case "remove": + var endColumn = delta.end.column; + var endRow = delta.end.row; + if (row === endRow) { + docLines[row] = line.substring(0, startColumn) + line.substring(endColumn); + } else { + docLines.splice( + row, endRow - row + 1, + line.substring(0, startColumn) + docLines[endRow].substring(endColumn) + ); + } + break; + } +} +}); diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index 7e5674ae..8e121f35 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -100,7 +100,7 @@ var Autocomplete = function() { var rect = editor.container.getBoundingClientRect(); pos.top += rect.top - renderer.layerConfig.offset; pos.left += rect.left - editor.renderer.scrollLeft; - pos.left += renderer.$gutterLayer.gutterWidth; + pos.left += renderer.gutterWidth; this.popup.show(pos, lineHeight); } else if (keepPopupPosition && !prefix) { @@ -142,10 +142,11 @@ var Autocomplete = function() { // we have to check if activeElement is a child of popup because // on IE preventDefault doesn't stop scrollbar from being focussed var el = document.activeElement; - var text = this.editor.textInput.getElement() - if (el != text && ( !this.popup || el.parentNode != this.popup.container ) - && el != this.tooltipNode && e.relatedTarget != this.tooltipNode - && e.relatedTarget != text + var text = this.editor.textInput.getElement(); + var fromTooltip = e.relatedTarget && e.relatedTarget == this.tooltipNode; + var container = this.popup && this.popup.container; + if (el != text && el.parentNode != container && !fromTooltip + && el != this.tooltipNode && e.relatedTarget != text ) { this.detach(); } @@ -349,7 +350,7 @@ var Autocomplete = function() { doc = selected; if (typeof doc == "string") - doc = {docText: doc} + doc = {docText: doc}; if (!doc || !(doc.docHTML || doc.docText)) return this.hideDocTooltip(); this.showDocTooltip(doc); @@ -416,7 +417,7 @@ Autocomplete.startCommand = { bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space" }; -var FilteredList = function(array, filterText, mutateData) { +var FilteredList = function(array, filterText) { this.all = array; this.filtered = array; this.filterText = filterText || ""; diff --git a/lib/ace/autocomplete/popup.js b/lib/ace/autocomplete/popup.js index a151c746..6c480b38 100644 --- a/lib/ace/autocomplete/popup.js +++ b/lib/ace/autocomplete/popup.js @@ -31,7 +31,6 @@ define(function(require, exports, module) { "use strict"; -var EditSession = require("../edit_session").EditSession; var Renderer = require("../virtual_renderer").VirtualRenderer; var Editor = require("../editor").Editor; var Range = require("../range").Range; @@ -221,8 +220,8 @@ var AcePopup = function(parentNode) { popup.data = []; popup.setData = function(list) { - popup.data = list || []; popup.setValue(lang.stringRepeat("\n", list.length), -1); + popup.data = list || []; popup.setRow(0); }; popup.getData = function(row) { diff --git a/lib/ace/background_tokenizer.js b/lib/ace/background_tokenizer.js index da8008bd..ea4ddd0f 100644 --- a/lib/ace/background_tokenizer.js +++ b/lib/ace/background_tokenizer.js @@ -68,11 +68,10 @@ var BackgroundTokenizer = function(tokenizer, editor) { var endLine = -1; var doc = self.doc; + var startLine = currentLine; while (self.lines[currentLine]) currentLine++; - - var startLine = currentLine; - + var len = doc.getLength(); var processedLines = 0; self.running = false; @@ -172,13 +171,12 @@ var BackgroundTokenizer = function(tokenizer, editor) { } this.$updateOnChange = function(delta) { - var range = delta.range; - var startRow = range.start.row; - var len = range.end.row - startRow; + var startRow = delta.start.row; + var len = delta.end.row - startRow; if (len === 0) { this.lines[startRow] = null; - } else if (delta.action == "removeText" || delta.action == "removeLines") { + } else if (delta.action == "remove") { this.lines.splice(startRow, len + 1, null); this.states.splice(startRow, len + 1, null); } else { diff --git a/lib/ace/background_tokenizer_test.js b/lib/ace/background_tokenizer_test.js index 7a4cc78c..fd738d06 100644 --- a/lib/ace/background_tokenizer_test.js +++ b/lib/ace/background_tokenizer_test.js @@ -75,6 +75,49 @@ module.exports = { forceTokenize(doc) testStates(doc, ["comment_regex_allowed", "start", "no_regex"]) + }, + "test background tokenizer sends update event" : function() { + var doc = new EditSession([ + "/*", + "var", + "juhu", + "*/" + ]); + doc.setMode("./mode/javascript"); + + var updateEvent = null; + doc.bgTokenizer.on("update", function(e) { + updateEvent = e.data; + }); + function checkEvent(first, last) { + assert.ok(!updateEvent, "unneccessary update event"); + doc.bgTokenizer.running = 1; + doc.bgTokenizer.$worker(); + assert.ok(updateEvent); + assert.equal([first, last] + "", + [updateEvent.first, updateEvent.last] + "") + updateEvent = null; + } + + forceTokenize(doc); + var comment = "comment_regex_allowed"; + testStates(doc, [comment, comment, comment, "start"]); + + doc.remove(new Range(0,0,0,2)); + testStates(doc, [comment, comment, comment, "start"]); + + checkEvent(0, 3); + testStates(doc, ["start", "no_regex", "no_regex", "regex"]); + + // insert /* and and press down several times quickly + doc.insert({row:0, column:0}, "/*"); + doc.getTokens(0); + doc.getTokens(1); + doc.getTokens(2); + checkEvent(0, 3); + + forceTokenize(doc); + testStates(doc, [comment, comment, comment, "start"]); } }; diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index f1e267c1..de14df85 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -423,6 +423,12 @@ exports.commands = [{ exec: function() {}, passEvent: true, readOnly: true +}, { + name: "copy", + exec: function(editor) { + // placeholder for replay macro + }, + readOnly: true }, // commands disabled in readOnly mode @@ -439,6 +445,12 @@ exports.commands = [{ }, scrollIntoView: "cursor", multiSelectAction: "forEach" +}, { + name: "paste", + exec: function(editor, args) { + editor.$handlePaste(args); + }, + scrollIntoView: "cursor" }, { name: "removeline", bindKey: bindKey("Ctrl-D", "Command-D"), diff --git a/lib/ace/commands/incremental_search_commands.js b/lib/ace/commands/incremental_search_commands.js index a0a0fe5f..59083a85 100644 --- a/lib/ace/commands/incremental_search_commands.js +++ b/lib/ace/commands/incremental_search_commands.js @@ -69,18 +69,14 @@ exports.iSearchCommands = [{ bindKey: {win: "Ctrl-F", mac: "Command-F"}, exec: function(iSearch) { iSearch.cancelSearch(true); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: "searchForward", bindKey: {win: "Ctrl-S|Ctrl-K", mac: "Ctrl-S|Command-G"}, exec: function(iSearch, options) { options.useCurrentOrPrevSearch = true; iSearch.next(options); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: "searchBackward", bindKey: {win: "Ctrl-R|Ctrl-Shift-K", mac: "Ctrl-R|Command-Shift-G"}, @@ -88,42 +84,30 @@ exports.iSearchCommands = [{ options.useCurrentOrPrevSearch = true; options.backwards = true; iSearch.next(options); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: "extendSearchTerm", exec: function(iSearch, string) { iSearch.addString(string); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: "extendSearchTermSpace", bindKey: "space", - exec: function(iSearch) { iSearch.addString(' '); }, - readOnly: true, - isIncrementalSearchCommand: true + exec: function(iSearch) { iSearch.addString(' '); } }, { name: "shrinkSearchTerm", bindKey: "backspace", exec: function(iSearch) { iSearch.removeChar(); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: 'confirmSearch', bindKey: 'return', - exec: function(iSearch) { iSearch.deactivate(); }, - readOnly: true, - isIncrementalSearchCommand: true + exec: function(iSearch) { iSearch.deactivate(); } }, { name: 'cancelSearch', bindKey: 'esc|Ctrl-G', - exec: function(iSearch) { iSearch.deactivate(true); }, - readOnly: true, - isIncrementalSearchCommand: true + exec: function(iSearch) { iSearch.deactivate(true); } }, { name: 'occurisearch', bindKey: 'Ctrl-O', @@ -131,9 +115,7 @@ exports.iSearchCommands = [{ var options = oop.mixin({}, iSearch.$options); iSearch.deactivate(); occurStartCommand.exec(iSearch.$editor, options); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: "yankNextWord", bindKey: "Ctrl-w", @@ -142,9 +124,7 @@ exports.iSearchCommands = [{ range = ed.selection.getRangeOfMovements(function(sel) { sel.moveCursorWordRight(); }), string = ed.session.getTextRange(range); iSearch.addString(string); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: "yankNextChar", bindKey: "Ctrl-Alt-y", @@ -153,15 +133,11 @@ exports.iSearchCommands = [{ range = ed.selection.getRangeOfMovements(function(sel) { sel.moveCursorRight(); }), string = ed.session.getTextRange(range); iSearch.addString(string); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: 'recenterTopBottom', bindKey: 'Ctrl-l', - exec: function(iSearch) { iSearch.$editor.execCommand('recenterTopBottom'); }, - readOnly: true, - isIncrementalSearchCommand: true + exec: function(iSearch) { iSearch.$editor.execCommand('recenterTopBottom'); } }, { name: 'selectAllMatches', bindKey: 'Ctrl-space', @@ -173,18 +149,19 @@ exports.iSearchCommands = [{ return ranges.concat(ea ? ea : []); }, []) : []; iSearch.deactivate(false); ranges.forEach(ed.selection.addRange.bind(ed.selection)); - }, - readOnly: true, - isIncrementalSearchCommand: true + } }, { name: 'searchAsRegExp', bindKey: 'Alt-r', exec: function(iSearch) { iSearch.convertNeedleToRegExp(); - }, - readOnly: true, - isIncrementalSearchCommand: true -}]; + } +}].map(function(cmd) { + cmd.readOnly = true; + cmd.isIncrementalSearchCommand = true; + cmd.scrollIntoView = "animate-cursor"; + return cmd; +}); function IncrementalSearchKeyboardHandler(iSearch) { this.$iSearch = iSearch; @@ -192,7 +169,7 @@ function IncrementalSearchKeyboardHandler(iSearch) { oop.inherits(IncrementalSearchKeyboardHandler, HashHandler); -;(function() { +(function() { this.attach = function(editor) { var iSearch = this.$iSearch; @@ -201,15 +178,19 @@ oop.inherits(IncrementalSearchKeyboardHandler, HashHandler); if (!e.command.isIncrementalSearchCommand) return undefined; e.stopPropagation(); e.preventDefault(); - return e.command.exec(iSearch, e.args || {}); + var scrollTop = editor.session.getScrollTop(); + var result = e.command.exec(iSearch, e.args || {}); + editor.renderer.scrollCursorIntoView(null, 0.5); + editor.renderer.animateScrolling(scrollTop); + return result; }); - } + }; this.detach = function(editor) { if (!this.$commandExecHandler) return; editor.commands.removeEventListener('exec', this.$commandExecHandler); delete this.$commandExecHandler; - } + }; var handleKeyboard$super = this.handleKeyboard; this.handleKeyboard = function(data, hashId, key, keyCode) { @@ -222,7 +203,7 @@ oop.inherits(IncrementalSearchKeyboardHandler, HashHandler); if (extendCmd) { return {command: extendCmd, args: key}; } } return {command: "null", passEvent: hashId == 0 || hashId == 4}; - } + }; }).call(IncrementalSearchKeyboardHandler.prototype); diff --git a/lib/ace/config.js b/lib/ace/config.js index 34bf50fd..fe9312fd 100644 --- a/lib/ace/config.js +++ b/lib/ace/config.js @@ -39,7 +39,7 @@ var AppConfig = require("./lib/app_config").AppConfig; module.exports = exports = new AppConfig(); var global = (function() { - return this; + return this || typeof window != "undefined" && window; })(); var options = { diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index cef995c8..87eb67b3 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -422,3 +422,30 @@ position: absolute; z-index: 8; } + +/* +styles = [] +for (var i = 1; i < 16; i++) { + styles.push(".ace_br" + i + "{" + ( + ["top-left", "top-right", "bottom-right", "bottom-left"] + ).map(function(x, j) { + return i & (1<= length) { - position.row = Math.max(0, length - 1); - position.column = this.getLine(length-1).length; - } else if (position.row < 0) - position.row = 0; - return position; + // Deprecated methods retained for backwards compatibility. + this.insertLines = function(row, lines) { + console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."); + return this.insertFullLines(row, lines); + }; + this.removeLines = function(firstRow, lastRow) { + console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."); + return this.removeFullLines(firstRow, lastRow); + }; + this.insertNewLine = function(position) { + console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, [\'\', \'\']) instead."); + return this.insertMergedLines(position, ["", ""]); }; /** @@ -243,53 +261,97 @@ var Document = function(text) { * **/ this.insert = function(position, text) { - if (!text || text.length === 0) - return position; - - position = this.$clipPosition(position); - - // only detect new lines if the document has no line break yet + // Only detect new lines if the document has no line break yet. if (this.getLength() <= 1) this.$detectNewLine(text); - - var lines = this.$split(text); - var firstLine = lines.splice(0, 1)[0]; - var lastLine = lines.length == 0 ? null : lines.splice(lines.length - 1, 1)[0]; - - position = this.insertInLine(position, firstLine); - if (lastLine !== null) { - position = this.insertNewLine(position); // terminate first line - position = this._insertLines(position.row, lines); - position = this.insertInLine(position, lastLine || ""); + + return this.insertMergedLines(position, this.$split(text)); + }; + + /** + * Inserts `text` into the `position` at the current row. This method also triggers the `"change"` event. + * + * This differs from the `insert` method in two ways: + * 1. This does NOT handle newline characters (single-line text only). + * 2. This is faster than the `insert` method for single-line text insertions. + * + * @param {Object} position The position to insert at; it's an object that looks like `{ row: row, column: column}` + * @param {String} text A chunk of text + * @returns {Object} Returns an object containing the final row and column, like this: + * ``` + * {row: endRow, column: 0} + * ``` + **/ + this.insertInLine = function(position, text) { + var start = this.clippedPos(position.row, position.column); + var end = this.pos(position.row, position.column + text.length); + + this.applyDelta({ + start: start, + end: end, + action: "insert", + lines: [text] + }, true); + + return this.clonePos(end); + }; + + this.clippedPos = function(row, column) { + var length = this.getLength(); + if (row === undefined) { + row = length; + } else if (row < 0) { + row = 0; + } else if (row >= length) { + row = length - 1; + column = undefined; + } + var line = this.getLine(row); + if (column == undefined) + column = line.length; + column = Math.min(Math.max(column, 0), line.length); + return {row: row, column: column}; + }; + + this.clonePos = function(pos) { + return {row: pos.row, column: pos.column}; + }; + + this.pos = function(row, column) { + return {row: row, column: column}; + }; + + this.$clipPosition = function(position) { + var length = this.getLength(); + if (position.row >= length) { + position.row = Math.max(0, length - 1); + position.column = this.getLine(length - 1).length; + } else { + position.row = Math.max(0, position.row); + position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length); } return position; }; /** - * Fires whenever the document changes. - * - * Several methods trigger different `"change"` events. Below is a list of each action type, followed by each property that's also available: - * - * * `"insertLines"` (emitted by [[Document.insertLines]]) - * * `range`: the [[Range]] of the change within the document - * * `lines`: the lines in the document that are changing - * * `"insertText"` (emitted by [[Document.insertNewLine]]) - * * `range`: the [[Range]] of the change within the document - * * `text`: the text that's being added - * * `"removeLines"` (emitted by [[Document.insertLines]]) - * * `range`: the [[Range]] of the change within the document - * * `lines`: the lines in the document that were removed - * * `nl`: the new line character (as defined by [[Document.getNewLineCharacter]]) - * * `"removeText"` (emitted by [[Document.removeInLine]] and [[Document.removeNewLine]]) - * * `range`: the [[Range]] of the change within the document - * * `text`: the text that's being removed - * - * @event change - * @param {Object} e Contains at least one property called `"action"`. `"action"` indicates the action that triggered the change. Each action also has a set of additional properties. - * - **/ + * Fires whenever the document changes. + * + * Several methods trigger different `"change"` events. Below is a list of each action type, followed by each property that's also available: + * + * * `"insert"` + * * `range`: the [[Range]] of the change within the document + * * `lines`: the lines being added + * * `"remove"` + * * `range`: the [[Range]] of the change within the document + * * `lines`: the lines being removed + * + * @event change + * @param {Object} e Contains at least one property called `"action"`. `"action"` indicates the action that triggered the change. Each action also has a set of additional properties. + * + **/ + /** - * Inserts the elements in `lines` into the document, starting at the row index given by `row`. This method also triggers the `'change'` event. + * Inserts the elements in `lines` into the document as full lines (does not merge with existing line), starting at the row index given by `row`. This method also triggers the `"change"` event. * @param {Number} row The index of the row to insert at * @param {Array} lines An array of strings * @returns {Object} Contains the final row and column, like this: @@ -302,101 +364,57 @@ var Document = function(text) { * ``` * **/ - this.insertLines = function(row, lines) { - if (row >= this.getLength()) - return this.insert({row: row, column: 0}, "\n" + lines.join("\n")); - return this._insertLines(Math.max(row, 0), lines); - }; - this._insertLines = function(row, lines) { - if (lines.length == 0) - return {row: row, column: 0}; - - // Apply doesn't work for big arrays due to max call stack detection. - // Chrome 64 bit requires threshold smaller than 32k, using a safe value of 20k. - // To circumvent that we have to break huge inserts into smaller chunks here. - while (lines.length > 20000) { - var end = this._insertLines(row, lines.slice(0, 20000)); - lines = lines.slice(20000); - row = end.row; + this.insertFullLines = function(row, lines) { + // Clip to document. + // Allow one past the document end. + row = Math.min(Math.max(row, 0), this.getLength()); + + // Calculate insertion point. + var column = 0; + if (row < this.getLength()) { + // Insert before the specified row. + lines = lines.concat([""]); + column = 0; + } else { + // Insert after the last row in the document. + lines = [""].concat(lines); + row--; + column = this.$lines[row].length; } + + // Insert. + this.insertMergedLines({row: row, column: column}, lines); + }; - var args = [row, 0]; - args.push.apply(args, lines); - this.$lines.splice.apply(this.$lines, args); - - var range = new Range(row, 0, row + lines.length, 0); - var delta = { - action: "insertLines", - range: range, + /** + * Inserts the elements in `lines` into the document, starting at the position index given by `row`. This method also triggers the `"change"` event. + * @param {Number} row The index of the row to insert at + * @param {Array} lines An array of strings + * @returns {Object} Contains the final row and column, like this: + * ``` + * {row: endRow, column: 0} + * ``` + * If `lines` is empty, this function returns an object containing the current row, and column, like this: + * ``` + * {row: row, column: 0} + * ``` + * + **/ + this.insertMergedLines = function(position, lines) { + var start = this.clippedPos(position.row, position.column); + var end = { + row: start.row + lines.length - 1, + column: (lines.length == 1 ? start.column : 0) + lines[lines.length - 1].length + }; + + this.applyDelta({ + start: start, + end: end, + action: "insert", lines: lines - }; - this._signal("change", { data: delta }); - return range.end; - }; - - /** - * Inserts a new line into the document at the current row's `position`. This method also triggers the `'change'` event. - * @param {Object} position The position to insert at - * @returns {Object} Returns an object containing the final row and column, like this:
- * ``` - * {row: endRow, column: 0} - * ``` - * - **/ - this.insertNewLine = function(position) { - position = this.$clipPosition(position); - var line = this.$lines[position.row] || ""; - - this.$lines[position.row] = line.substring(0, position.column); - this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length)); - - var end = { - row : position.row + 1, - column : 0 - }; - - var delta = { - action: "insertText", - range: Range.fromPoints(position, end), - text: this.getNewLineCharacter() - }; - this._signal("change", { data: delta }); - - return end; - }; - - /** - * Inserts `text` into the `position` at the current row. This method also triggers the `'change'` event. - * @param {Object} position The position to insert at; it's an object that looks like `{ row: row, column: column}` - * @param {String} text A chunk of text - * @returns {Object} Returns an object containing the final row and column, like this: - * ``` - * {row: endRow, column: 0} - * ``` - * - **/ - this.insertInLine = function(position, text) { - if (text.length == 0) - return position; - - var line = this.$lines[position.row] || ""; - - this.$lines[position.row] = line.substring(0, position.column) + text - + line.substring(position.column); - - var end = { - row : position.row, - column : position.column + text.length - }; - - var delta = { - action: "insertText", - range: Range.fromPoints(position, end), - text: text - }; - this._signal("change", { data: delta }); - - return end; + }); + + return this.clonePos(end); }; /** @@ -406,113 +424,90 @@ var Document = function(text) { * **/ this.remove = function(range) { - if (!(range instanceof Range)) - range = Range.fromPoints(range.start, range.end); - // clip to document - range.start = this.$clipPosition(range.start); - range.end = this.$clipPosition(range.end); - - if (range.isEmpty()) - return range.start; - - var firstRow = range.start.row; - var lastRow = range.end.row; - - if (range.isMultiLine()) { - var firstFullRow = range.start.column == 0 ? firstRow : firstRow + 1; - var lastFullRow = lastRow - 1; - - if (range.end.column > 0) - this.removeInLine(lastRow, 0, range.end.column); - - if (lastFullRow >= firstFullRow) - this._removeLines(firstFullRow, lastFullRow); - - if (firstFullRow != firstRow) { - this.removeInLine(firstRow, range.start.column, this.getLine(firstRow).length); - this.removeNewLine(range.start.row); - } - } - else { - this.removeInLine(firstRow, range.start.column, range.end.column); - } - return range.start; + var start = this.clippedPos(range.start.row, range.start.column); + var end = this.clippedPos(range.end.row, range.end.column); + this.applyDelta({ + start: start, + end: end, + action: "remove", + lines: this.getLinesForRange({start: start, end: end}) + }); + return this.clonePos(start); }; /** - * Removes the specified columns from the `row`. This method also triggers the `'change'` event. - * @param {Number} row The row to remove from - * @param {Number} startColumn The column to start removing at - * @param {Number} endColumn The column to stop removing at - * @returns {Object} Returns an object containing `startRow` and `startColumn`, indicating the new row and column values.
If `startColumn` is equal to `endColumn`, this function returns nothing. - * - **/ + * Removes the specified columns from the `row`. This method also triggers a `"change"` event. + * @param {Number} row The row to remove from + * @param {Number} startColumn The column to start removing at + * @param {Number} endColumn The column to stop removing at + * @returns {Object} Returns an object containing `startRow` and `startColumn`, indicating the new row and column values.
If `startColumn` is equal to `endColumn`, this function returns nothing. + * + **/ this.removeInLine = function(row, startColumn, endColumn) { - if (startColumn == endColumn) - return; - - var range = new Range(row, startColumn, row, endColumn); - var line = this.getLine(row); - var removed = line.substring(startColumn, endColumn); - var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length); - this.$lines.splice(row, 1, newLine); - - var delta = { - action: "removeText", - range: range, - text: removed - }; - this._signal("change", { data: delta }); - return range.start; + var start = this.clippedPos(row, startColumn); + var end = this.clippedPos(row, endColumn); + + this.applyDelta({ + start: start, + end: end, + action: "remove", + lines: this.getLinesForRange({start: start, end: end}) + }, true); + + return this.clonePos(start); }; /** - * Removes a range of full lines. This method also triggers the `'change'` event. + * Removes a range of full lines. This method also triggers the `"change"` event. * @param {Number} firstRow The first row to be removed * @param {Number} lastRow The last row to be removed * @returns {[String]} Returns all the removed lines. * **/ - this.removeLines = function(firstRow, lastRow) { - if (firstRow < 0 || lastRow >= this.getLength()) - return this.remove(new Range(firstRow, 0, lastRow + 1, 0)); - return this._removeLines(firstRow, lastRow); - }; - - this._removeLines = function(firstRow, lastRow) { - var range = new Range(firstRow, 0, lastRow + 1, 0); - var removed = this.$lines.splice(firstRow, lastRow - firstRow + 1); - - var delta = { - action: "removeLines", - range: range, - nl: this.getNewLineCharacter(), - lines: removed - }; - this._signal("change", { data: delta }); - return removed; + this.removeFullLines = function(firstRow, lastRow) { + // Clip to document. + firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1); + lastRow = Math.min(Math.max(0, lastRow ), this.getLength() - 1); + + // Calculate deletion range. + // Delete the ending new line unless we're at the end of the document. + // If we're at the end of the document, delete the starting new line. + var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0; + var deleteLastNewLine = lastRow < this.getLength() - 1; + var startRow = ( deleteFirstNewLine ? firstRow - 1 : firstRow ); + var startCol = ( deleteFirstNewLine ? this.getLine(startRow).length : 0 ); + var endRow = ( deleteLastNewLine ? lastRow + 1 : lastRow ); + var endCol = ( deleteLastNewLine ? 0 : this.getLine(endRow).length ); + var range = new Range(startRow, startCol, endRow, endCol); + + // Store delelted lines with bounding newlines ommitted (maintains previous behavior). + var deletedLines = this.$lines.slice(firstRow, lastRow + 1); + + this.applyDelta({ + start: range.start, + end: range.end, + action: "remove", + lines: this.getLinesForRange(range) + }); + + // Return the deleted lines. + return deletedLines; }; /** - * Removes the new line between `row` and the row immediately following it. This method also triggers the `'change'` event. + * Removes the new line between `row` and the row immediately following it. This method also triggers the `"change"` event. * @param {Number} row The row to check * **/ this.removeNewLine = function(row) { - var firstLine = this.getLine(row); - var secondLine = this.getLine(row+1); - - var range = new Range(row, firstLine.length, row+1, 0); - var line = firstLine + secondLine; - - this.$lines.splice(row, 2, line); - - var delta = { - action: "removeText", - range: range, - text: this.getNewLineCharacter() - }; - this._signal("change", { data: delta }); + if (row < this.getLength() - 1 && row >= 0) { + this.applyDelta({ + start: this.pos(row, this.getLine(row).length), + end: this.pos(row + 1, 0), + action: "remove", + lines: ["", ""] + }); + } }; /** @@ -526,9 +521,9 @@ var Document = function(text) { * **/ this.replace = function(range, text) { - if (!(range instanceof Range)) + if (!range instanceof Range) range = Range.fromPoints(range.start, range.end); - if (text.length == 0 && range.isEmpty()) + if (text.length === 0 && range.isEmpty()) return range.start; // Shortcut: If the text we want to insert is the same as it is already @@ -537,55 +532,106 @@ var Document = function(text) { return range.end; this.remove(range); + var end; if (text) { - var end = this.insert(range.start, text); + end = this.insert(range.start, text); } else { end = range.start; } - + return end; }; /** - * Applies all the changes previously accumulated. These can be either `'insertText'`, `'insertLines'`, `'removeText'`, and `'removeLines'`. + * Applies all changes in `deltas` to the document. + * @param {Array} deltas An array of delta objects (can include "insert" and "remove" actions) **/ this.applyDeltas = function(deltas) { for (var i=0; i=0; i--) { - var delta = deltas[i]; - - var range = Range.fromPoints(delta.range.start, delta.range.end); - - if (delta.action == "insertLines") - this._removeLines(range.start.row, range.end.row - 1); - else if (delta.action == "insertText") - this.remove(range); - else if (delta.action == "removeLines") - this._insertLines(range.start.row, delta.lines); - else if (delta.action == "removeText") - this.insert(range.start, delta.text); + this.revertDelta(deltas[i]); } }; - + + /** + * Applies `delta` to the document. + * @param {Object} delta A delta object (can include "insert" and "remove" actions) + **/ + this.applyDelta = function(delta, doNotValidate) { + var isInsert = delta.action == "insert"; + // An empty range is a NOOP. + if (isInsert ? delta.lines.length <= 1 && !delta.lines[0] + : !Range.comparePoints(delta.start, delta.end)) { + return; + } + + if (isInsert && delta.lines.length > 20000) + this.$splitAndapplyLargeDelta(delta, 20000); + + // Apply. + applyDelta(this.$lines, delta, doNotValidate); + this._signal("change", delta); + }; + + this.$splitAndapplyLargeDelta = function(delta, MAX) { + // Split large insert deltas. This is necessary because: + // 1. We need to support splicing delta lines into the document via $lines.splice.apply(...) + // 2. fn.apply() doesn't work for a large number of params. The smallest threshold is on chrome 40 ~42000. + // we use 20000 to leave some space for actual stack + // + // To Do: Ideally we'd be consistent and also split 'delete' deltas. We don't do this now, because delete + // delta handling is too slow. If we make delete delta handling faster we can split all large deltas + // as shown in https://gist.github.com/aldendaniels/8367109#file-document-snippet-js + // If we do this, update validateDelta() to limit the number of lines in a delete delta. + var lines = delta.lines; + var l = lines.length; + var row = delta.start.row; + var column = delta.start.column; + var from = 0, to = 0; + do { + from = to; + to += MAX - 1; + var chunk = lines.slice(from, to); + if (to > l) { + // Update remaining delta. + delta.lines = chunk; + delta.start.row = row + from; + delta.start.column = column; + break; + } + chunk.push(""); + this.applyDelta({ + start: this.pos(row + from, column), + end: this.pos(row + to, column = 0), + action: delta.action, + lines: chunk + }, true); + } while(true); + }; + + /** + * Reverts `delta` from the document. + * @param {Object} delta A delta object (can include "insert" and "remove" actions) + **/ + this.revertDelta = function(delta) { + this.applyDelta({ + start: this.clonePos(delta.start), + end: this.clonePos(delta.end), + action: (delta.action == "insert" ? "remove" : "insert"), + lines: delta.lines.slice() + }); + }; + /** * Converts an index position in a document to a `{row, column}` object. * diff --git a/lib/ace/document_test.js b/lib/ace/document_test.js index 051434b4..ada56cd2 100644 --- a/lib/ace/document_test.js +++ b/lib/ace/document_test.js @@ -46,7 +46,7 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); doc.insert({row: 0, column: 1}, "juhu"); assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n")); @@ -63,9 +63,9 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); - doc.insertNewLine({row: 0, column: 1}); + doc.insertMergedLines({row: 0, column: 1}, ['', '']); assert.equal(doc.getValue(), ["1", "2", "34"].join("\n")); var d = deltas.concat(); @@ -80,9 +80,9 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); - doc.insertLines(0, ["aa", "bb"]); + doc.insertFullLines(0, ["aa", "bb"]); assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n")); var d = deltas.concat(); @@ -97,9 +97,9 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); - doc.insertLines(2, ["aa", "bb"]); + doc.insertFullLines(2, ["aa", "bb"]); assert.equal(doc.getValue(), ["12", "34", "aa", "bb"].join("\n")); }, @@ -107,9 +107,9 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); - doc.insertLines(1, ["aa", "bb"]); + doc.insertFullLines(1, ["aa", "bb"]); assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n")); var d = deltas.concat(); @@ -124,7 +124,7 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); doc.insert({row: 0, column: 0}, "aa\nbb\ncc"); assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n")); @@ -141,9 +141,9 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); - doc.insert({row: 2, column: 0}, "aa\nbb\ncc"); + doc.insert({row: 1, column: 2}, "aa\nbb\ncc"); assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n")); var d = deltas.concat(); @@ -158,7 +158,7 @@ module.exports = { var doc = new Document(["12", "34"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); doc.insert({row: 0, column: 1}, "aa\nbb\ncc"); assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n")); @@ -175,7 +175,7 @@ module.exports = { var doc = new Document(["1234", "5678"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); doc.remove(new Range(0, 1, 0, 3)); assert.equal(doc.getValue(), ["14", "5678"].join("\n")); @@ -192,7 +192,7 @@ module.exports = { var doc = new Document(["1234", "5678"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); doc.remove(new Range(0, 4, 1, 0)); assert.equal(doc.getValue(), ["12345678"].join("\n")); @@ -209,7 +209,7 @@ module.exports = { var doc = new Document(["1234", "5678", "abcd"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); doc.remove(new Range(0, 2, 2, 2)); assert.equal(doc.getValue(), ["12cd"].join("\n")); @@ -226,7 +226,7 @@ module.exports = { var doc = new Document(["1234", "5678", "abcd"]); var deltas = []; - doc.on("change", function(e) { deltas.push(e.data); }); + doc.on("change", function(e) { deltas.push(e); }); doc.remove(new Range(1, 0, 3, 0)); assert.equal(doc.getValue(), ["1234", ""].join("\n")); @@ -235,7 +235,7 @@ module.exports = { "test: remove lines should return the removed lines" : function() { var doc = new Document(["1234", "5678", "abcd"]); - var removed = doc.removeLines(1, 2); + var removed = doc.removeFullLines(1, 2); assert.equal(removed.join("\n"), ["5678", "abcd"].join("\n")); }, diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 3e941375..2aa9b378 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -151,7 +151,7 @@ var EditSession = function(text, mode) { this.$foldData = []; this.$foldData.toString = function() { return this.join("\n"); - } + }; this.on("changeFold", this.onChangeFold.bind(this)); this.$onChange = this.onChange.bind(this); @@ -249,13 +249,12 @@ var EditSession = function(text, mode) { this.$resetRowCache(fold.start.row); }; - this.onChange = function(e) { - var delta = e.data; + this.onChange = function(delta) { this.$modified = true; - this.$resetRowCache(delta.range.start.row); + this.$resetRowCache(delta.start.row); - var removedFolds = this.$updateInternalDataOnChange(e); + var removedFolds = this.$updateInternalDataOnChange(delta); if (!this.$fromUndo && this.$undoManager && !delta.ignore) { this.$deltasDoc.push(delta); if (removedFolds && removedFolds.length != 0) { @@ -269,7 +268,7 @@ var EditSession = function(text, mode) { } this.bgTokenizer && this.bgTokenizer.$updateOnChange(delta); - this._signal("change", e); + this._signal("change", delta); }; /** @@ -679,10 +678,10 @@ var EditSession = function(text, mode) { }; /** - * Returns an array containing the IDs of all the markers, either front or back. + * Returns an object containing all of the markers, either front or back. * @param {Boolean} inFront If `true`, indicates you only want front markers; `false` indicates only back markers * - * @returns {Array} + * @returns {Object} **/ this.getMarkers = function(inFront) { return inFront ? this.$frontMarkers : this.$backMarkers; @@ -1146,6 +1145,19 @@ var EditSession = function(text, mode) { this.remove = function(range) { return this.doc.remove(range); }; + + /** + * Removes a range of full lines. This method also triggers the `'change'` event. + * @param {Number} firstRow The first row to be removed + * @param {Number} lastRow The last row to be removed + * @returns {[String]} Returns all the removed lines. + * + * @related Document.removeFullLines + * + **/ + this.removeFullLines = function(firstRow, lastRow){ + return this.doc.removeFullLines(firstRow, lastRow); + }; /** * Reverts previous changes to your document. @@ -1222,39 +1234,36 @@ var EditSession = function(text, mode) { this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) { function isInsert(delta) { - var insert = - delta.action === "insertText" || delta.action === "insertLines"; - return isUndo ? !insert : insert; + return isUndo ? delta.action !== "insert" : delta.action === "insert"; } var delta = deltas[0]; var range, point; var lastDeltaIsInsert = false; if (isInsert(delta)) { - range = Range.fromPoints(delta.range.start, delta.range.end); + range = Range.fromPoints(delta.start, delta.end); lastDeltaIsInsert = true; } else { - range = Range.fromPoints(delta.range.start, delta.range.start); + range = Range.fromPoints(delta.start, delta.start); lastDeltaIsInsert = false; } for (var i = 1; i < deltas.length; i++) { delta = deltas[i]; if (isInsert(delta)) { - point = delta.range.start; + point = delta.start; if (range.compare(point.row, point.column) == -1) { - range.setStart(delta.range.start); + range.setStart(point); } - point = delta.range.end; + point = delta.end; if (range.compare(point.row, point.column) == 1) { - range.setEnd(delta.range.end); + range.setEnd(point); } lastDeltaIsInsert = true; } else { - point = delta.range.start; + point = delta.start; if (range.compare(point.row, point.column) == -1) { - range = - Range.fromPoints(delta.range.start, delta.range.start); + range = Range.fromPoints(delta.start, delta.start); } lastDeltaIsInsert = false; } @@ -1368,7 +1377,7 @@ var EditSession = function(text, mode) { this.indentRows = function(startRow, endRow, indentString) { indentString = indentString.replace(/\t/g, this.getTabString()); for (var row=startRow; row<=endRow; row++) - this.insert({row: row, column:0}, indentString); + this.doc.insertInLine({row: row, column: 0}, indentString); }; /** @@ -1425,11 +1434,11 @@ var EditSession = function(text, mode) { x.end.row += diff; return x; }); - + var lines = dir == 0 ? this.doc.getLines(firstRow, lastRow) - : this.doc.removeLines(firstRow, lastRow); - this.doc.insertLines(firstRow+diff, lines); + : this.doc.removeFullLines(firstRow, lastRow); + this.doc.insertFullLines(firstRow+diff, lines); folds.length && this.addFolds(folds); return diff; }; @@ -1439,8 +1448,6 @@ var EditSession = function(text, mode) { * @param {Number} lastRow The final row to move up * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1. * - * @related Document.insertLines - * **/ this.moveLinesUp = function(firstRow, lastRow) { return this.$moveLines(firstRow, lastRow, -1); @@ -1451,8 +1458,6 @@ var EditSession = function(text, mode) { * @param {Number} firstRow The starting row to move down * @param {Number} lastRow The final row to move down * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1. - * - * @related Document.insertLines **/ this.moveLinesDown = function(firstRow, lastRow) { return this.$moveLines(firstRow, lastRow, 1); @@ -1656,34 +1661,23 @@ var EditSession = function(text, mode) { }; }; - this.$updateInternalDataOnChange = function(e) { + this.$updateInternalDataOnChange = function(delta) { var useWrapMode = this.$useWrapMode; - var len; - var action = e.data.action; - var firstRow = e.data.range.start.row; - var lastRow = e.data.range.end.row; - var start = e.data.range.start; - var end = e.data.range.end; + var action = delta.action; + var start = delta.start; + var end = delta.end; + var firstRow = start.row; + var lastRow = end.row; + var len = lastRow - firstRow; var removedFolds = null; - - if (action.indexOf("Lines") != -1) { - if (action == "insertLines") { - lastRow = firstRow + (e.data.lines.length); - } else { - lastRow = firstRow; - } - len = e.data.lines ? e.data.lines.length : lastRow - firstRow; - } else { - len = lastRow - firstRow; - } - + this.$updating = true; if (len != 0) { - if (action.indexOf("remove") != -1) { + if (action === "remove") { this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len); var foldLines = this.$foldData; - removedFolds = this.getFoldsInRange(e.data.range); + removedFolds = this.getFoldsInRange(delta); this.removeFolds(removedFolds); var foldLine = this.getFoldLine(end.row); @@ -1748,10 +1742,10 @@ var EditSession = function(text, mode) { } else { // Realign folds. E.g. if you add some new chars before a fold, the // fold should "move" to the right. - len = Math.abs(e.data.range.start.column - e.data.range.end.column); - if (action.indexOf("remove") != -1) { + len = Math.abs(delta.start.column - delta.end.column); + if (action === "remove") { // Get all the folds in the change range and remove them. - removedFolds = this.getFoldsInRange(e.data.range); + removedFolds = this.getFoldsInRange(delta); this.removeFolds(removedFolds); len = -len; diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index 53071dda..77c0f22d 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -829,15 +829,13 @@ function Folding() { } }; - this.updateFoldWidgets = function(e) { - var delta = e.data; - var range = delta.range; - var firstRow = range.start.row; - var len = range.end.row - firstRow; + this.updateFoldWidgets = function(delta) { + var firstRow = delta.start.row; + var len = delta.end.row - firstRow; if (len === 0) { this.foldWidgets[firstRow] = null; - } else if (delta.action == "removeText" || delta.action == "removeLines") { + } else if (delta.action == 'remove') { this.foldWidgets.splice(firstRow, len + 1, null); } else { var args = Array(len + 1); diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index 5e309cf6..0b904b63 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -442,12 +442,12 @@ module.exports = { session.setTabSize(4); assert.equal(session.getScreenWidth(), 2); - session.doc.insertNewLine({row: 0, column: Infinity}); - session.doc.insertLines(1, ["123"]); + session.doc.insertMergedLines({row: 0, column: Infinity}, ['', '']); + session.doc.insertFullLines(1, ["123"]); assert.equal(session.getScreenWidth(), 3); - session.doc.insertNewLine({row: 0, column: Infinity}); - session.doc.insertLines(1, ["\t\t"]); + session.doc.insertMergedLines({row: 0, column: Infinity}, ['', '']); + session.doc.insertFullLines(1, ["\t\t"]); assert.equal(session.getScreenWidth(), 8); @@ -471,9 +471,9 @@ module.exports = { session.setUseWrapMode(true); - document.insertLines(0, ["a", "b"]); - document.insertLines(2, ["c", "d"]); - document.removeLines(1, 2); + document.insertFullLines(0, ["a", "b"]); + document.insertFullLines(2, ["c", "d"]); + document.removeFullLines(1, 2); }, "test wrapMode init has to create wrapData array": function() { diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 6ce561bb..5da8a961 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -162,8 +162,12 @@ var Editor = function(renderer, session) { var command = this.curOp.command; if (command.name && this.$blockScrolling > 0) this.$blockScrolling--; - if (command && command.scrollIntoView) { - switch (command.scrollIntoView) { + var scrollIntoView = command && command.scrollIntoView; + if (scrollIntoView) { + switch (scrollIntoView) { + case "center-animate": + scrollIntoView = "animate"; + /* fall through */ case "center": this.renderer.scrollCursorIntoView(null, 0.5); break; @@ -181,7 +185,7 @@ var Editor = function(renderer, session) { default: break; } - if (command.scrollIntoView == "animate") + if (scrollIntoView == "animate") this.renderer.animateScrolling(this.curOp.scrollTop); } @@ -274,6 +278,10 @@ var Editor = function(renderer, session) { this.setSession = function(session) { if (this.session == session) return; + + // make sure operationEnd events are not emitted to wrong session + if (this.curOp) this.endOperation(); + this.curOp = {}; var oldSession = this.session; if (oldSession) { @@ -373,6 +381,8 @@ var Editor = function(renderer, session) { oldSession: oldSession }); + this.curOp = null; + oldSession && oldSession._signal("changeEditor", {oldEditor: this}); session && session._signal("changeEditor", {editor: this}); }; @@ -682,20 +692,15 @@ var Editor = function(renderer, session) { * * **/ - this.onDocumentChange = function(e) { - var delta = e.data; - var range = delta.range; - var lastRow; + this.onDocumentChange = function(delta) { + // Rerender and emit "change" event. + var wrap = this.session.$useWrapMode; + var lastRow = (delta.start.row == delta.end.row ? delta.end.row : Infinity); + this.renderer.updateLines(delta.start.row, lastRow, wrap); - if (range.start.row == range.end.row && delta.action != "insertLines" && delta.action != "removeLines") - lastRow = range.end.row; - else - lastRow = Infinity; - this.renderer.updateLines(range.start.row, lastRow, this.session.$useWrapMode); - - this._signal("change", e); - - // update cursor because tab characters can influence the cursor position + this._signal("change", delta); + + // Update cursor because tab characters can influence the cursor position. this.$cursorChange(); this.$updateHighlightActiveLine(); }; @@ -910,14 +915,16 @@ var Editor = function(renderer, session) { * * **/ - this.onPaste = function(text) { - // todo this should change when paste becomes a command - if (this.$readOnly) - return; - - var e = {text: text}; + this.onPaste = function(text, event) { + var e = {text: text, event: event}; + this.commands.exec("paste", this, e); + }; + + this.$handlePaste = function(e) { + if (typeof e == "string") + e = {text: e}; this._signal("paste", e); - text = e.text; + var text = e.text; if (!this.inMultiSelectMode || this.inVirtualSelectionMode) { this.insert(text); } else { @@ -935,7 +942,6 @@ var Editor = function(renderer, session) { this.session.insert(range.start, lines[i]); } } - this.renderer.scrollCursorIntoView(); }; this.execCommand = function(command, args) { @@ -1623,15 +1629,7 @@ var Editor = function(renderer, session) { **/ this.removeLines = function() { var rows = this.$getSelectedRows(); - var range; - if (rows.first === 0 || rows.last+1 < this.session.getLength()) - range = new Range(rows.first, 0, rows.last+1, 0); - else - range = new Range( - rows.first-1, this.session.getLine(rows.first-1).length, - rows.last, this.session.getLine(rows.last).length - ); - this.session.remove(range); + this.session.removeFullLines(rows.first, rows.last); this.clearSelection(); }; diff --git a/lib/ace/ext/chromevox.js b/lib/ace/ext/chromevox.js index 9f7a7996..52a180d4 100644 --- a/lib/ace/ext/chromevox.js +++ b/lib/ace/ext/chromevox.js @@ -578,15 +578,14 @@ var onSelectionChange = function(evt) { * and deleting text. * @param {!Event} evt The event. */ -var onChange = function(evt) { - var data = evt.data; +var onChange = function(delta) { switch (data.action) { - case 'removeText': + case 'remove': cvox.Api.speak(data.text, 0, DELETED_PROP); /* Let the future cursor change event know it's from text change. */ changed = true; break; - case 'insertText': + case 'insert': cvox.Api.speak(data.text, 0); /* Let the future cursor change event know it's from text change. */ changed = true; diff --git a/lib/ace/ext/elastic_tabstops_lite.js b/lib/ace/ext/elastic_tabstops_lite.js index 9901c5df..0f89423a 100644 --- a/lib/ace/ext/elastic_tabstops_lite.js +++ b/lib/ace/ext/elastic_tabstops_lite.js @@ -44,13 +44,12 @@ var ElasticTabstopsLite = function(editor) { this.onExec = function() { recordChanges = true; }; - this.onChange = function(e) { - var range = e.data.range + this.onChange = function(delta) { if (recordChanges) { - if (changedRows.indexOf(range.start.row) == -1) - changedRows.push(range.start.row); - if (range.end.row != range.start.row) - changedRows.push(range.end.row); + if (changedRows.indexOf(delta.start.row) == -1) + changedRows.push(delta.start.row); + if (delta.end.row != delta.start.row) + changedRows.push(delta.end.row); } }; }; diff --git a/lib/ace/ext/emmet.js b/lib/ace/ext/emmet.js index ceb155ef..4faacef2 100644 --- a/lib/ace/ext/emmet.js +++ b/lib/ace/ext/emmet.js @@ -393,7 +393,7 @@ exports.updateCommands = function(editor, enabled) { }; exports.isSupportedMode = function(modeId) { - return modeId && /css|less|scss|sass|stylus|html|php|twig|ejs/.test(modeId); + return modeId && /css|less|scss|sass|stylus|html|php|twig|ejs|handlebars/.test(modeId); }; var onChangeMode = function(e, target) { diff --git a/lib/ace/ext/language_tools.js b/lib/ace/ext/language_tools.js index f02390ee..563fe58a 100644 --- a/lib/ace/ext/language_tools.js +++ b/lib/ace/ext/language_tools.js @@ -155,7 +155,6 @@ function getCompletionPrefix(editor) { var doLiveAutocomplete = function(e) { var editor = e.editor; - var text = e.args || ""; var hasCompleter = editor.completer && editor.completer.activated; // We don't want to autocomplete with no prefix diff --git a/lib/ace/ext/modelist.js b/lib/ace/ext/modelist.js index 7a5a1e9c..675a4b85 100644 --- a/lib/ace/ext/modelist.js +++ b/lib/ace/ext/modelist.js @@ -117,12 +117,13 @@ var supportedModes = { MUSHCode: ["mc|mush"], MySQL: ["mysql"], Nix: ["nix"], + Nim: ["nim"], ObjectiveC: ["m|mm"], OCaml: ["ml|mli"], Pascal: ["pas|p"], Perl: ["pl|pm"], pgSQL: ["pgsql"], - PHP: ["php|phtml"], + PHP: ["php|phtml|shtml|php3|php4|php5|phps|phpt|aw|ctp"], Powershell: ["ps1"], Praat: ["praat|praatscript|psc|proc"], Prolog: ["plg|prolog"], diff --git a/lib/ace/ext/static_highlight.js b/lib/ace/ext/static_highlight.js index b2287f15..2acb3ac5 100644 --- a/lib/ace/ext/static_highlight.js +++ b/lib/ace/ext/static_highlight.js @@ -37,6 +37,10 @@ var baseStyles = require("../requirejs/text!./static.css"); var config = require("../config"); var dom = require("../lib/dom"); +var SimpleTextLayer = function() { + this.config = {}; +}; +SimpleTextLayer.prototype = TextLayer.prototype; var highlight = function(el, opts, callback) { var m = el.className.match(/lang-(\w+)/); @@ -149,12 +153,8 @@ highlight.renderSync = function(input, mode, theme, lineStart, disableGutter) { session.setUseWorker(false); session.setMode(mode); - var textLayer = new TextLayer(document.createElement("div")); + var textLayer = new SimpleTextLayer(); textLayer.setSession(session); - textLayer.config = { - characterWidth: 10, - lineHeight: 20 - }; session.setValue(input); diff --git a/lib/ace/ext/themelist.js b/lib/ace/ext/themelist.js index 2350a2e2..0df76b02 100644 --- a/lib/ace/ext/themelist.js +++ b/lib/ace/ext/themelist.js @@ -73,6 +73,7 @@ var themeData = [ ["Solarized Dark" ,"solarized_dark" , "dark"], ["Terminal" ,"terminal" , "dark"], ["Tomorrow Night" ,"tomorrow_night" , "dark"], + ["The Night After Tomorrow" ,"the_night_after_tomorrow" , "dark"], ["Tomorrow Night Blue" ,"tomorrow_night_blue" , "dark"], ["Tomorrow Night Bright","tomorrow_night_bright" , "dark"], ["Tomorrow Night 80s" ,"tomorrow_night_eighties" , "dark"], diff --git a/lib/ace/incremental_search.js b/lib/ace/incremental_search.js index a64e1857..e4c1bdf8 100644 --- a/lib/ace/incremental_search.js +++ b/lib/ace/incremental_search.js @@ -105,7 +105,7 @@ function objectToRegExp(obj) { this.$mousedownHandler = ed.addEventListener('mousedown', this.onMouseDown.bind(this)); this.selectionFix(ed); this.statusMessage(true); - } + }; this.deactivate = function(reset) { this.cancelSearch(reset); @@ -117,7 +117,7 @@ function objectToRegExp(obj) { } ed.onPaste = this.$originalEditorOnPaste; this.message(''); - } + }; this.selectionFix = function(editor) { // Fix selection bug: When clicked inside the editor @@ -128,7 +128,7 @@ function objectToRegExp(obj) { if (editor.selection.isEmpty() && !editor.session.$emacsMark) { editor.clearSelection(); } - } + }; this.highlight = function(regexp) { var sess = this.$editor.session, @@ -136,7 +136,7 @@ function objectToRegExp(obj) { new SearchHighlight(null, "ace_isearch-result", "text")); hl.setRegexp(regexp); sess._emit("changeBackMarker"); // force highlight layer redraw - } + }; this.cancelSearch = function(reset) { var e = this.$editor; @@ -150,7 +150,7 @@ function objectToRegExp(obj) { } this.highlight(null); return Range.fromPoints(this.$currentPos, this.$currentPos); - } + }; this.highlightAndFindWithNeedle = function(moveToNext, needleUpdateFunc) { if (!this.$editor) return null; @@ -163,7 +163,7 @@ function objectToRegExp(obj) { if (options.needle.length === 0) { this.statusMessage(true); return this.cancelSearch(true); - }; + } // try to find the next occurence and enable highlighting marker options.start = this.$currentPos; @@ -176,13 +176,13 @@ function objectToRegExp(obj) { this.$editor.selection.setRange(Range.fromPoints(shouldSelect ? this.$startPos : found.end, found.end)); if (moveToNext) this.$currentPos = found.end; // highlight after cursor move, so selection works properly - this.highlight(options.re) + this.highlight(options.re); } this.statusMessage(found); return found; - } + }; this.addString = function(s) { return this.highlightAndFindWithNeedle(false, function(needle) { @@ -192,7 +192,7 @@ function objectToRegExp(obj) { reObj.expression += s; return objectToRegExp(reObj); }); - } + }; this.removeChar = function(c) { return this.highlightAndFindWithNeedle(false, function(needle) { @@ -202,7 +202,7 @@ function objectToRegExp(obj) { reObj.expression = reObj.expression.substring(0, reObj.expression.length-1); return objectToRegExp(reObj); }); - } + }; this.next = function(options) { // try to find the next occurence of whatever we have searched for @@ -215,29 +215,29 @@ function objectToRegExp(obj) { return options.useCurrentOrPrevSearch && needle.length === 0 ? this.$prevNeedle || '' : needle; }); - } + }; this.onMouseDown = function(evt) { // when mouse interaction happens then we quit incremental search this.deactivate(); return true; - } + }; this.onPaste = function(text) { this.addString(text); - } + }; this.convertNeedleToRegExp = function() { return this.highlightAndFindWithNeedle(false, function(needle) { return isRegExp(needle) ? needle : stringToRegExp(needle, 'ig'); }); - } + }; this.convertNeedleToString = function() { return this.highlightAndFindWithNeedle(false, function(needle) { return isRegExp(needle) ? regExpToObject(needle).expression : needle; }); - } + }; this.statusMessage = function(found) { var options = this.$options, msg = ''; @@ -245,7 +245,7 @@ function objectToRegExp(obj) { msg += 'isearch: ' + options.needle; msg += found ? '' : ' (not found)'; this.message(msg); - } + }; this.message = function(msg) { if (this.$editor.showCommandLine) { @@ -254,7 +254,7 @@ function objectToRegExp(obj) { } else { console.log(msg); } - } + }; }).call(IncrementalSearch.prototype); diff --git a/lib/ace/keyboard/emacs.js b/lib/ace/keyboard/emacs.js index 945eddf3..8cecad1b 100644 --- a/lib/ace/keyboard/emacs.js +++ b/lib/ace/keyboard/emacs.js @@ -428,7 +428,7 @@ exports.emacsKeys = { "M-;": "togglecomment", "C-/|C-x u|S-C--|C-z": "undo", - "S-C-/|S-C-x u|C--|S-C-z": "redo", //infinite undo? + "S-C-/|S-C-x u|C--|S-C-z": "redo", // infinite undo? // vertical editing "C-x r": "selectRectangularRegion", "M-x": {command: "focusCommandLine", args: "M-x "} @@ -483,7 +483,7 @@ exports.handler.addCommands({ // different. Deactivate the mark when setMark is run with active // mark if (transientMarkModeActive && (mark || !hasNoSelection)) { - if (editor.inMultiSelectMode) editor.forEachSelection({exec: editor.clearSelection.bind(editor)}) + if (editor.inMultiSelectMode) editor.forEachSelection({exec: editor.clearSelection.bind(editor)}); else editor.clearSelection(); if (mark) editor.pushEmacsMark(null); return; diff --git a/lib/ace/keyboard/hash_handler.js b/lib/ace/keyboard/hash_handler.js index 06badccd..a7dc1a93 100644 --- a/lib/ace/keyboard/hash_handler.js +++ b/lib/ace/keyboard/hash_handler.js @@ -87,9 +87,12 @@ MultiHashHandler.prototype = HashHandler.prototype; } }; - this.bindKey = function(key, command, asDefault) { - if (typeof key == "object") + this.bindKey = function(key, command, position) { + if (typeof key == "object") { + if (position == undefined) + position = key.position; key = key[this.platform]; + } if (!key) return; if (typeof command == "function") @@ -110,11 +113,15 @@ MultiHashHandler.prototype = HashHandler.prototype; } var binding = this.parseKeys(keyPart); var id = KEY_MODS[binding.hashId] + binding.key; - this._addCommandToBinding(chain + id, command, asDefault); + this._addCommandToBinding(chain + id, command, position); }, this); }; - this._addCommandToBinding = function(keyId, command, asDefault) { + function getPosition(command) { + return typeof command == "object" && command.bindKey + && command.bindKey.position || 0; + } + this._addCommandToBinding = function(keyId, command, position) { var ckb = this.commandKeyBinding, i; if (!command) { delete ckb[keyId]; @@ -126,11 +133,21 @@ MultiHashHandler.prototype = HashHandler.prototype; } else if ((i = ckb[keyId].indexOf(command)) != -1) { ckb[keyId].splice(i, 1); } - - if (asDefault || command.isDefault) - ckb[keyId].unshift(command); - else - ckb[keyId].push(command); + + if (typeof position != "number") { + if (position || command.isDefault) + position = -100; + else + position = getPosition(command); + } + var commands = ckb[keyId]; + for (i = 0; i < commands.length; i++) { + var other = commands[i]; + var otherPos = getPosition(other); + if (otherPos > position) + break; + } + commands.splice(i, 0, command); } }; @@ -219,10 +236,18 @@ MultiHashHandler.prototype = HashHandler.prototype; } } - if (data.$keyChain && keyCode > 0) - data.$keyChain = ""; + if (data.$keyChain) { + if ((!hashId || hashId == 4) && keyString.length == 1) + data.$keyChain = data.$keyChain.slice(0, -key.length - 1); // wait for input + else if (hashId == -1 || keyCode > 0) + data.$keyChain = ""; // reset keyChain + } return {command: command}; }; + + this.getStatusText = function(editor, data) { + return data.$keyChain || ""; + }; }).call(HashHandler.prototype); diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 0bd57eed..4a2bb72c 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -306,7 +306,7 @@ var TextInput = function(parentNode, host) { var data = handleClipboardData(e); if (typeof data == "string") { if (data) - host.onPaste(data); + host.onPaste(data, e); if (useragent.isIE) setTimeout(resetSelection); event.preventDefault(e); diff --git a/lib/ace/keyboard/vim.js b/lib/ace/keyboard/vim.js index 1b4507b3..17c26c16 100644 --- a/lib/ace/keyboard/vim.js +++ b/lib/ace/keyboard/vim.js @@ -178,13 +178,6 @@ define(function(require, exports, module) { return this.ace.inVirtualSelectionMode && this.ace.selection.index; }; this.onChange = function(delta) { - var oldDelta = delta.data; - delta = { - start: oldDelta.range.start, - end: oldDelta.range.end, - action: oldDelta.action, - lines: oldDelta.lines || [oldDelta.text] - };// v1.2 api compatibility if (delta.action[0] == 'i') { var change = { text: delta.lines }; var curOp = this.curOp = this.curOp || {}; @@ -264,6 +257,7 @@ define(function(require, exports, module) { } if (!this.ace.inVirtualSelectionMode) this.ace.exitMultiSelectMode(); + this.ace.session.unfold({row: line, column: ch}); this.ace.selection.moveTo(line, ch); }; this.getCursor = function(p) { @@ -577,7 +571,6 @@ define(function(require, exports, module) { highlight.session = null; }; highlight.updateOnChange = function(delta) { - delta = delta.data.range;// v1.2 api compatibility var row = delta.start.row; if (row == delta.end.row) highlight.cache[row] = undefined; else highlight.cache.splice(row, highlight.cache.length); @@ -737,10 +730,14 @@ CodeMirror.defineExtension = function(name, fn) { CodeMirror.prototype[name] = fn; }; dom.importCssString(".normal-mode .ace_cursor{\ - border: 0!important;\ + border: 1px solid red;\ background-color: red;\ opacity: 0.5;\ -}.ace_dialog {\ +}\ +.normal-mode .ace_hidden-cursors .ace_cursor{\ + background-color: transparent;\ +}\ +.ace_dialog {\ position: absolute;\ left: 0; right: 0;\ background: white;\ @@ -766,23 +763,6 @@ dom.importCssString(".normal-mode .ace_cursor{\ font-family: monospace;\ }", "vimMode"); (function() { - function dialogDiv(cm, template, bottom) { - var wrap = cm.ace.container; - var dialog; - dialog = wrap.appendChild(document.createElement("div")); - if (bottom) - dialog.className = "ace_dialog ace_dialog-bottom"; - else - dialog.className = "ace_dialog ace_dialog-top"; - - if (typeof template == "string") { - dialog.innerHTML = template; - } else { // Assuming it's a detached DOM element. - dialog.appendChild(template); - } - return dialog; - } - function closeNotification(cm, newVal) { if (cm.state.currentNotificationClose) cm.state.currentNotificationClose(); @@ -2204,6 +2184,8 @@ dom.importCssString(".normal-mode .ace_cursor{\ return; } if (motionArgs.toJumplist) { + if (!operator) + cm.ace.curOp.command.scrollIntoView = "center-animate"; // ace patch var jumpList = vimGlobalState.jumpList; // if the current motion is # or *, use cachedCursor var cachedCursor = jumpList.cachedCursor; @@ -2890,6 +2872,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ var markPos = mark ? mark.find() : undefined; markPos = markPos ? markPos : cm.getCursor(); cm.setCursor(markPos); + cm.ace.curOp.command.scrollIntoView = "center-animate"; // ace patch }, scroll: function(cm, actionArgs, vim) { if (vim.visualMode) { @@ -5940,7 +5923,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ }, true); } return isHandled; - }; + } exports.CodeMirror = CodeMirror; var getVim = Vim.maybeInitVimState_; exports.handler = { @@ -5954,9 +5937,9 @@ dom.importCssString(".normal-mode .ace_cursor{\ if (!vim.insertMode) { var isbackwards = !sel.cursor ? session.selection.isBackwards() || session.selection.isEmpty() - : Range.comparePoints(sel.cursor, sel.start) <= 0 + : Range.comparePoints(sel.cursor, sel.start) <= 0; if (!isbackwards && left > w) - left -= w + left -= w; } if (!vim.insertMode && vim.status) { h = h / 2; @@ -6120,13 +6103,13 @@ dom.importCssString(".normal-mode .ace_cursor{\ }; var renderVirtualNumbers = { getText: function(session, row) { - return (Math.abs(session.selection.lead.row - row) || (row + 1 + (row < 9? "\xb7" : "" ))) + "" + return (Math.abs(session.selection.lead.row - row) || (row + 1 + (row < 9? "\xb7" : "" ))) + ""; }, getWidth: function(session, lastLineNumber, config) { return session.getLength().toString().length * config.characterWidth; }, update: function(e, editor) { - editor.renderer.$loop.schedule(editor.renderer.CHANGE_GUTTER) + editor.renderer.$loop.schedule(editor.renderer.CHANGE_GUTTER); }, attach: function(editor) { editor.renderer.$gutterLayer.$renderer = this; @@ -6171,7 +6154,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ if (cm.ace.inVirtualSelectionMode) cm.ace.on("beforeEndOperation", delayedExecAceCommand); else - delayedExecAceCommand(null, cm.ace) + delayedExecAceCommand(null, cm.ace); }; function delayedExecAceCommand(op, ace) { ace.off("beforeEndOperation", delayedExecAceCommand); @@ -6190,5 +6173,5 @@ dom.importCssString(".normal-mode .ace_cursor{\ exports.handler.actions = actions; exports.Vim = Vim; - Vim.map("Y", "yy"); + Vim.map("Y", "yy", "normal"); }); diff --git a/lib/ace/layer/font_metrics.js b/lib/ace/layer/font_metrics.js index 7e71f8a8..715c6cf3 100644 --- a/lib/ace/layer/font_metrics.js +++ b/lib/ace/layer/font_metrics.js @@ -120,9 +120,9 @@ var FontMetrics = exports.FontMetrics = function(parentEl, interval) { this.setPolling = function(val) { if (val) { this.$pollSizeChanges(); - } else { - if (this.$pollSizeChangesTimer) - this.$pollSizeChangesTimer; + } else if (this.$pollSizeChangesTimer) { + clearInterval(this.$pollSizeChangesTimer); + this.$pollSizeChangesTimer = 0; } }; diff --git a/lib/ace/layer/gutter.js b/lib/ace/layer/gutter.js index 13ee8a25..dc1055c2 100644 --- a/lib/ace/layer/gutter.js +++ b/lib/ace/layer/gutter.js @@ -100,16 +100,14 @@ var Gutter = function(parentEl) { } }; - this.$updateAnnotations = function (e) { + this.$updateAnnotations = function (delta) { if (!this.$annotations.length) return; - var delta = e.data; - var range = delta.range; - var firstRow = range.start.row; - var len = range.end.row - firstRow; + var firstRow = delta.start.row; + var len = delta.end.row - firstRow; if (len === 0) { // do nothing - } else if (delta.action == "removeText" || delta.action == "removeLines") { + } else if (delta.action == 'remove') { this.$annotations.splice(firstRow, len + 1, null); } else { var args = new Array(len + 1); diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index 26f38447..9818d225 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -90,7 +90,7 @@ var Marker = function(parentEl) { else this.drawMultiLineMarker(html, range, marker.clazz, config); } else { - this.drawSingleLineMarker(html, range, marker.clazz + " ace_start", config); + this.drawSingleLineMarker(html, range, marker.clazz + " ace_start" + " ace_br15", config); } } this.element.innerHTML = html.join(""); @@ -100,29 +100,30 @@ var Marker = function(parentEl) { return (row - layerConfig.firstRowScreen) * layerConfig.lineHeight; }; + function getBorderClass(tl, tr, br, bl) { + return (tl ? 1 : 0) | (tr ? 2 : 0) | (br ? 4 : 0) | (bl ? 8 : 0); + } // Draws a marker, which spans a range of text on multiple lines this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig, extraStyle) { - // selection start - var row = range.start.row; var session = this.session; - - var lineRange = new Range( - row, range.start.column, - row, session.getScreenLastRowColumn(row) - ); - this.drawSingleLineMarker(stringBuilder, lineRange, clazz + " ace_start", layerConfig, 1, extraStyle); - - // selection end - row = range.end.row; - lineRange = new Range(row, session.getRowWrapIndent(row), row, range.end.column); - this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 0, extraStyle); - - for (row = range.start.row + 1; row < range.end.row; row++) { - lineRange.start.row = row; - lineRange.start.column = session.getRowWrapIndent(row); - lineRange.end.row = row; - lineRange.end.column = session.getScreenLastRowColumn(row); - this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1, extraStyle); + var start = range.start.row; + var end = range.end.row; + var row = start; + var prev = 0; + var curr = 0; + var next = session.getScreenLastRowColumn(row); + var lineRange = new Range(row, range.start.column, row, curr); + for (; row <= end; row++) { + lineRange.start.row = lineRange.end.row = row; + lineRange.start.column = row == start ? range.start.column : session.getRowWrapIndent(row); + lineRange.end.column = next; + prev = curr; + curr = next; + next = row + 1 < end ? session.getScreenLastRowColumn(row + 1) : row == end ? 0 : range.end.column; + this.drawSingleLineMarker(stringBuilder, lineRange, + clazz + (row == start ? " ace_start" : "") + " ace_br" + + getBorderClass(row == start || row == start + 1 && range.start.column, prev < curr, curr > next, row == end), + layerConfig, row == end ? 0 : 1, extraStyle); } }; @@ -136,7 +137,7 @@ var Marker = function(parentEl) { extraStyle = extraStyle || ""; stringBuilder.push( - "
" - + this.TAB_CHAR - + lang.stringRepeat(" ", i - 1) + + lang.stringRepeat(this.TAB_CHAR, i) + ""); } else { tabStr.push(lang.stringRepeat(" ", i)); @@ -145,7 +144,7 @@ var Text = function(parentEl) { spaceClass = " ace_invisible_space"; tabClass = " ace_invisible_tab"; var spaceContent = lang.stringRepeat(this.SPACE_CHAR, this.tabSize); - var tabContent = this.TAB_CHAR + lang.stringRepeat(" ", this.tabSize - 1); + var tabContent = lang.stringRepeat(this.TAB_CHAR, this.tabSize); } else{ var spaceContent = lang.stringRepeat(" ", this.tabSize); var tabContent = spaceContent; diff --git a/lib/ace/layer/text_test.js b/lib/ace/layer/text_test.js index e3403ca4..3946ec66 100644 --- a/lib/ace/layer/text_test.js +++ b/lib/ace/layer/text_test.js @@ -91,7 +91,7 @@ module.exports = { var EOL = "" + textLayer.EOL_CHAR + ""; var SPACE = function(i) {return Array(i+1).join(" ")} var DOT = function(i) {return Array(i+1).join(textLayer.SPACE_CHAR)} - var TAB = function(i) {return textLayer.TAB_CHAR + SPACE(i-1)} + var TAB = function(i) {return Array(i+1).join(textLayer.TAB_CHAR)} function testRender(results) { for (var i = results.length; i--; ) { var stringBuilder = []; diff --git a/lib/ace/lib/dom.js b/lib/ace/lib/dom.js index ef2f7caa..2cbfe23e 100644 --- a/lib/ace/lib/dom.js +++ b/lib/ace/lib/dom.js @@ -91,6 +91,11 @@ exports.toggleCssClass = function(el, name) { return add; }; +if (typeof document == "undefined") { + exports.importCssString = function() {}; + return; +} + /* * Add or remove a CSS class from the list of classes on the given node * depending on the value of include @@ -173,9 +178,6 @@ exports.getInnerHeight = function(element) { }; -if (typeof document == "undefined") - return; - if (window.pageYOffset !== undefined) { exports.getPageScrollTop = function() { return window.pageYOffset; diff --git a/lib/ace/lib/event.js b/lib/ace/lib/event.js index 06932d3c..57924056 100644 --- a/lib/ace/lib/event.js +++ b/lib/ace/lib/event.js @@ -244,7 +244,7 @@ function normalizeCommandKeys(callback, e, keyCode) { if (pressedKeys[keyCode] == 1) ts = e.timeStamp; } else if (keyCode === 18 && hashId === 3 && location === 2) { - var dt = e.timestamp - ts; + var dt = e.timeStamp - ts; if (dt < 50) pressedKeys.altGr = true; } @@ -326,13 +326,14 @@ exports.addCommandKeyListener = function(el, callback) { }); if (!pressedKeys) { - pressedKeys = Object.create(null); - addListener(window, "focus", function(e) { - pressedKeys = Object.create(null); - }); + resetPressedKeys(); + addListener(window, "focus", resetPressedKeys); } } }; +function resetPressedKeys(e) { + pressedKeys = Object.create(null); +} if (window.postMessage && !useragent.isOldIE) { var postMessageId = 1; diff --git a/lib/ace/line_widgets.js b/lib/ace/line_widgets.js index d4080b53..ba87d857 100644 --- a/lib/ace/line_widgets.js +++ b/lib/ace/line_widgets.js @@ -113,18 +113,16 @@ function LineWidgets(session) { }); }; - this.updateOnChange = function(e) { + this.updateOnChange = function(delta) { var lineWidgets = this.session.lineWidgets; if (!lineWidgets) return; - - var delta = e.data; - var range = delta.range; - var startRow = range.start.row; - var len = range.end.row - startRow; + + var startRow = delta.start.row; + var len = delta.end.row - startRow; if (len === 0) { // return - } else if (delta.action == "removeText" || delta.action == "removeLines") { + } else if (delta.action == 'remove') { var removed = lineWidgets.splice(startRow + 1, len); removed.forEach(function(w) { w && this.removeLineWidget(w); diff --git a/lib/ace/mode/_test/tokens_gherkin.json b/lib/ace/mode/_test/tokens_gherkin.json index d16ffc6c..173d9798 100644 --- a/lib/ace/mode/_test/tokens_gherkin.json +++ b/lib/ace/mode/_test/tokens_gherkin.json @@ -56,34 +56,34 @@ ],[ "start", ["text"," "], - ["comment","| "], - ["string","start "], - ["comment","| "], - ["string","eat "], - ["comment","| "], - ["string","left "], + ["comment","|"], + ["string"," start "], + ["comment","|"], + ["string"," eat "], + ["comment","|"], + ["string"," left "], ["comment","|"] ],[ "start", ["text"," "], - ["comment","| "], - ["string"," 12 "], - ["comment","| "], - ["string"," 5 "], - ["comment","| "], - ["string"," 7 "], + ["comment","|"], + ["string"," 12 "], + ["comment","|"], + ["string"," 5 "], + ["comment","|"], + ["string"," 7 "], ["comment","|"] ],[ "start", ["text"," "], - ["comment","| "], - ["string"," 20 "], - ["comment","| "], - ["string"," 5 "], - ["comment","| "], - ["string"," 15 "], - ["comment","| "], - ["string"," "] + ["comment","|"], + ["string"," 20 "], + ["comment","|"], + ["string"," 5 "], + ["comment","|"], + ["string"," 15 "], + ["comment","|"], + ["string"," "] ],[ "start" ],[ diff --git a/lib/ace/mode/behaviour/behaviour_test.js b/lib/ace/mode/behaviour/behaviour_test.js index 9dc27bf8..245edf99 100644 --- a/lib/ace/mode/behaviour/behaviour_test.js +++ b/lib/ace/mode/behaviour/behaviour_test.js @@ -144,6 +144,16 @@ module.exports = { exec("selectleft", 1); exec("insertstring", 1, '"'); assert.equal(editor.getValue(), '("foo")'); + + editor.setValue("", 1); + exec("selectleft", 1); + exec("insertstring", 1, '"'); + assert.equal(editor.getValue(), '""'); + exec("insertstring", 1, '\\'); + exec("insertstring", 1, 'n'); + exec("insertstring", 1, '"'); + assert.equal(editor.getValue(), '"\\n"'); + }, "test: xml": function() { editor = new Editor(new MockRenderer()); diff --git a/lib/ace/mode/behaviour/cstyle.js b/lib/ace/mode/behaviour/cstyle.js index a1dce91e..dd1b0d14 100644 --- a/lib/ace/mode/behaviour/cstyle.js +++ b/lib/ace/mode/behaviour/cstyle.js @@ -269,8 +269,8 @@ var CstyleBehaviour = function() { if (leftChar == "\\" && token && /escape/.test(token.type)) return null; - var stringBefore = token && /string/.test(token.type); - var stringAfter = !rightToken || /string/.test(rightToken.type); + var stringBefore = token && /string|escape/.test(token.type); + var stringAfter = !rightToken || /string|escape/.test(rightToken.type); var pair; if (rightChar == quote) { diff --git a/lib/ace/mode/gherkin_highlight_rules.js b/lib/ace/mode/gherkin_highlight_rules.js index d54db204..04ce877c 100644 --- a/lib/ace/mode/gherkin_highlight_rules.js +++ b/lib/ace/mode/gherkin_highlight_rules.js @@ -36,18 +36,18 @@ var stringEscape = "\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv'\"]|U[0-9A-Fa-f] var GherkinHighlightRules = function() { - // need to include constant ints + // need to include constant ints this.$rules = { - start : [{ + start : [{ token: 'constant.numeric', regex: "(?:(?:[1-9]\\d*)|(?:0))" - }, { - token : "comment", - regex : "#.*$" - }, { - token : "keyword", - regex : "Feature:|Background:|Scenario:|Scenario\ Outline:|Examples:|Given|When|Then|And|But|\\*", - }, { + }, { + token : "comment", + regex : "#.*$" + }, { + token : "keyword", + regex : "Feature:|Background:|Scenario:|Scenario\ Outline:|Examples:|Given|When|Then|And|But|\\*", + }, { token : "string", // multi line """ string start regex : '"{3}', next : "qqstring3" @@ -56,22 +56,22 @@ var GherkinHighlightRules = function() { regex : '"', next : "qqstring" }, { - token : "comment", - regex : "@[A-Za-z0-9]+", - next : "start" + token : "comment", + regex : "@[A-Za-z0-9]+", + next : "start" }, { - token : "comment", - regex : "<.+>" + token : "comment", + regex : "<.+>" }, { - token : "comment", - regex : "\\| ", - next : "table-item" + token : "comment", + regex : "\\|(?=.)", + next : "table-item" }, { - token : "comment", - regex : "\\|$", - next : "start" + token : "comment", + regex : "\\|$", + next : "start" }], - "qqstring3" : [ { + "qqstring3" : [ { token : "constant.language.escape", regex : stringEscape }, { @@ -81,7 +81,7 @@ var GherkinHighlightRules = function() { }, { defaultToken : "string" }], - "qqstring" : [{ + "qqstring" : [{ token : "constant.language.escape", regex : stringEscape }, { @@ -96,15 +96,19 @@ var GherkinHighlightRules = function() { defaultToken: "string" }], "table-item" : [{ + token : "comment", + regex : /$/, + next : "start" + }, { + token : "comment", + regex : /\|/ + }, { token : "string", - regex : "[A-Za-z0-9 ]*", - next : "start" - }], + regex : /\\./ + }, { + defaultToken : "string" + }] }; - - - //new TextHighlightRules().getRules(); - } oop.inherits(GherkinHighlightRules, TextHighlightRules); diff --git a/lib/ace/mode/handlebars.js b/lib/ace/mode/handlebars.js index 3f2e7179..164ad43f 100644 --- a/lib/ace/mode/handlebars.js +++ b/lib/ace/mode/handlebars.js @@ -13,7 +13,6 @@ var Mode = function() { HtmlMode.call(this); this.HighlightRules = HandlebarsHighlightRules; this.$behaviour = new HtmlBehaviour(); - this.foldingRules = new HtmlFoldMode(); }; @@ -21,7 +20,7 @@ var Mode = function() { oop.inherits(Mode, HtmlMode); (function() { - this.blockComment = {start: "{!--", end: "--}"}; + this.blockComment = {start: "{{!--", end: "--}}"}; this.$id = "ace/mode/handlebars"; }).call(Mode.prototype); diff --git a/lib/ace/mode/less_highlight_rules.js b/lib/ace/mode/less_highlight_rules.js index d39bdeae..a0162765 100644 --- a/lib/ace/mode/less_highlight_rules.js +++ b/lib/ace/mode/less_highlight_rules.js @@ -231,7 +231,7 @@ var LessHighlightRules = function() { regex: "\\.[a-z0-9-_]+" }, { token: "variable.language", - regex: ":[a-z0-9-_]+" + regex: ":[a-z_][a-z0-9-_]*" }, { token: "constant", regex: "[a-z0-9-_]+" diff --git a/lib/ace/mode/nim.js b/lib/ace/mode/nim.js new file mode 100644 index 00000000..cbf48ef2 --- /dev/null +++ b/lib/ace/mode/nim.js @@ -0,0 +1,113 @@ +/* ***** 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 TextMode = require("./text").Mode; +var NimHighlightRules = require("./nim_highlight_rules").NimHighlightRules; +var NimFoldMode = require("./folding/pythonic").FoldMode; +var Range = require("../range").Range; + +var Mode = function() { + this.HighlightRules = NimHighlightRules; + this.foldingRules = new NimFoldMode("\\:|="); +}; +oop.inherits(Mode, TextMode); + +(function() { + + this.lineCommentStart = "#"; + + this.getNextLineIndent = function(state, line, tab) { + var indent = this.$getIndent(line); + + var tokenizedLine = this.getTokenizer().getLineTokens(line, state); + var tokens = tokenizedLine.tokens; + + if (tokens.length && tokens[tokens.length-1].type == "comment") { + return indent; + } + + if (state == "start") { + var match = line.match(/^.*[\{\(\[\:=]\s*$/); + if (match) { + indent += tab; + } + } + + return indent; + }; + + var outdents = { + "discard": 1, + "return": 1, + "raise": 1, + "break": 1, + "continue": 1 + }; + + this.checkOutdent = function(state, line, input) { + if (input !== "\r\n" && input !== "\r" && input !== "\n") + return false; + + var tokens = this.getTokenizer().getLineTokens(line.trim(), state).tokens; + + if (!tokens) + return false; + + // ignore trailing comments + do { + var last = tokens.pop(); + } while (last && (last.type == "comment" || (last.type == "text" && last.value.match(/^\s+$/)))); + + if (!last) + return false; + + return (last.type == "keyword" && outdents[last.value]); + }; + + this.autoOutdent = function(state, doc, row) { + // outdenting in python is slightly different because it always applies + // to the next line and only of a new line is inserted + + row += 1; + var indent = this.$getIndent(doc.getLine(row)); + var tab = doc.getTabString(); + if (indent.slice(-tab.length) == tab) + doc.remove(new Range(row, indent.length-tab.length, row, indent.length)); + }; + + this.$id = "ace/mode/nim"; +}).call(Mode.prototype); + +exports.Mode = Mode; +}); diff --git a/lib/ace/mode/nim_highlight_rules.js b/lib/ace/mode/nim_highlight_rules.js new file mode 100644 index 00000000..e762a636 --- /dev/null +++ b/lib/ace/mode/nim_highlight_rules.js @@ -0,0 +1,238 @@ +/* ***** 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 ***** */ +/* + * TODO: nim delimiters + */ + +define(function(require, exports, module) { +"use strict"; + +var oop = require("../lib/oop"); +var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; +var num_suffix = "(\'[Ff]32|\'[Ff]64|\'[IiUu]8|\'[IiUu]16|\'[IiUu]32|\'[IiUu]64)"; + +var NimHighlightRules = function() { + + var keywords = ( + "addr|and|as|asm|atomic|bind|block|break|case|cast|const|continue|" + + "converter|defer|discard|distinct|div|do|elif|else|end|enum|except|" + + "export|finally|for|from|func|generic|if|import|in|include|interface|" + + "is|isnot|iterator|let|macro|method|mixin|mod|nil|not|notin|object|of|" + + "or|out|proc|ptr|raise|ref|return|shl|shr|static|template|try|tuple|" + + "type|using|var|when|while|with|without|xor|yield" + ); + + var builtinConstants = ( + "true|false|nil|NotImplemented|Ellipsis" + ); + + var storageType = ( + "string|seq|array|expr|stmt|typed|untyped|any|auto|bool|cdouble|cfloat|"+ + "cchar|clongdouble|clong|clonglong|cshort|csize|cstring|cstringarray|"+ + "culong|culonglong|cushort|guarded|natural|openarray|ordinal|pointer|"+ + "range|set|shared|static|typedesc|varargs|void" + ); + + var builtinFunctions = ( + "defined|declared|declaredInScope|echo|$" + ); + + //var futureReserved = ""; + var keywordMapper = this.createKeywordMapper({ + "invalid.deprecated": "debugger", + "support.function": builtinFunctions, + //"invalid.illegal": futureReserved, + "constant.language": builtinConstants, + "storage.type" : storageType, + "keyword": keywords + }, "identifier"); + + var strPre = "(?:r|R)?"; + + var decimalInteger = "(?:(?:[1-9]\\d*)|(?:0))"; + var octInteger = "(?:0[o]?[0-7]+)"; + var hexInteger = "(?:0[xX][\\dA-Fa-f]+)"; + var binInteger = "(?:0[bB][01]+)"; + var integer = "(?:" + decimalInteger + "|" + octInteger + "|" + hexInteger + "|" + binInteger + ")"; + + var exponent = "(?:[eE][+-]?\\d+)"; + var fraction = "(?:\\.\\d+)"; + var intPart = "(?:\\d+)"; + var pointFloat = "(?:(?:" + intPart + "?" + fraction + ")|(?:" + intPart + "\\.))"; + var exponentFloat = "(?:(?:" + pointFloat + "|" + intPart + ")" + exponent + ")"; + var floatNumber = "(?:" + exponentFloat + "|" + pointFloat + ")"; + + var stringEscape = "\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv'\"]|U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})"; + + this.$rules = { + "start" : [ { + token : "comment", + regex : "#.*$" + }, { + token : "keyword", + regex : "(proc|method|temdplate|macro|macromethod|converter|func|iterator) ", + next : "qproc_name" + }, { + token : "storage.type", + regex : "(c|cu|u)?int(8|16|32|64)?", + }, { + token : "storage.type", + regex : "(c|cu|u|cs)?char", + }, { + token : "storage.type", + regex : "float(32|64)?", + }, { + token : "docstring", + regex : "##.*$" + }, { + token : "string", // multi line """ string start + regex : strPre + '"{3}', + next : "qqstring3" + }, { + token : "string", // " string + regex : strPre + '"(?=.)', + next : "qqstring" + }, { + token : "string", // multi line ''' string start + regex : strPre + "'{3}", + next : "qstring3" + }, { + token : "backtick", // ` string + regex : strPre + "`(?=.)", + next : "qxstring" + }, { + token : "string", // ' string + regex : strPre + "'(?=.)", + next : "qstring" + }, { + token : "constant.numeric", // imaginary + regex : "(?:" + floatNumber + "|\\d+)[jJ]\\b" + }, { + token : "constant.numeric", // float + regex : floatNumber + }, { + token : "constant.numeric", // long integer + regex : integer + "[lL]\\b" + }, { + token : "constant.numeric", // integer + regex : integer + "\\b" + }, { + token : keywordMapper, + regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" + }, { + token : "keyword.operator", + regex : "\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|%|<<|>>|&|\\||\\^|~|<|>|<=|=>|==|!=|<>|=" + }, { + token : "paren.lparen", + regex : "[\\[\\(\\{]" + }, { + token : "paren.rparen", + regex : "[\\]\\)\\}]" + }, { + token : "text", + regex : "\\s+" + } ], + "qqstring3" : [ { + token : "constant.language.escape", + regex : stringEscape + }, { + token : "string", // multi line """ string end + regex : '"{3}', + next : "start" + }, { + defaultToken : "string" + } ], + "qstring3" : [ { + token : "constant.language.escape", + regex : stringEscape + }, { + token : "string", // multi line ''' string end + regex : "'{3}", + next : "start" + }, { + defaultToken : "string" + } ], + "qqstring" : [{ + token : "constant.language.escape", + regex : stringEscape + }, { + token : "string", + regex : "\\\\$", + next : "qqstring" + }, { + token : "string", + regex : '"|$', + next : "start" + }, { + defaultToken: "string" + }], + "qstring" : [{ + token : "constant.language.escape", + regex : stringEscape + }, { + token : "string", + regex : "\\\\$", + next : "qstring" + }, { + token : "string", + regex : "'|$", + next : "start" + }, { + defaultToken: "string" + }], + "qxstring" : [{ + token : "constant.language.escape", + regex : stringEscape + }, { + token : "backtick", + regex : "\\\\$", + next : "qxstring" + }, { + token : "backtick", + regex : "`|$", + next : "start" + }, { + defaultToken: "backtick" + }], + "qproc_name":[{ + token : "proc_name", + regex : "\\w+", + },{ + token : "start_bracket", + regex : "(\\(|=|$)", + next : "start" + }], + }; +}; + +oop.inherits(NimHighlightRules, TextHighlightRules); + +exports.NimHighlightRules = NimHighlightRules; +}); diff --git a/lib/ace/mode/scheme.js b/lib/ace/mode/scheme.js index aa462a10..2360b3da 100644 --- a/lib/ace/mode/scheme.js +++ b/lib/ace/mode/scheme.js @@ -39,15 +39,92 @@ define(function(require, exports, module) { var oop = require("../lib/oop"); var TextMode = require("./text").Mode; var SchemeHighlightRules = require("./scheme_highlight_rules").SchemeHighlightRules; +var MatchingParensOutdent = require("./matching_parens_outdent").MatchingParensOutdent; var Mode = function() { this.HighlightRules = SchemeHighlightRules; + this.$outdent = new MatchingParensOutdent(); }; oop.inherits(Mode, TextMode); (function() { this.lineCommentStart = ";"; + this.minorIndentFunctions = ["define", "lambda", "define-macro", "define-syntax", "syntax-rules", "define-record-type", "define-structure"]; + + this.$toIndent = function(str) { + return str.split('').map(function(ch) { + if (/\s/.exec(ch)) { + return ch; + } else { + return ' '; + } + }).join(''); + }; + + this.$calculateIndent = function(line, tab) { + var baseIndent = this.$getIndent(line); + var delta = 0; + var isParen, ch; + // Walk back from end of line, find matching braces + for (var i = line.length - 1; i >= 0; i--) { + ch = line[i]; + if (ch === '(') { + delta--; + isParen = true; + } else if (ch === '(' || ch === '[' || ch === '{') { + delta--; + isParen = false; + } else if (ch === ')' || ch === ']' || ch === '}') { + delta++; + } + if (delta < 0) { + break; + } + } + if (delta < 0 && isParen) { + // Were more brackets opened than closed and was a ( left open? + i += 1; + var iBefore = i; + var fn = ''; + while (true) { + ch = line[i]; + if (ch === ' ' || ch === '\t') { + if(this.minorIndentFunctions.indexOf(fn) !== -1) { + return this.$toIndent(line.substring(0, iBefore - 1) + tab); + } else { + return this.$toIndent(line.substring(0, i + 1)); + } + } else if (ch === undefined) { + return this.$toIndent(line.substring(0, iBefore - 1) + tab); + } + fn += line[i]; + i++; + } + } else if(delta < 0 && !isParen) { + // Were more brackets openend than closed and was it not a (? + return this.$toIndent(line.substring(0, i+1)); + } else if(delta > 0) { + // Mere more brackets closed than opened? Outdent. + baseIndent = baseIndent.substring(0, baseIndent.length - tab.length); + return baseIndent; + } else { + // Were they nicely matched? Just indent like line before. + return baseIndent; + } + }; + + this.getNextLineIndent = function(state, line, tab) { + return this.$calculateIndent(line, tab); + }; + + this.checkOutdent = function(state, line, input) { + return this.$outdent.checkOutdent(line, input); + }; + + this.autoOutdent = function(state, doc, row) { + this.$outdent.autoOutdent(doc, row); + }; this.$id = "ace/mode/scheme"; }).call(Mode.prototype); diff --git a/lib/ace/mode/sqlserver.js b/lib/ace/mode/sqlserver.js index 0f66c1e5..5f24730c 100644 --- a/lib/ace/mode/sqlserver.js +++ b/lib/ace/mode/sqlserver.js @@ -46,7 +46,14 @@ oop.inherits(Mode, TextMode); (function() { this.lineCommentStart = "--"; this.blockComment = {start: "/*", end: "*/"}; - + + /** + * Override keyword completions using list created in highlight rules + */ + this.getCompletions = function(state, session, pos, prefix) { + return session.$mode.$highlightRules.completions; + }; + this.$id = "ace/mode/sql"; }).call(Mode.prototype); diff --git a/lib/ace/mode/sqlserver_highlight_rules.js b/lib/ace/mode/sqlserver_highlight_rules.js index 42e193d7..c914c0ae 100644 --- a/lib/ace/mode/sqlserver_highlight_rules.js +++ b/lib/ace/mode/sqlserver_highlight_rules.js @@ -77,22 +77,24 @@ var SqlServerHighlightRules = function() { /* https://msdn.microsoft.com/en-us/library/ms177520.aspx */ "@@CONNECTIONS|@@CPU_BUSY|@@IDLE|@@IO_BUSY|@@PACKET_ERRORS|@@PACK_RECEIVED|@@PACK_SENT|@@TIMETICKS|@@TOTAL_ERRORS|@@TOTAL_READ|@@TOTAL_WRITE|FN_VIRTUALFILESTATS|" + /* https://msdn.microsoft.com/en-us/library/ms188353.aspx */ - "PATINDEX|TEXTPTR|TEXTVALID" + "PATINDEX|TEXTPTR|TEXTVALID|" + + /* other */ + "COALESCE|NULLIF" ); // https://msdn.microsoft.com/en-us/library/ms187752.aspx var dataTypes = ("BIGINT|BINARY|BIT|CHAR|CURSOR|DATE|DATETIME|DATETIME2|DATETIMEOFFSET|DECIMAL|FLOAT|HIERARCHYID|IMAGE|INTEGER|INT|MONEY|NCHAR|NTEXT|NUMERIC|NVARCHAR|REAL|SMALLDATETIME|SMALLINT|SMALLMONEY|SQL_VARIANT|TABLE|TEXT|TIME|TIMESTAMP|TINYINT|UNIQUEIDENTIFIER|VARBINARY|VARCHAR|XML"); - + //https://msdn.microsoft.com/en-us/library/ms176007.aspx (these are lower case!) var builtInStoredProcedures = "sp_addextendedproc|sp_addextendedproperty|sp_addmessage|sp_addtype|sp_addumpdevice|sp_add_data_file_recover_suspect_db|sp_add_log_file_recover_suspect_db|sp_altermessage|sp_attach_db|sp_attach_single_file_db|sp_autostats|sp_bindefault|sp_bindrule|sp_bindsession|sp_certify_removable|sp_clean_db_file_free_space|sp_clean_db_free_space|sp_configure|sp_control_plan_guide|sp_createstats|sp_create_plan_guide|sp_create_plan_guide_from_handle|sp_create_removable|sp_cycle_errorlog|sp_datatype_info|sp_dbcmptlevel|sp_dbmmonitoraddmonitoring|sp_dbmmonitorchangealert|sp_dbmmonitorchangemonitoring|sp_dbmmonitordropalert|sp_dbmmonitordropmonitoring|sp_dbmmonitorhelpalert|sp_dbmmonitorhelpmonitoring|sp_dbmmonitorresults|sp_db_increased_partitions|sp_delete_backuphistory|sp_depends|sp_describe_first_result_set|sp_describe_undeclared_parameters|sp_detach_db|sp_dropdevice|sp_dropextendedproc|sp_dropextendedproperty|sp_dropmessage|sp_droptype|sp_execute|sp_executesql|sp_getapplock|sp_getbindtoken|sp_help|sp_helpconstraint|sp_helpdb|sp_helpdevice|sp_helpextendedproc|sp_helpfile|sp_helpfilegroup|sp_helpindex|sp_helplanguage|sp_helpserver|sp_helpsort|sp_helpstats|sp_helptext|sp_helptrigger|sp_indexoption|sp_invalidate_textptr|sp_lock|sp_monitor|sp_prepare|sp_prepexec|sp_prepexecrpc|sp_procoption|sp_recompile|sp_refreshview|sp_releaseapplock|sp_rename|sp_renamedb|sp_resetstatus|sp_sequence_get_range|sp_serveroption|sp_setnetname|sp_settriggerorder|sp_spaceused|sp_tableoption|sp_unbindefault|sp_unbindrule|sp_unprepare|sp_updateextendedproperty|sp_updatestats|sp_validname|sp_who|sys.sp_merge_xtp_checkpoint_files|sys.sp_xtp_bind_db_resource_pool|sys.sp_xtp_checkpoint_force_garbage_collection|sys.sp_xtp_control_proc_exec_stats|sys.sp_xtp_control_query_exec_stats|sys.sp_xtp_unbind_db_resource_pool"; - + // https://msdn.microsoft.com/en-us/library/ms189822.aspx var keywords = "ABSOLUTE|ACTION|ADA|ADD|ADMIN|AFTER|AGGREGATE|ALIAS|ALL|ALLOCATE|ALTER|AND|ANY|ARE|ARRAY|AS|ASC|ASENSITIVE|ASSERTION|ASYMMETRIC|AT|ATOMIC|AUTHORIZATION|BACKUP|BEFORE|BEGIN|BETWEEN|BIT_LENGTH|BLOB|BOOLEAN|BOTH|BREADTH|BREAK|BROWSE|BULK|BY|CALL|CALLED|CARDINALITY|CASCADE|CASCADED|CASE|CATALOG|CHARACTER|CHARACTER_LENGTH|CHAR_LENGTH|CHECK|CHECKPOINT|CLASS|CLOB|CLOSE|CLUSTERED|COALESCE|COLLATE|COLLATION|COLLECT|COLUMN|COMMIT|COMPLETION|COMPUTE|CONDITION|CONNECT|CONNECTION|CONSTRAINT|CONSTRAINTS|CONSTRUCTOR|CONTAINS|CONTAINSTABLE|CONTINUE|CORR|CORRESPONDING|COVAR_POP|COVAR_SAMP|CREATE|CROSS|CUBE|CUME_DIST|CURRENT|CURRENT_CATALOG|CURRENT_DATE|CURRENT_DEFAULT_TRANSFORM_GROUP|CURRENT_PATH|CURRENT_ROLE|CURRENT_SCHEMA|CURRENT_TIME|CURRENT_TRANSFORM_GROUP_FOR_TYPE|CYCLE|DATA|DATABASE|DBCC|DEALLOCATE|DEC|DECLARE|DEFAULT|DEFERRABLE|DEFERRED|DELETE|DENY|DEPTH|DEREF|DESC|DESCRIBE|DESCRIPTOR|DESTROY|DESTRUCTOR|DETERMINISTIC|DIAGNOSTICS|DICTIONARY|DISCONNECT|DISK|DISTINCT|DISTRIBUTED|DOMAIN|DOUBLE|DROP|DUMP|DYNAMIC|EACH|ELEMENT|ELSE|END|END-EXEC|EQUALS|ERRLVL|ESCAPE|EVERY|EXCEPT|EXCEPTION|EXEC|EXECUTE|EXISTS|EXIT|EXTERNAL|EXTRACT|FETCH|FILE|FILLFACTOR|FILTER|FIRST|FOR|FOREIGN|FORTRAN|FOUND|FREE|FREETEXT|FREETEXTTABLE|FROM|FULL|FULLTEXTTABLE|FUNCTION|FUSION|GENERAL|GET|GLOBAL|GO|GOTO|GRANT|GROUP|HAVING|HOLD|HOLDLOCK|HOST|HOUR|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IGNORE|IMMEDIATE|IN|INCLUDE|INDEX|INDICATOR|INITIALIZE|INITIALLY|INNER|INOUT|INPUT|INSENSITIVE|INSERT|INTEGER|INTERSECT|INTERSECTION|INTERVAL|INTO|IS|ISOLATION|ITERATE|JOIN|KEY|KILL|LANGUAGE|LARGE|LAST|LATERAL|LEADING|LESS|LEVEL|LIKE|LIKE_REGEX|LIMIT|LINENO|LN|LOAD|LOCAL|LOCALTIME|LOCALTIMESTAMP|LOCATOR|MAP|MATCH|MEMBER|MERGE|METHOD|MINUTE|MOD|MODIFIES|MODIFY|MODULE|MULTISET|NAMES|NATIONAL|NATURAL|NCLOB|NEW|NEXT|NO|NOCHECK|NONCLUSTERED|NONE|NORMALIZE|NOT|NULL|NULLIF|OBJECT|OCCURRENCES_REGEX|OCTET_LENGTH|OF|OFF|OFFSETS|OLD|ON|ONLY|OPEN|OPERATION|OPTION|OR|ORDER|ORDINALITY|OUT|OUTER|OUTPUT|OVER|OVERLAPS|OVERLAY|PAD|PARAMETER|PARAMETERS|PARTIAL|PARTITION|PASCAL|PATH|PERCENT|PERCENTILE_CONT|PERCENTILE_DISC|PERCENT_RANK|PIVOT|PLAN|POSITION|POSITION_REGEX|POSTFIX|PRECISION|PREFIX|PREORDER|PREPARE|PRESERVE|PRIMARY|PRINT|PRIOR|PRIVILEGES|PROC|PROCEDURE|PUBLIC|RAISERROR|RANGE|READ|READS|READTEXT|RECONFIGURE|RECURSIVE|REF|REFERENCES|REFERENCING|REGR_AVGX|REGR_AVGY|REGR_COUNT|REGR_INTERCEPT|REGR_R2|REGR_SLOPE|REGR_SXX|REGR_SXY|REGR_SYY|RELATIVE|RELEASE|REPLICATION|RESTORE|RESTRICT|RESULT|RETURN|RETURNS|REVERT|REVOKE|ROLE|ROLLBACK|ROLLUP|ROUTINE|ROW|ROWCOUNT|ROWGUIDCOL|ROWS|RULE|SAVE|SAVEPOINT|SCHEMA|SCOPE|SCROLL|SEARCH|SECOND|SECTION|SECURITYAUDIT|SELECT|SEMANTICKEYPHRASETABLE|SEMANTICSIMILARITYDETAILSTABLE|SEMANTICSIMILARITYTABLE|SENSITIVE|SEQUENCE|SESSION|SET|SETS|SETUSER|SHUTDOWN|SIMILAR|SIZE|SOME|SPECIFIC|SPECIFICTYPE|SQL|SQLCA|SQLCODE|SQLERROR|SQLEXCEPTION|SQLSTATE|SQLWARNING|START|STATE|STATEMENT|STATIC|STATISTICS|STDDEV_POP|STDDEV_SAMP|STRUCTURE|SUBMULTISET|SUBSTRING_REGEX|SYMMETRIC|SYSTEM|TABLESAMPLE|TEMPORARY|TERMINATE|TEXTSIZE|THAN|THEN|TIMEZONE_HOUR|TIMEZONE_MINUTE|TO|TOP|TRAILING|TRAN|TRANSACTION|TRANSLATE|TRANSLATE_REGEX|TRANSLATION|TREAT|TRIGGER|TRIM|TRUNCATE|TSEQUAL|UESCAPE|UNDER|UNION|UNIQUE|UNKNOWN|UNNEST|UNPIVOT|UPDATE|UPDATETEXT|USAGE|USE|USER|USING|VALUE|VALUES|VARIABLE|VARYING|VAR_POP|VAR_SAMP|VIEW|WAITFOR|WHEN|WHENEVER|WHERE|WHILE|WIDTH_BUCKET|WINDOW|WITH|WITHIN|WITHIN GROUP|WITHOUT|WORK|WRITE|WRITETEXT|XMLAGG|XMLATTRIBUTES|XMLBINARY|XMLCAST|XMLCOMMENT|XMLCONCAT|XMLDOCUMENT|XMLELEMENT|XMLEXISTS|XMLFOREST|XMLITERATE|XMLNAMESPACES|XMLPARSE|XMLPI|XMLQUERY|XMLSERIALIZE|XMLTABLE|XMLTEXT|XMLVALIDATE|ZONE"; - + // Microsoft's keyword list is missing a lot of things that are located on various other pages // https://msdn.microsoft.com/en-us/library/ms187373.aspx, https://msdn.microsoft.com/en-us/library/ms181714.aspx keywords += "|KEEPIDENTITY|KEEPDEFAULTS|IGNORE_CONSTRAINTS|IGNORE_TRIGGERS|XLOCK|FORCESCAN|FORCESEEK|HOLDLOCK|NOLOCK|NOWAIT|PAGLOCK|READCOMMITTED|READCOMMITTEDLOCK|READPAST|READUNCOMMITTED|REPEATABLEREAD|ROWLOCK|SERIALIZABLE|SNAPSHOT|SPATIAL_WINDOW_MAX_CELLS|TABLOCK|TABLOCKX|UPDLOCK|XLOCK|IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX|EXPAND|VIEWS|FAST|FORCE|KEEP|KEEPFIXED|MAXDOP|MAXRECURSION|OPTIMIZE|PARAMETERIZATION|SIMPLE|FORCED|RECOMPILE|ROBUST|PLAN|SPATIAL_WINDOW_MAX_CELLS|NOEXPAND|HINT"; @@ -103,13 +105,15 @@ var SqlServerHighlightRules = function() { // highlighted words in SSMS that I'm not even sure where they come from keywords += "|TYPE"; - - //remove any other built in things from key word list + + //remove specific built in types from keyword list keywords = keywords.split('|'); keywords = keywords.filter(function(value, index, self) { return logicalOperators.split('|').indexOf(value) === -1 && builtinFunctions.split('|').indexOf(value) === -1 && dataTypes.split('|').indexOf(value) === -1; }); keywords = keywords.sort().join('|'); + + var keywordMapper = this.createKeywordMapper({ "constant.language": logicalOperators, "storage.type": dataTypes, @@ -117,37 +121,23 @@ var SqlServerHighlightRules = function() { "support.storedprocedure": builtInStoredProcedures, "keyword": keywords, }, "identifier", true); - - - // createKeywordMapper ignores case which we want because SqlServer keywords are not case sensitive which causes our keywords to get changed to lowercase. - // However, the preferred standard for keywords is uppercase, so this transforms them back to uppercase for code completion - // EXCEPTION: build in stored procedures are lower case - for (var i = 0; i < this.$keywordList.length; i++) { - var keyword = this.$keywordList[i]; - if (builtInStoredProcedures.indexOf(keyword) !== -1) continue; - this.$keywordList[i] = keyword.toUpperCase(); - } - - + + //https://msdn.microsoft.com/en-us/library/ms190356.aspx var setStatements = "SET ANSI_DEFAULTS|SET ANSI_NULLS|SET ANSI_NULL_DFLT_OFF|SET ANSI_NULL_DFLT_ON|SET ANSI_PADDING|SET ANSI_WARNINGS|SET ARITHABORT|SET ARITHIGNORE|SET CONCAT_NULL_YIELDS_NULL|SET CURSOR_CLOSE_ON_COMMIT|SET DATEFIRST|SET DATEFORMAT|SET DEADLOCK_PRIORITY|SET FIPS_FLAGGER|SET FMTONLY|SET FORCEPLAN|SET IDENTITY_INSERT|SET IMPLICIT_TRANSACTIONS|SET LANGUAGE|SET LOCK_TIMEOUT|SET NOCOUNT|SET NOEXEC|SET NUMERIC_ROUNDABORT|SET OFFSETS|SET PARSEONLY|SET QUERY_GOVERNOR_COST_LIMIT|SET QUOTED_IDENTIFIER|SET REMOTE_PROC_TRANSACTIONS|SET ROWCOUNT|SET SHOWPLAN_ALL|SET SHOWPLAN_TEXT|SET SHOWPLAN_XML|SET STATISTICS IO|SET STATISTICS PROFILE|SET STATISTICS TIME|SET STATISTICS XML|SET TEXTSIZE|SET XACT_ABORT".split('|'); var isolationLevels = "READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SNAPSHOP|SERIALIZABLE".split('|'); for (var i = 0; i < isolationLevels.length; i++) { setStatements.push('SET TRANSACTION ISOLATION LEVEL ' + isolationLevels[i]); } - //add set statements to keywordList for completions - for (var i = 0; i < setStatements.length; i++) { - this.$keywordList.push(setStatements[i]); - } - + this.$rules = { start: [{ token: "string.start", regex: "'", next: [{ token: "constant.language.escape", - regex: /\\'/ + regex: /''/ }, { token: "string.end", next: "start", @@ -163,15 +153,12 @@ var SqlServerHighlightRules = function() { token: "comment", start: "/\\*", end: "\\*/" - }, { - token: "string", // ' string - regex: "'.*?'" }, { token: "constant.numeric", // float regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b" }, { token: keywordMapper, - regex: "@{0,2}[a-zA-Z_$][a-zA-Z0-9_$]*\\b" //up to 2 @symbols for some build in functions + regex: "@{0,2}[a-zA-Z_$][a-zA-Z0-9_$]*\\b(?!])" //up to 2 @symbols for some built in functions }, { token: "constant.class", regex: "@@?[a-zA-Z_$][a-zA-Z0-9_$]*\\b" @@ -202,20 +189,41 @@ var SqlServerHighlightRules = function() { caseInsensitive: true }], }; - + //add each set statment as regex at top of rules so that they are processed first because they require multiple words - //note, this makes the statements not match if they are not upper case.. which is not ideal but I don't know of an easy way to fix this + //note: this makes the statements not match if they are not upper case.. which is not ideal but I don't know of an easy way to fix this for (var i = 0; i < setStatements.length; i++) { - var statement = setStatements[i]; this.$rules.start.unshift({ token: "set.statement", - regex: statement + regex: setStatements[i] }); } - + this.embedRules(DocCommentHighlightRules, "doc-", [DocCommentHighlightRules.getEndRule("start")]); - this.normalizeRules(); + + + //prepare custom keyword completions used by mode to override default completor + //this allows for custom 'meta' and proper case of completions + var completions = []; + var addCompletions = function(arr, meta) { + arr.forEach(function(v) { + completions.push({ + name: v, + value: v, + score: 0, + meta: meta, + }); + }); + }; + addCompletions(builtInStoredProcedures.split('|'), 'procedure'); + addCompletions(logicalOperators.split('|'), 'operator'); + addCompletions(builtinFunctions.split('|'), 'function'); + addCompletions(dataTypes.split('|'), 'type'); + addCompletions(setStatements, 'statement'); + addCompletions(keywords.split('|'), 'keyword'); + + this.completions = completions; }; oop.inherits(SqlServerHighlightRules, TextHighlightRules); diff --git a/lib/ace/mode/toml_highlight_rules.js b/lib/ace/mode/toml_highlight_rules.js index 686ffae2..1a7871ab 100644 --- a/lib/ace/mode/toml_highlight_rules.js +++ b/lib/ace/mode/toml_highlight_rules.js @@ -57,6 +57,10 @@ var TomlHighlightRules = function() { regex : '"(?=.)', next : "qqstring" }, + { + token: ["variable.keygroup.toml"], + regex: "(?:^\\s*)(\\[\\[([^\\]]+)\\]\\])" + }, { token: ["variable.keygroup.toml"], regex: "(?:^\\s*)(\\[([^\\]]+)\\])" @@ -100,4 +104,4 @@ var TomlHighlightRules = function() { oop.inherits(TomlHighlightRules, TextHighlightRules); exports.TomlHighlightRules = TomlHighlightRules; -}); \ No newline at end of file +}); diff --git a/lib/ace/mode/xml.js b/lib/ace/mode/xml.js index 8c7033f1..38861eee 100644 --- a/lib/ace/mode/xml.js +++ b/lib/ace/mode/xml.js @@ -53,7 +53,7 @@ oop.inherits(Mode, TextMode); this.blockComment = {start: ""}; - this.createWorker = function(session) { + this.createWorker = function(session) { var worker = new WorkerClient(["ace"], "ace/mode/xml_worker", "Worker"); worker.attachToDocument(session.getDocument()); diff --git a/lib/ace/mouse/multi_select_handler.js b/lib/ace/mouse/multi_select_handler.js index 45704b39..649043fa 100644 --- a/lib/ace/mouse/multi_select_handler.js +++ b/lib/ace/mouse/multi_select_handler.js @@ -83,7 +83,7 @@ function onMouseDown(e) { var selectionMode; if (editor.$mouseHandler.$enableJumpToDef) { if (ctrl && alt || accel && alt) - selectionMode = "add"; + selectionMode = shift ? "block" : "add"; else if (alt && editor.$blockSelectEnabled) selectionMode = "block"; } else { @@ -117,7 +117,7 @@ function onMouseDown(e) { if (shift) { oldRange = null; - range = selection.ranges[0]; + range = selection.ranges[0] || range; editor.removeSelectionMarker(range); } editor.once("mouseup", function() { diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 85611a33..1d0b69e7 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -754,9 +754,9 @@ var Editor = require("./editor").Editor; if (fr < 0) fr = 0; if (lr >= max) lr = max - 1; } - var lines = this.session.doc.removeLines(fr, lr); + var lines = this.session.removeFullLines(fr, lr); lines = this.$reAlignText(lines, guessRange); - this.session.doc.insert({row: fr, column: 0}, lines.join("\n") + "\n"); + this.session.insert({row: fr, column: 0}, lines.join("\n") + "\n"); if (!guessRange) { range.start.column = 0; range.end.column = lines[lines.length - 1].length; diff --git a/lib/ace/multi_select_test.js b/lib/ace/multi_select_test.js index 1eccc4ce..7fd504fb 100644 --- a/lib/ace/multi_select_test.js +++ b/lib/ace/multi_select_test.js @@ -30,6 +30,7 @@ if (typeof process !== "undefined") { require("amd-loader"); + require("./test/mockdom"); } define(function(require, exports, module) { @@ -41,6 +42,7 @@ var Range = require("./range").Range; var Editor = require("./editor").Editor; var EditSession = require("./edit_session").EditSession; var MockRenderer = require("./test/mockrenderer").MockRenderer; +var UndoManager = require("./undomanager").UndoManager; var editor; var exec = function(name, times, args) { @@ -82,11 +84,11 @@ function setSelection(editor, data) { return isBackwards ? { start: end, end: start, - isBackwards: true + isBackwards: isBackwards } : { start: start, end: end, - isBackwards: true + isBackwards: isBackwards }; })); } @@ -223,19 +225,19 @@ module.exports = { assert.equal(editor.getValue(),"l1\nl1\nl2\nl2\nl3\nl3\nl4\nl4"); testSelection(editor, [[1,0],[3,0],[5,0],[7,0]]); - setSelection(editor, [[1,2],[1,0,1,1],[3,0,3,1],[5,0,5,1],[7,0,7,1]]); + setSelection(editor, [[1,2],[1,1,1,0],[3,0,3,1],[5,0,5,1],[7,0,7,1]]); exec("copylinesdown"); exec("copylinesup"); assert.equal(editor.getValue(),"l1\nl1\nl1\nl1\nl2\nl2\nl2\nl2\nl3\nl3\nl3\nl3\nl4\nl4\nl4\nl4"); - testSelection(editor, [[2,2],[2,0,2,1],[6,0,6,1],[10,0,10,1],[14,0,14,1]]); + testSelection(editor, [[2,2],[2,1,2,0],[6,0,6,1],[10,0,10,1],[14,0,14,1]]); exec("movelinesdown", 12); assert.equal(editor.getValue(),"l1\nl1\nl1\nl2\nl2\nl2\nl3\nl3\nl3\nl4\nl4\nl4\nl1\nl2\nl3\nl4"); - testSelection(editor, [[12,2],[12,0,12,1],[13,0,13,1],[14,0,14,1],[15,0,15,1]]); + testSelection(editor, [[12,2],[12,1,12,0],[13,0,13,1],[14,0,14,1],[15,0,15,1]]); exec("movelinesup", 12); assert.equal(editor.getValue(),"l1\nl2\nl3\nl4\nl1\nl1\nl1\nl2\nl2\nl2\nl3\nl3\nl3\nl4\nl4\nl4"); - testSelection(editor, [[0,2],[0,0,0,1],[1,0,1,1],[2,0,2,1],[3,0,3,1]]); + testSelection(editor, [[0,2],[0,1,0,0],[1,0,1,1],[2,0,2,1],[3,0,3,1]]); }, "test multiselect fromJSON/toJSON": function() { @@ -259,6 +261,20 @@ module.exports = { selection.fromJSON(after); assert.ok(!selection.isEqual(before)); assert.ok(selection.isEqual(after)); + }, + + "test multiselect align": function() { + var doc = new EditSession(["l1", "l2", "l3"]); + doc.setUndoManager(new UndoManager()); + editor = new Editor(new MockRenderer(), doc); + var selection = editor.selection; + selection.addRange(new Range(1,0,1,0)) + selection.addRange(new Range(2,2,2,2)) + editor.execCommand("alignCursors"); + assert.equal(' l1\n l2\nl3', editor.getValue()); + doc.markUndoGroup(); + editor.execCommand("undo"); + assert.equal('l1\nl2\nl3', editor.getValue()); } }; diff --git a/lib/ace/placeholder.js b/lib/ace/placeholder.js index 582e2c2b..0e299594 100644 --- a/lib/ace/placeholder.js +++ b/lib/ace/placeholder.js @@ -150,28 +150,27 @@ var PlaceHolder = function(session, length, pos, others, mainClass, othersClass) * Emitted when the place holder updates. * **/ - this.onUpdate = function(event) { - var delta = event.data; - var range = delta.range; + this.onUpdate = function(delta) { + var range = delta; if(range.start.row !== range.end.row) return; if(range.start.row !== this.pos.row) return; if (this.$updating) return; this.$updating = true; - var lengthDiff = delta.action === "insertText" ? range.end.column - range.start.column : range.start.column - range.end.column; + var lengthDiff = delta.action === "insert" ? range.end.column - range.start.column : range.start.column - range.end.column; if(range.start.column >= this.pos.column && range.start.column <= this.pos.column + this.length + 1) { var distanceFromStart = range.start.column - this.pos.column; this.length += lengthDiff; if(!this.session.$fromUndo) { - if(delta.action === "insertText") { + if(delta.action === 'insert') { for (var i = this.others.length - 1; i >= 0; i--) { var otherPos = this.others[i]; var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart}; if(otherPos.row === range.start.row && range.start.column < otherPos.column) newPos.column += lengthDiff; - this.doc.insert(newPos, delta.text); + this.doc.insertMergedLines(newPos, delta.lines); } - } else if(delta.action === "removeText") { + } else if(delta.action === 'remove') { for (var i = this.others.length - 1; i >= 0; i--) { var otherPos = this.others[i]; var newPos = {row: otherPos.row, column: otherPos.column + distanceFromStart}; @@ -181,7 +180,7 @@ var PlaceHolder = function(session, length, pos, others, mainClass, othersClass) } } // Special case: insert in beginning - if(range.start.column === this.pos.column && delta.action === "insertText") { + if(range.start.column === this.pos.column && delta.action === 'insert') { setTimeout(function() { this.pos.setPosition(this.pos.row, this.pos.column - lengthDiff); for (var i = 0; i < this.others.length; i++) { @@ -193,7 +192,7 @@ var PlaceHolder = function(session, length, pos, others, mainClass, othersClass) } }.bind(this), 0); } - else if(range.start.column === this.pos.column && delta.action === "removeText") { + else if(range.start.column === this.pos.column && delta.action === 'remove') { setTimeout(function() { for (var i = 0; i < this.others.length; i++) { var other = this.others[i]; diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index 0cbcc394..326bc41b 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -180,14 +180,13 @@ var RangeList = function() { this.session = null; }; - this.$onChange = function(e) { - var changeRange = e.data.range; - if (e.data.action[0] == "i"){ - var start = changeRange.start; - var end = changeRange.end; + this.$onChange = function(delta) { + if (delta.action == "insert"){ + var start = delta.start; + var end = delta.end; } else { - var end = changeRange.start; - var start = changeRange.end; + var end = delta.start; + var start = delta.end; } var startRow = start.row; var endRow = end.row; diff --git a/lib/ace/selection.js b/lib/ace/selection.js index b712fc6b..ed57682f 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -923,7 +923,7 @@ var Selection = function(session) { this.toSingleRange(data[0]); for (var i = data.length; i--; ) { var r = Range.fromPoints(data[i].start, data[i].end); - if (data.isBackwards) + if (data[i].isBackwards) r.cursor = r.start; this.addRange(r, true); } diff --git a/lib/ace/snippets.js b/lib/ace/snippets.js index 10c34bf0..699cd6e9 100644 --- a/lib/ace/snippets.js +++ b/lib/ace/snippets.js @@ -667,11 +667,11 @@ var TabstopManager = function(editor) { this.editor = null; }; - this.onChange = function(e) { - var changeRange = e.data.range; - var isRemove = e.data.action[0] == "r"; - var start = changeRange.start; - var end = changeRange.end; + this.onChange = function(delta) { + var changeRange = delta; + var isRemove = delta.action[0] == "r"; + var start = delta.start; + var end = delta.end; var startRow = start.row; var endRow = end.row; var lineDif = endRow - startRow; diff --git a/lib/ace/snippets/mask.js b/lib/ace/snippets/mask.js new file mode 100644 index 00000000..7fbca678 --- /dev/null +++ b/lib/ace/snippets/mask.js @@ -0,0 +1,7 @@ +define(function(require, exports, module) { +"use strict"; + +exports.snippetText = require("../requirejs/text!./mask.snippets"); +exports.scope = "mask"; + +}); diff --git a/lib/ace/snippets/mask.snippets b/lib/ace/snippets/mask.snippets new file mode 100644 index 00000000..e69de29b diff --git a/lib/ace/snippets/nim.js b/lib/ace/snippets/nim.js new file mode 100644 index 00000000..6e6d02bb --- /dev/null +++ b/lib/ace/snippets/nim.js @@ -0,0 +1,7 @@ +define(function(require, exports, module) { +"use strict"; + +exports.snippetText = require("../requirejs/text!./nim.snippets"); +exports.scope = "nim"; + +}); diff --git a/lib/ace/snippets/nim.snippets b/lib/ace/snippets/nim.snippets new file mode 100644 index 00000000..a8a20a00 --- /dev/null +++ b/lib/ace/snippets/nim.snippets @@ -0,0 +1,130 @@ +snippet #! + #!/usr/bin/env nim +snippet imp + import ${1:module} +snippet from + from ${1:package} import ${2:module} +# Module Docstring +snippet docs + ## File: ${1:FILENAME:file_name} + ## Author: ${2:author} + ## Description: ${3} +snippet wh + while ${1:condition}: + ${2:# TODO: write code...} +# dowh - does the same as do...while in other languages +snippet dowh + while true: + ${1:# TODO: write code...} + if ${2:condition}: + break +snippet with + with ${1:expr} as ${2:var}: + ${3:# TODO: write code...} +# New Function +snippet proc + proc ${1:fname}(${2:`indent('.') ? 'self' : ''`}): ${3: return type} = + ##${4:docstring for $1} + ${5:# TODO: write code...} +snippet deff + def ${1:fname}(${2:`indent('.') ? 'self' : ''`}): + ${3:# TODO: write code...} +# New Method +snippet defs + def ${1:mname}(self, ${2:arg}): + ${3:# TODO: write code...} +# New Property +snippet property + def ${1:foo}(): + doc = "${2:The $1 property.}" + def fget(self): + ${3:return self._$1} + def fset(self, value): + ${4:self._$1 = value} +# Ifs +snippet if + if ${1:condition}: + ${2:# TODO: write code...} +snippet el + else: + ${1:# TODO: write code...} +snippet ei + elif ${1:condition}: + ${2:# TODO: write code...} +# For +snippet for + for ${1:item} in ${2:items}: + ${3:# TODO: write code...} +# Encodes +snippet cutf8 + # -*- coding: utf-8 -*- +snippet clatin1 + # -*- coding: latin-1 -*- +snippet cascii + # -*- coding: ascii -*- +# Lambda +snippet ld + ${1:var} = lambda ${2:vars} : ${3:action} +snippet . + self. +snippet try Try/Except + try: + ${1:# TODO: write code...} + except ${2:Exception}, ${3:e}: + ${4:raise $3} +snippet try Try/Except/Else + try: + ${1:# TODO: write code...} + except ${2:Exception}, ${3:e}: + ${4:raise $3} + else: + ${5:# TODO: write code...} +snippet try Try/Except/Finally + try: + ${1:# TODO: write code...} + except ${2:Exception}, ${3:e}: + ${4:raise $3} + finally: + ${5:# TODO: write code...} +snippet try Try/Except/Else/Finally + try: + ${1:# TODO: write code...} + except ${2:Exception}, ${3:e}: + ${4:raise $3} + else: + ${5:# TODO: write code...} + finally: + ${6:# TODO: write code...} +# if __name__ == '__main__': +snippet ifmain + if isMainModule: + ${1:main()} +snippet " + ## ${1:doc} +# test function/method +snippet test + def test_${1:description}(${2:self}): + ${3:# TODO: write code...} +# test case +snippet testcase + class ${1:ExampleCase}(unittest.TestCase): + + def test_${2:description}(self): + ${3:# TODO: write code...} +#getopt +snippet getopt + try: + # Short option syntax: "hv:" + # Long option syntax: "help" or "verbose=" + opts, args = getopt.getopt(sys.argv[1:], "${1:short_options}", [${2:long_options}]) + + except getopt.GetoptError, err: + # Print debug info + print str(err) + ${3:error_action} + + for option, argument in opts: + if option in ("-h", "--help"): + ${4} + elif option in ("-v", "--verbose"): + verbose = argument diff --git a/lib/ace/snippets/sqlserver.snippets b/lib/ace/snippets/sqlserver.snippets index 51c00ae4..403bd6bc 100644 --- a/lib/ace/snippets/sqlserver.snippets +++ b/lib/ace/snippets/sqlserver.snippets @@ -22,6 +22,16 @@ snippet dateadd # DATEFROMPARTS snippet datefromparts DATEFROMPARTS(${1:year}, ${2:month}, ${3:day}) +# OBJECT_DEFINITION +snippet objectdef + SELECT OBJECT_DEFINITION(OBJECT_ID('${1:sys.server_permissions /*object name*/}')) +# STUFF XML +snippet stuffxml + STUFF((SELECT ', ' + ${1:ColumnName} + FROM ${2:TableName} + WHERE ${3:WhereClause} + FOR XML PATH('')), 1, 1, '') AS ${4:Alias} + ${5:/*https://msdn.microsoft.com/en-us/library/ms188043.aspx*/} # Create Procedure snippet createproc -- ============================================= @@ -30,12 +40,14 @@ snippet createproc -- Description: ${3:Description} -- ============================================= CREATE PROCEDURE ${4:Procedure_Name} - -- Add the parameters for the stored procedure here + ${5:/*Add the parameters for the stored procedure here*/} AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements. SET NOCOUNT ON; + ${6:/*Add the T-SQL statements to compute the return value here*/} + END GO # Create Scalar Function @@ -52,5 +64,7 @@ snippet createfn BEGIN DECLARE @Result ${5:Function_Data_Type} + ${6:/*Add the T-SQL statements to compute the return value here*/} + END GO \ No newline at end of file diff --git a/lib/ace/theme/clouds.css b/lib/ace/theme/clouds.css index e3884e02..c11308d4 100644 --- a/lib/ace/theme/clouds.css +++ b/lib/ace/theme/clouds.css @@ -23,7 +23,6 @@ .ace-clouds.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #FFFFFF; - border-radius: 2px } .ace-clouds .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/clouds_midnight.css b/lib/ace/theme/clouds_midnight.css index 3b932996..08ee089d 100644 --- a/lib/ace/theme/clouds_midnight.css +++ b/lib/ace/theme/clouds_midnight.css @@ -23,7 +23,6 @@ .ace-clouds-midnight.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #191919; - border-radius: 2px } .ace-clouds-midnight .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/cobalt.css b/lib/ace/theme/cobalt.css index 73140082..b883ba97 100644 --- a/lib/ace/theme/cobalt.css +++ b/lib/ace/theme/cobalt.css @@ -23,7 +23,6 @@ .ace-cobalt.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #002240; - border-radius: 2px } .ace-cobalt .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/dawn.css b/lib/ace/theme/dawn.css index 719f4877..3c2884e1 100644 --- a/lib/ace/theme/dawn.css +++ b/lib/ace/theme/dawn.css @@ -23,7 +23,6 @@ .ace-dawn.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #F9F9F9; - border-radius: 2px } .ace-dawn .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/idle_fingers.css b/lib/ace/theme/idle_fingers.css index f507ec10..b5e1bff2 100644 --- a/lib/ace/theme/idle_fingers.css +++ b/lib/ace/theme/idle_fingers.css @@ -23,7 +23,6 @@ .ace-idle-fingers.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #323232; - border-radius: 2px } .ace-idle-fingers .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/katzenmilch.css b/lib/ace/theme/katzenmilch.css index 0b5a7a23..bf1569fb 100644 --- a/lib/ace/theme/katzenmilch.css +++ b/lib/ace/theme/katzenmilch.css @@ -31,7 +31,6 @@ .ace-katzenmilch.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #f3f2f3; - border-radius: 2px } .ace-katzenmilch .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/kr_theme.css b/lib/ace/theme/kr_theme.css index 339786ad..e232de47 100644 --- a/lib/ace/theme/kr_theme.css +++ b/lib/ace/theme/kr_theme.css @@ -23,7 +23,6 @@ .ace-kr-theme.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #0B0A09; - border-radius: 2px } .ace-kr-theme .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/merbivore.css b/lib/ace/theme/merbivore.css index a84ec3a6..9be25c66 100644 --- a/lib/ace/theme/merbivore.css +++ b/lib/ace/theme/merbivore.css @@ -23,7 +23,6 @@ .ace-merbivore.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #161616; - border-radius: 2px } .ace-merbivore .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/merbivore_soft.css b/lib/ace/theme/merbivore_soft.css index f0ad0bac..0d615376 100644 --- a/lib/ace/theme/merbivore_soft.css +++ b/lib/ace/theme/merbivore_soft.css @@ -23,7 +23,6 @@ .ace-merbivore-soft.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #1C1C1C; - border-radius: 2px } .ace-merbivore-soft .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/mono_industrial.css b/lib/ace/theme/mono_industrial.css index 7e40a3b9..6630e4a2 100644 --- a/lib/ace/theme/mono_industrial.css +++ b/lib/ace/theme/mono_industrial.css @@ -23,7 +23,6 @@ .ace-mono-industrial.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #222C28; - border-radius: 2px } .ace-mono-industrial .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/monokai.css b/lib/ace/theme/monokai.css index a3a063df..bcc26ef4 100644 --- a/lib/ace/theme/monokai.css +++ b/lib/ace/theme/monokai.css @@ -23,7 +23,6 @@ .ace-monokai.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #272822; - border-radius: 2px } .ace-monokai .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/pastel_on_dark.css b/lib/ace/theme/pastel_on_dark.css index 8cab16a1..0b637174 100644 --- a/lib/ace/theme/pastel_on_dark.css +++ b/lib/ace/theme/pastel_on_dark.css @@ -23,7 +23,6 @@ .ace-pastel-on-dark.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #2C2828; - border-radius: 2px } .ace-pastel-on-dark .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/solarized_dark.css b/lib/ace/theme/solarized_dark.css index ea8ec8aa..09740e69 100644 --- a/lib/ace/theme/solarized_dark.css +++ b/lib/ace/theme/solarized_dark.css @@ -30,7 +30,6 @@ .ace-solarized-dark.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #002B36; - border-radius: 2px } .ace-solarized-dark .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/solarized_light.css b/lib/ace/theme/solarized_light.css index 64c4bac9..65399fda 100644 --- a/lib/ace/theme/solarized_light.css +++ b/lib/ace/theme/solarized_light.css @@ -23,7 +23,6 @@ .ace-solarized-light.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #FDF6E3; - border-radius: 2px } .ace-solarized-light .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/terminal.css b/lib/ace/theme/terminal.css index 3636a03d..f23a9ad6 100644 --- a/lib/ace/theme/terminal.css +++ b/lib/ace/theme/terminal.css @@ -23,7 +23,6 @@ .ace-terminal-theme.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px black; - border-radius: 2px } .ace-terminal-theme .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/textmate.css b/lib/ace/theme/textmate.css index 6cb159ba..18e2b4ee 100644 --- a/lib/ace/theme/textmate.css +++ b/lib/ace/theme/textmate.css @@ -122,7 +122,6 @@ } .ace-tm.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px white; - border-radius: 2px; } .ace-tm .ace_marker-layer .ace_step { background: rgb(252, 255, 0); diff --git a/lib/ace/theme/the_night_after_tomorrow.css b/lib/ace/theme/the_night_after_tomorrow.css new file mode 100644 index 00000000..48d2602c --- /dev/null +++ b/lib/ace/theme/the_night_after_tomorrow.css @@ -0,0 +1,139 @@ +.ace-tomorrow-night .ace_gutter { + background: #25282c; + color: #C5C8C6 +} + +.ace-tomorrow-night .ace_print-margin { + width: 1px; + background: #25282c +} + +.ace-tomorrow-night { + background-color: #1D1F21; + color: #C5C8C6 +} + +.ace-tomorrow-night .ace_cursor { + color: #FFFFFF +} + +.ace-tomorrow-night .ace_marker-layer .ace_selection { + background: #373B41 +} + +.ace-tomorrow-night.ace_multiselect .ace_selection.ace_start { + box-shadow: 0 0 3px 0px #1D1F21; + border-radius: 2px +} + +.ace-tomorrow-night .ace_marker-layer .ace_step { + background: rgb(102, 82, 0) +} + +.ace-tomorrow-night .ace_marker-layer .ace_bracket { + margin: -1px 0 0 -1px; + border: 1px solid #4B4E55 +} + +.ace-tomorrow-night .ace_marker-layer .ace_active-line { + background: #282A2E +} + +.ace-tomorrow-night .ace_gutter-active-line { + background-color: #282A2E +} + +.ace-tomorrow-night .ace_marker-layer .ace_selected-word { + border: 1px solid #373B41 +} + +.ace-tomorrow-night .ace_invisible { + color: #4B4E55 +} + +.ace-tomorrow-night .ace_keyword, +.ace-tomorrow-night .ace_meta, +.ace-tomorrow-night .ace_support.ace_type { + color: #C4A400 +} + +.ace-tomorrow-night .ace_storage, +.ace-tomorrow-night .ace_storage.ace_type { + color: #327FD7 +} + +.ace-tomorrow-night .ace_keyword.ace_operator { + color: #8ABEB7 +} + +.ace-tomorrow-night .ace_constant.ace_language{ + color: #934B9F +} + +.ace-tomorrow-night .ace_constant.ace_character, +.ace-tomorrow-night .ace_constant.ace_numeric, +.ace-tomorrow-night .ace_keyword.ace_other.ace_unit, +.ace-tomorrow-night .ace_support.ace_constant, +.ace-tomorrow-night .ace_variable.ace_parameter { + color: #37BC9B +} + +.ace-tomorrow-night .ace_constant.ace_other { + color: #CED1CF +} + +.ace-tomorrow-night .ace_invalid { + color: #CED2CF; + background-color: #DF5F5F +} + +.ace-tomorrow-night .ace_invalid.ace_deprecated { + color: #CED2CF; + background-color: #B798BF +} + +.ace-tomorrow-night .ace_fold { + background-color: #62A5D6; + border-color: #C5C8C6 +} + +.ace-tomorrow-night .ace_entity.ace_name.ace_function, +.ace-tomorrow-night .ace_support.ace_function, +.ace-tomorrow-night .ace_variable { + color: #62A5D6 +} + +.ace-tomorrow-night .ace_support.ace_class, +.ace-tomorrow-night .ace_support.ace_type { + color: #F0C674 +} + +.ace-tomorrow-night .ace_heading, +.ace-tomorrow-night .ace_markup.ace_heading, +.ace-tomorrow-night .ace_string { + color: #CD0000 +} + +.ace-tomorrow-night .ace_proc_name { + color: #04939A +} + +.ace-tomorrow-night .ace_backtick { + color: #1DAA49 +} + +.ace-tomorrow-night .ace_entity.ace_name.ace_tag, +.ace-tomorrow-night .ace_entity.ace_other.ace_attribute-name, +.ace-tomorrow-night .ace_meta.ace_tag, +.ace-tomorrow-night .ace_string.ace_regexp, +.ace-tomorrow-night .ace_variable { + color: #FFFFFF +} + +.ace-tomorrow-night .ace_comment { + color: #3465A4 +} + +.ace-tomorrow-night .ace_indent-guide { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHB3d/8PAAOIAdULw8qMAAAAAElFTkSuQmCC) right repeat-y +} diff --git a/lib/ace/theme/the_night_after_tomorrow.js b/lib/ace/theme/the_night_after_tomorrow.js new file mode 100644 index 00000000..3108e2a9 --- /dev/null +++ b/lib/ace/theme/the_night_after_tomorrow.js @@ -0,0 +1,39 @@ +/* ***** 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) { + +exports.isDark = true; +exports.cssClass = "ace-tomorrow-night"; +exports.cssText = require("../requirejs/text!./the_night_after_tomorrow.css"); + +var dom = require("../lib/dom"); +dom.importCssString(exports.cssText, exports.cssClass); +}); diff --git a/lib/ace/theme/tomorrow.css b/lib/ace/theme/tomorrow.css index 20975004..77407e66 100644 --- a/lib/ace/theme/tomorrow.css +++ b/lib/ace/theme/tomorrow.css @@ -23,7 +23,6 @@ .ace-tomorrow.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #FFFFFF; - border-radius: 2px } .ace-tomorrow .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/tomorrow_night.css b/lib/ace/theme/tomorrow_night.css index aafceab6..e98a6580 100644 --- a/lib/ace/theme/tomorrow_night.css +++ b/lib/ace/theme/tomorrow_night.css @@ -23,7 +23,6 @@ .ace-tomorrow-night.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #1D1F21; - border-radius: 2px } .ace-tomorrow-night .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/tomorrow_night_blue.css b/lib/ace/theme/tomorrow_night_blue.css index e717be0a..907eef35 100644 --- a/lib/ace/theme/tomorrow_night_blue.css +++ b/lib/ace/theme/tomorrow_night_blue.css @@ -24,7 +24,6 @@ .ace-tomorrow-night-blue.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #002451; - border-radius: 2px } .ace-tomorrow-night-blue .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/tomorrow_night_bright.css b/lib/ace/theme/tomorrow_night_bright.css index 5c896fb9..c0c33739 100644 --- a/lib/ace/theme/tomorrow_night_bright.css +++ b/lib/ace/theme/tomorrow_night_bright.css @@ -23,7 +23,6 @@ .ace-tomorrow-night-bright.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #000000; - border-radius: 2px } .ace-tomorrow-night-bright .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/tomorrow_night_eighties.css b/lib/ace/theme/tomorrow_night_eighties.css index 85d7b089..69277fb1 100644 --- a/lib/ace/theme/tomorrow_night_eighties.css +++ b/lib/ace/theme/tomorrow_night_eighties.css @@ -24,7 +24,6 @@ .ace-tomorrow-night-eighties.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #2D2D2D; - border-radius: 2px } .ace-tomorrow-night-eighties .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/twilight.css b/lib/ace/theme/twilight.css index 0ca694fb..ae353dae 100644 --- a/lib/ace/theme/twilight.css +++ b/lib/ace/theme/twilight.css @@ -23,7 +23,6 @@ .ace-twilight.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #141414; - border-radius: 2px } .ace-twilight .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/vibrant_ink.css b/lib/ace/theme/vibrant_ink.css index e2901156..a3a89402 100644 --- a/lib/ace/theme/vibrant_ink.css +++ b/lib/ace/theme/vibrant_ink.css @@ -23,7 +23,6 @@ .ace-vibrant-ink.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #0F0F0F; - border-radius: 2px } .ace-vibrant-ink .ace_marker-layer .ace_step { diff --git a/lib/ace/theme/xcode.css b/lib/ace/theme/xcode.css index 56eb9a89..a22bd169 100644 --- a/lib/ace/theme/xcode.css +++ b/lib/ace/theme/xcode.css @@ -25,7 +25,6 @@ .ace-xcode.ace_multiselect .ace_selection.ace_start { box-shadow: 0 0 3px 0px #FFFFFF; - border-radius: 2px } .ace-xcode .ace_marker-layer .ace_step { diff --git a/lib/ace/undomanager.js b/lib/ace/undomanager.js index 4411ae8b..6da50a8b 100644 --- a/lib/ace/undomanager.js +++ b/lib/ace/undomanager.js @@ -61,15 +61,19 @@ var UndoManager = function() { * **/ this.execute = function(options) { - var deltas = options.args[0]; + // Normalize deltas for storage. + // var deltaSets = this.$serializeDeltas(options.args[0]); + var deltaSets = options.args[0]; + // Add deltas to undo stack. this.$doc = options.args[1]; if (options.merge && this.hasUndo()){ this.dirtyCounter--; - deltas = this.$undoStack.pop().concat(deltas); + deltaSets = this.$undoStack.pop().concat(deltaSets); } - this.$undoStack.push(deltas); + this.$undoStack.push(deltaSets); + + // Reset redo stack. this.$redoStack = []; - if (this.dirtyCounter < 0) { // The user has made a change after undoing past the last clean state. // We can never get back to a clean state now until markClean() is called. @@ -86,12 +90,11 @@ var UndoManager = function() { * @returns {Range} The range of the undo. **/ this.undo = function(dontSelect) { - var deltas = this.$undoStack.pop(); + var deltaSets = this.$undoStack.pop(); var undoSelectionRange = null; - if (deltas) { - undoSelectionRange = - this.$doc.undoChanges(deltas, dontSelect); - this.$redoStack.push(deltas); + if (deltaSets) { + undoSelectionRange = this.$doc.undoChanges(this.$deserializeDeltas(deltaSets), dontSelect); + this.$redoStack.push(deltaSets); this.dirtyCounter--; } @@ -105,15 +108,14 @@ var UndoManager = function() { * **/ this.redo = function(dontSelect) { - var deltas = this.$redoStack.pop(); + var deltaSets = this.$redoStack.pop(); var redoSelectionRange = null; - if (deltas) { + if (deltaSets) { redoSelectionRange = - this.$doc.redoChanges(deltas, dontSelect); - this.$undoStack.push(deltas); + this.$doc.redoChanges(this.$deserializeDeltas(deltaSets), dontSelect); + this.$undoStack.push(deltaSets); this.dirtyCounter++; } - return redoSelectionRange; }; @@ -161,7 +163,52 @@ var UndoManager = function() { this.isClean = function() { return this.dirtyCounter === 0; }; - + + // Serializes deltaSets to reduce memory usage. + this.$serializeDeltas = function(deltaSets) { + return cloneDeltaSetsObj(deltaSets, $serializeDelta); + }; + + // Deserializes deltaSets to allow application to the document. + this.$deserializeDeltas = function(deltaSets) { + return cloneDeltaSetsObj(deltaSets, $deserializeDelta); + }; + + function $serializeDelta(delta){ + return { + action: delta.action, + start: delta.start, + end: delta.end, + lines: delta.lines.length == 1 ? null : delta.lines, + text: delta.lines.length == 1 ? delta.lines[0] : null, + }; + } + + function $deserializeDelta(delta) { + return { + action: delta.action, + start: delta.start, + end: delta.end, + lines: delta.lines || [delta.text] + }; + } + + function cloneDeltaSetsObj(deltaSets_old, fnGetModifiedDelta) { + var deltaSets_new = new Array(deltaSets_old.length); + for (var i = 0; i < deltaSets_old.length; i++) { + var deltaSet_old = deltaSets_old[i]; + var deltaSet_new = { group: deltaSet_old.group, deltas: new Array(deltaSet_old.length)}; + + for (var j = 0; j < deltaSet_old.deltas.length; j++) { + var delta_old = deltaSet_old.deltas[j]; + deltaSet_new.deltas[j] = fnGetModifiedDelta(delta_old); + } + + deltaSets_new[i] = deltaSet_new; + } + return deltaSets_new; + } + }).call(UndoManager.prototype); exports.UndoManager = UndoManager; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 5696745c..45226a1d 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -615,7 +615,7 @@ var VirtualRenderer = function(container, theme) { * @returns {DOMElement} **/ this.getMouseEventTarget = function() { - return this.content; + return this.scroller; }; /** @@ -919,6 +919,8 @@ var VirtualRenderer = function(container, theme) { (this.$minLines||1) * this.lineHeight, Math.min(maxHeight, height) ) + this.scrollMargin.v + (this.$extraHeight || 0); + if (this.$horizScroll) + desiredHeight += this.scrollBarH.getHeight(); var vScroll = height > maxHeight; if (desiredHeight != this.desiredHeight || @@ -939,9 +941,6 @@ var VirtualRenderer = function(container, theme) { }; this.$computeLayerConfig = function() { - if (this.$maxLines && this.lineHeight > 1) - this.$autosize(); - var session = this.session; var size = this.$size; @@ -949,9 +948,6 @@ var VirtualRenderer = function(container, theme) { var screenLines = this.session.getScreenLength(); var maxHeight = screenLines * this.lineHeight; - var offset = this.scrollTop % this.lineHeight; - var minHeight = size.scrollerHeight + this.lineHeight; - var longestLine = this.$getLongestLine(); var horizScroll = !hideScrollbars && (this.$hScrollBarAlwaysVisible || @@ -962,20 +958,27 @@ var VirtualRenderer = function(container, theme) { this.$horizScroll = horizScroll; this.scrollBarH.setVisible(horizScroll); } + // autoresize only after updating hscroll to include scrollbar height in desired height + if (this.$maxLines && this.lineHeight > 1) + this.$autosize(); + + var offset = this.scrollTop % this.lineHeight; + var minHeight = size.scrollerHeight + this.lineHeight; var scrollPastEnd = !this.$maxLines && this.$scrollPastEnd ? (size.scrollerHeight - this.lineHeight) * this.$scrollPastEnd : 0; maxHeight += scrollPastEnd; - this.session.setScrollTop(Math.max(-this.scrollMargin.top, - Math.min(this.scrollTop, maxHeight - size.scrollerHeight + this.scrollMargin.bottom))); + var sm = this.scrollMargin; + this.session.setScrollTop(Math.max(-sm.top, + Math.min(this.scrollTop, maxHeight - size.scrollerHeight + sm.bottom))); - this.session.setScrollLeft(Math.max(-this.scrollMargin.left, Math.min(this.scrollLeft, - longestLine + 2 * this.$padding - size.scrollerWidth + this.scrollMargin.right))); + this.session.setScrollLeft(Math.max(-sm.left, Math.min(this.scrollLeft, + longestLine + 2 * this.$padding - size.scrollerWidth + sm.right))); var vScroll = !hideScrollbars && (this.$vScrollBarAlwaysVisible || - size.scrollerHeight - maxHeight + scrollPastEnd < 0 || this.scrollTop); + size.scrollerHeight - maxHeight + scrollPastEnd < 0 || this.scrollTop > sm.top); var vScrollChanged = this.$vScroll !== vScroll; if (vScrollChanged) { this.$vScroll = vScroll; diff --git a/lib/ace/virtual_renderer_test.js b/lib/ace/virtual_renderer_test.js index e8a8fcbd..9b1ed866 100644 --- a/lib/ace/virtual_renderer_test.js +++ b/lib/ace/virtual_renderer_test.js @@ -36,26 +36,40 @@ if (typeof process !== "undefined") { define(function(require, exports, module) { "use strict"; +var Editor = require("./edit_session").Editor; var EditSession = require("./edit_session").EditSession; var VirtualRenderer = require("./virtual_renderer").VirtualRenderer; var assert = require("./test/assertions"); +var editor = null; module.exports = { - "test: screen2text the column should be rounded to the next character edge" : function() { + setUp: function() { + if (editor) + editor.destroy() var el = document.createElement("div"); - if (!el.getBoundingClientRect) { console.log("Skipping test: This test only runs in the browser"); return; } - el.style.left = "20px"; el.style.top = "30px"; el.style.width = "300px"; el.style.height = "100px"; document.body.appendChild(el); - var renderer = new VirtualRenderer(el); + var editor = new Editor(renderer); + editor.on("destroy", function() { + document.body.removeChild(el); + }); + }, + tearDown: function() { + editor && editor.destroy(); + editor = null; + }, + "test: screen2text the column should be rounded to the next character edge" : function(done) { + if (!editor) return done(); + var renderer = editor.renderer; + renderer.setPadding(0); renderer.setSession(new EditSession("1234")); @@ -73,7 +87,23 @@ module.exports = { testPixelToText(10, 0, 0, 1); testPixelToText(14, 0, 0, 1); testPixelToText(15, 0, 0, 2); - document.body.removeChild(el); + done(); + }, + + "test scrollmargin + autosize": function(done) { + if (!editor) return done(); + editor.setOptions({ + maxLines: 100, + useWrapMode: true + }); + editor.renderer.setScrollMargin(10, 10); + editor.setValue("\n\n"); + editor.setValue("\n\n\n\n"); + editor.renderer.once("afterRender", function() { + setTimeout(function() { + done(); + }, 0); + }); } // change tab size after setDocument (for text layer) diff --git a/lib/ace/worker/mirror.js b/lib/ace/worker/mirror.js index 7a3318fb..ef6e2aa3 100644 --- a/lib/ace/worker/mirror.js +++ b/lib/ace/worker/mirror.js @@ -1,6 +1,7 @@ define(function(require, exports, module) { "use strict"; +var Range = require("../range").Range; var Document = require("../document").Document; var lang = require("../lib/lang"); @@ -12,7 +13,19 @@ var Mirror = exports.Mirror = function(sender) { var _self = this; sender.on("change", function(e) { - doc.applyDeltas(e.data); + var data = e.data; + if (data[0].start) { + doc.applyDeltas(data); + } else { + for (var i = 0; i < data.length; i += 2) { + if (Array.isArray(data[i+1])) { + var d = {action: "insert", start: data[i], lines: data[i+1]}; + } else { + var d = {action: "remove", start: data[i], end: data[i+1]}; + } + doc.applyDelta(d, true); + } + } if (_self.$timeout) return deferredUpdate.schedule(_self.$timeout); _self.onUpdate(); diff --git a/lib/ace/worker/worker.js b/lib/ace/worker/worker.js index 928000dd..28fc0fe2 100644 --- a/lib/ace/worker/worker.js +++ b/lib/ace/worker/worker.js @@ -1,8 +1,9 @@ "no use strict"; ;(function(window) { -if (typeof window.window != "undefined" && window.document) { +if (typeof window.window != "undefined" && window.document) + return; +if (window.require && window.define) return; -} window.console = function() { var msgs = Array.prototype.slice.call(arguments, 0); @@ -19,6 +20,7 @@ window.ace = window; window.onerror = function(message, file, line, col, err) { postMessage({type: "error", data: { message: message, + data: err.data, file: file, line: line, col: col, @@ -37,7 +39,7 @@ window.normalizeModule = function(parentId, moduleName) { var base = parentId.split("/").slice(0, -1).join("/"); moduleName = (base ? base + "/" : "") + moduleName; - while(moduleName.indexOf(".") !== -1 && previous != moduleName) { + while (moduleName.indexOf(".") !== -1 && previous != moduleName) { var previous = moduleName; moduleName = moduleName.replace(/^\.\//, "").replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, ""); } @@ -46,7 +48,7 @@ window.normalizeModule = function(parentId, moduleName) { return moduleName; }; -window.require = function(parentId, id) { +window.require = function require(parentId, id) { if (!id) { id = parentId; parentId = null; @@ -64,17 +66,36 @@ window.require = function(parentId, id) { } return module.exports; } - - var chunks = id.split("/"); + if (!window.require.tlns) return console.log("unable to load " + id); - chunks[0] = window.require.tlns[chunks[0]] || chunks[0]; - var path = chunks.join("/") + ".js"; + + var path = resolveModuleId(id, window.require.tlns); + if (path.slice(-3) != ".js") path += ".js"; window.require.id = id; + window.require.modules[id] = {}; // prevent infinite loop on broken modules importScripts(path); return window.require(parentId, id); }; +function resolveModuleId(id, paths) { + var testPath = id, tail = ""; + while (testPath) { + var alias = paths[testPath]; + if (typeof alias == "string") { + return alias + tail; + } else if (alias) { + return alias.location.replace(/\/*$/, "/") + (tail || alias.main || alias.name); + } else if (alias === false) { + return ""; + } + var i = testPath.lastIndexOf("/"); + if (i === -1) break; + tail = testPath.substr(i) + tail; + testPath = testPath.slice(0, i); + } + return id; +} window.require.modules = {}; window.require.tlns = {}; @@ -100,9 +121,9 @@ window.define = function(id, deps, factory) { } if (!deps.length) - // If there is no dependencies, we inject 'require', 'exports' and - // 'module' as dependencies, to provide CommonJS compatibility. - deps = ['require', 'exports', 'module']; + // If there is no dependencies, we inject "require", "exports" and + // "module" as dependencies, to provide CommonJS compatibility. + deps = ["require", "exports", "module"]; var req = function(childId) { return window.require(id, childId); @@ -113,16 +134,16 @@ window.define = function(id, deps, factory) { factory: function() { var module = this; var returnExports = factory.apply(this, deps.map(function(dep) { - switch(dep) { - // Because 'require', 'exports' and 'module' aren't actual - // dependencies, we must handle them seperately. - case 'require': return req; - case 'exports': return module.exports; - case 'module': return module; - // But for all other dependencies, we can just go ahead and - // require them. - default: return req(dep); - } + switch (dep) { + // Because "require", "exports" and "module" aren't actual + // dependencies, we must handle them seperately. + case "require": return req; + case "exports": return module.exports; + case "module": return module; + // But for all other dependencies, we can just go ahead and + // require them. + default: return req(dep); + } })); if (returnExports) module.exports = returnExports; @@ -131,9 +152,10 @@ window.define = function(id, deps, factory) { }; }; window.define.amd = {}; - +require.tlns = {}; window.initBaseUrls = function initBaseUrls(topLevelNamespaces) { - require.tlns = topLevelNamespaces; + for (var i in topLevelNamespaces) + require.tlns[i] = topLevelNamespaces[i]; }; window.initSender = function initSender() { @@ -173,21 +195,23 @@ var sender = window.sender = null; window.onmessage = function(e) { var msg = e.data; - if (msg.command) { + if (msg.event && sender) { + sender._signal(msg.event, msg.data); + } + else if (msg.command) { if (main[msg.command]) main[msg.command].apply(main, msg.args); + else if (window[msg.command]) + window[msg.command].apply(window, msg.args); else throw new Error("Unknown command:" + msg.command); } - else if (msg.init) { - initBaseUrls(msg.tlns); + else if (msg.init) { + window.initBaseUrls(msg.tlns); require("ace/lib/es5-shim"); - sender = window.sender = initSender(); + sender = window.sender = window.initSender(); var clazz = require(msg.module)[msg.classname]; main = window.main = new clazz(sender); - } - else if (msg.event && sender) { - sender._signal(msg.event, msg.data); } }; })(this); \ No newline at end of file diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index ad445287..ba4f20a0 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -162,19 +162,22 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { doc.on("change", this.changeListener); }; - this.changeListener = function(e) { + this.changeListener = function(delta) { if (!this.deltaQueue) { - this.deltaQueue = [e.data]; + this.deltaQueue = []; setTimeout(this.$sendDeltaQueue, 0); - } else - this.deltaQueue.push(e.data); + } + if (delta.action == "insert") + this.deltaQueue.push(delta.start, delta.lines); + else + this.deltaQueue.push(delta.start, delta.end); }; this.$sendDeltaQueue = function() { var q = this.deltaQueue; if (!q) return; this.deltaQueue = null; - if (q.length > 20 && q.length > this.$doc.getLength() >> 1) { + if (q.length > 50 && q.length > this.$doc.getLength() >> 1) { this.call("setValue", [this.$doc.getValue()]); } else this.emit("change", {data: q}); diff --git a/static.js b/static.js index a711715e..3b75c2b6 100755 --- a/static.js +++ b/static.js @@ -21,7 +21,7 @@ http.createServer(function(req, res) { if (req.method == "PUT") { if (!allowSave) return error(res, 404, "Saving not allowed pass --allow-save to enable"); - save(req, res, filename); + return save(req, res, filename); } fs.exists(filename, function(exists) { @@ -86,6 +86,7 @@ function save(req, res, filePath) { } res.statusCode = 200; res.end("OK"); + console.log("saved ", filePath); }); } diff --git a/tool/lib.js b/tool/lib.js index 8809595c..3d57b6e0 100644 --- a/tool/lib.js +++ b/tool/lib.js @@ -14,8 +14,16 @@ exports.parsePlist = function(xmlOrJSON, callback) { }); } else { try { - xmlOrJSON = xmlOrJSON.replace(/^\s*\/\/.*/gm, ""); - json = JSON.parse(xmlOrJSON) + xmlOrJSON = xmlOrJSON.replace( + /("(?:\\.|[^"])*")|(?:,\s*)+([\]\}])|(\w+)\s*:|([\]\}]\s*[\[\{])|(\/\/.*|\/\*(?:[^\*]|\*(?=[^\/]))*?\*\/)/g, + function(_, str, extraComma, noQuote, missingComma, comment) { + if (comment) + return ""; + if (missingComma) + return missingComma[0] + "," + missingComma.slice(1); + return str || extraComma || '"' + noQuote + '":'; + }); + json = JSON.parse(xmlOrJSON); } catch(e) { json = cson.parse(xmlOrJSON); } @@ -24,10 +32,101 @@ exports.parsePlist = function(xmlOrJSON, callback) { return json; }; + exports.formatJSON = function(object, initialIndent) { - return util.inspect(object, false, 40).replace(/^/gm, initialIndent||""); + return JSON.stringify(object, null, 4).replace(/^/gm, initialIndent||""); }; +exports.formatJS = function(object, initialIndent) { + return formatJS(object, 4, initialIndent); +}; + +function formatJS(object, indent, initialIndent) { + if (typeof indent == "number") + indent = Array(indent + 1).join(" "); + + function $format(buffer, totalIndent, state, o) { + if (typeof o != "object" || !o) { + if (typeof o == "string") + buffer.push(JSON.stringify(o)); + else + buffer.push("" + o); + } + else if (Array.isArray(o)) { + buffer.push("[") + + var len = totalIndent.length + var oneLine = true; + for (var i = 0; i < o.length; i++) { + if (typeof o[i] == "string") { + len += o[i].length + 2 + } else if (!o[i]) { + len += (o[i] + "").length + } else { + oneLine = false; + break; + } + len += 2; + if (len > 60) { + oneLine = false; + break; + } + } + + for (var i = 0; i < o.length; i++) { + if (o[i] && typeof o[i] == "object") { + $format(buffer, totalIndent, state, o[i]); + if (i < o.length - 1) + buffer.push(", "); + } else { + if (oneLine) + i && buffer.push(" "); + else + buffer.push("\n", totalIndent + indent) + $format(buffer, totalIndent + indent, state, o[i]); + if (i < o.length - 1) + buffer.push(","); + } + + } + if (!oneLine && buffer[buffer.length - 1] != "}") + buffer.push("\n" + totalIndent) + buffer.push("]") + } + else { + var keys = Object.keys(o); + buffer.push("{", "\n"); + for (var i = 0; i < keys.length; i++) { + buffer.push(totalIndent + indent); + if (/^\w+$/.test(keys[i])) + buffer.push(keys[i]); + else + buffer.push(JSON.stringify(keys[i])); + buffer.push(": ") + + if (keys[i] == "regex" && typeof o[keys[i]] == "string") { + try { + var re = new RegExp(o[keys[i]]); + buffer.push("/" + re.source.replace(/\\.|\//g, function(f) { + return f.length == 1 ? "\\" + f : f; + }) + "/"); + } catch(e) { + $format(buffer, totalIndent + indent, state, o[keys[i]]); + } + } else { + $format(buffer, totalIndent + indent, state, o[keys[i]]); + } + + if (i < keys.length - 1) + buffer.push(",", "\n"); + } + buffer.push("\n", totalIndent, "}"); + } + } + var buffer = []; + $format(buffer, initialIndent || "", {}, object); + return buffer.join(""); +} exports.fillTemplate = function(template, replacements) { return template.replace(/%(.+?)%/g, function(str, m) { diff --git a/tool/mode_creator.js b/tool/mode_creator.js index 7a2aea59..44aa67d0 100644 --- a/tool/mode_creator.js +++ b/tool/mode_creator.js @@ -126,8 +126,8 @@ function handleSaveResult(err, editor) { return log( "Write access to this file is disabled.\n"+ "To enable saving your changes to disk, clone the Ace repository\n"+ - "and run the included web server with the --allow-write option\n"+ - "`node static.js --allow-write` or `static.py --puttable=*`" + "and run the included web server with the --allow-save option\n"+ + "`node static.js --allow-save` or `static.py --puttable=*`" ); } editor.session.getUndoManager().markClean(); diff --git a/tool/tmlanguage.js b/tool/tmlanguage.js index 0e458330..b5d8b851 100644 --- a/tool/tmlanguage.js +++ b/tool/tmlanguage.js @@ -662,10 +662,10 @@ function convertTmLanguage(name, langStr) { var languageHighlightRules = lib.fillTemplate(modeHighlightTemplate, { language: languageNameSanitized, - languageTokens: lib.formatJSON(patterns, " ").trim(), + languageTokens: lib.formatJS(patterns, " ").trim(), uuid: language.uuid, name: name, - metaData: lib.formatJSON(language, " ").trim() + metaData: lib.formatJS(language, "").trim() }); if (devMode) {