diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c44edcb0..062af59c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,16 +7,9 @@ Feel free to fork and improve/enhance Ace any way you want. If you feel that the There are two versions of the agreement: -1. [The Individual CLA](https://github.com/ajaxorg/ace/raw/master/doc/Contributor_License_Agreement-v2.pdf): use this version if you're working on an ajax.org in your spare time, or can clearly claim ownership of copyright in what you'll be submitting. -2. [The Corporate CLA](https://github.com/ajaxorg/ace/raw/master/doc/Corporate_Contributor_License_Agreement-v2.pdf): have your corporate lawyer review and submit this if your company is going to be contributing to ajax.org projects +1. [The Individual CLA](https://docs.google.com/a/c9.io/forms/d/1MfmfrxqD_PNlNsuK0lC2KSelRLxGLGfh_wEcG0ijVvo/viewform): use this version if you're working on the Cloud9 SDK or open source projects in your spare time, or can clearly claim ownership of copyright in what you'll be submitting. +2. [The Corporate CLA](https://docs.google.com/a/c9.io/forms/d/1vFejn4111GdnCNuQ6BfnJDaxdsUEMD4KCo1ayovAfu0/viewform): have your corporate lawyer review and submit this if your company is going to be contributing to the Cloud9 SDK and/or open source projects. -If you want to contribute to an ajax.org project please print the CLA and fill it out and sign it. Then either send it by snail mail or fax to us or send it back scanned (or as a photo) by email. +If you want to contribute to the Cloud9 SDK and/or open source projects please go to the online form, fill it out and submit it. -Email: ace+cla@c9.io - -Fax: +31 (0) 206388953 - -Address: Ajax.org B.V. - Keizersgracht 241 - 1016 EA, Amsterdam - the Netherlands \ No newline at end of file +Happy coding, Cloud9 diff --git a/ChangeLog.txt b/ChangeLog.txt index cad1e517..3077d171 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +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/Readme.md b/Readme.md index 2195ceda..42e81567 100644 --- a/Readme.md +++ b/Readme.md @@ -79,7 +79,7 @@ By default the editor only supports plain text mode; many other languages are av The mode can then be used like this: ```javascript - var JavaScriptMode = require("ace/mode/javascript").Mode; + var JavaScriptMode = ace.require("ace/mode/javascript").Mode; editor.getSession().setMode(new JavaScriptMode()); ``` 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/emmet.html b/demo/emmet.html
index 122085d9..bd0d4abe 100644
--- a/demo/emmet.html
+++ b/demo/emmet.html
@@ -23,7 +23,7 @@
 

 
 
-
+
 
 
 
 
+        
+    
+    
+        

+ +
+ + + \ No newline at end of file diff --git a/index.html b/index.html index 9e05ed68..e2440eb1 100644 --- a/index.html +++ b/index.html @@ -470,7 +470,6 @@ var fonts = lang.arrayToMap(
this.$rules = {
     "start" : [ {
         token : "text",
-        merge : true,
         regex : "<\\!\\[CDATA\\[",
         next : "cdata"
     },
@@ -480,13 +479,7 @@ var fonts = lang.arrayToMap(
         regex : "\\]\\]>",
         next : "start"
     }, {
-        token : "text",
-        merge : true,
-        regex : "\\s+"
-    }, {
-        token : "text",
-        merge : true,
-        regex : ".+"
+        defaultToken : "text"
     } ]
 };

In this extremly short sample, we're defining some highlighting rules for when Ace detectes a <![CDATA tag. When one is encountered, the tokenizer moves from start into the cdata state. It remains there, applying the text token to any string it encounters. Finally, when it hits a closing ]> symbol, it returns to the start state and continues to tokenize anything else.

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 5cfc49c6..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(); } @@ -205,7 +206,6 @@ var Autocomplete = function() { "Ctrl-Down|Ctrl-End": function(editor) { editor.completer.goTo("end"); }, "Esc": function(editor) { editor.completer.detach(); }, - "Space": function(editor) { editor.completer.detach(); editor.insert(" ");}, "Return": function(editor) { return editor.completer.insertMatch(); }, "Shift-Return": function(editor) { editor.completer.insertMatch(true); }, "Tab": function(editor) { @@ -350,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); @@ -417,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 862a1c5e..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; @@ -197,8 +196,12 @@ var AcePopup = function(parentNode) { if (data.meta) { var maxW = popup.renderer.$size.scrollerWidth / popup.renderer.layerConfig.characterWidth; - if (data.meta.length + data.caption.length < maxW - 2) - tokens.push({type: "rightAlignedText", value: data.meta}); + var metaData = data.meta; + if (metaData.length + data.caption.length > maxW - 2) { + // trim meta to fit this popup and add ellipsis + metaData = metaData.substr(0, maxW - data.caption.length - 3) + "\u2026" + } + tokens.push({type: "rightAlignedText", value: metaData}); } return tokens; }; @@ -217,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) { @@ -338,4 +341,4 @@ dom.importCssString("\ exports.AcePopup = AcePopup; -}); \ No newline at end of file +}); 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,100 +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 (smallest threshold is on safari 0xFFFF) - // to circumvent that we have to break huge inserts into smaller chunks here - while (lines.length > 0xF000) { - var end = this._insertLines(row, lines.slice(0, 0xF000)); - lines = lines.slice(0xF000); - 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); }; /** @@ -405,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: ["", ""] + }); + } }; /** @@ -525,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 @@ -536,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 5c324db0..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")); }, @@ -296,6 +296,35 @@ module.exports = { "test: empty document has to contain one line": function() { var doc = new Document(""); assert.equal(doc.$lines.length, 1); + }, + + "test: ignore empty delta": function() { + var doc = new Document(""); + doc.on("change", function() { + throw "should ignore empty delta"; + }) + doc.insert({row: 0, column: 0}, ""); + doc.insert({row: 1, column: 1}, ""); + doc.remove({start: {row: 1, column: 1}, end: {row: 1, column: 1}}); + }, + + "test: inserting huge delta": function() { + var doc = new Document(""); + var val = ""; + var MAX = 0xF000; + for (var i = 0; i < 10 * MAX; i++) { + val += i + "\n" + } + doc.setValue(val); + assert.equal(doc.getValue(), val); + + for (var i = 3 * MAX + 2; i >= 3 * MAX - 2; i--) { + val = doc.getLines(0, i).join("\n"); + doc.setValue("\nab"); + assert.equal(doc.getValue(), "\nab"); + doc.insert({row: 1, column: 1}, val); + assert.equal(doc.getValue(), "\na" + val + "b"); + } } }; diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index b35ea4e0..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; @@ -1835,7 +1829,7 @@ var EditSession = function(text, mode) { TAB_SPACE = 12; - this.$computeWrapSplits = function(tokens, wrapLimit) { + this.$computeWrapSplits = function(tokens, wrapLimit, tabSize) { if (tokens.length == 0) { return []; } @@ -1846,6 +1840,31 @@ var EditSession = function(text, mode) { var isCode = this.$wrapAsCode; + var indentedSoftWrap = this.$indentedSoftWrap; + var maxIndent = wrapLimit <= Math.max(2 * tabSize, 8) + || indentedSoftWrap === false ? 0 : Math.floor(wrapLimit / 2); + + function getWrapIndent() { + var indentation = 0; + if (maxIndent === 0) + return indentation; + if (indentedSoftWrap) { + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (token == SPACE) + indentation += 1; + else if (token == TAB) + indentation += tabSize; + else if (token == TAB_SPACE) + continue; + else + break; + } + } + if (isCode && indentedSoftWrap !== false) + indentation += tabSize; + return Math.min(indentation, maxIndent); + } function addSplit(screenPos) { var displayed = tokens.slice(lastSplit, screenPos); @@ -1862,14 +1881,18 @@ var EditSession = function(text, mode) { len -= 1; }); + if (!splits.length) { + indent = getWrapIndent(); + splits.indent = indent; + } lastDocSplit += len; splits.push(lastDocSplit); lastSplit = screenPos; } - - while (displayLength - lastSplit > wrapLimit) { + var indent = 0; + while (displayLength - lastSplit > wrapLimit - indent) { // This is, where the split should be. - var split = lastSplit + wrapLimit; + var split = lastSplit + wrapLimit - indent; // If there is a space or tab at this split position, then making // a split is simple. @@ -1929,7 +1952,7 @@ var EditSession = function(text, mode) { // === ELSE === // Search for the first non space/tab/placeholder/punctuation token backwards. - var minSplit = Math.max(split - (isCode ? 10 : wrapLimit-(wrapLimit>>2)), lastSplit - 1); + var minSplit = Math.max(split - (wrapLimit -(wrapLimit>>2)), lastSplit - 1); while (split > minSplit && tokens[split] < PLACEHOLDER_START) { split --; } @@ -1957,7 +1980,7 @@ var EditSession = function(text, mode) { // around -> force a split. if (tokens[split] == CHAR_EXT) split--; - addSplit(split); + addSplit(split - indent); } return splits; }; @@ -2064,6 +2087,16 @@ var EditSession = function(text, mode) { } }; + this.getRowWrapIndent = function(screenRow) { + if (this.$useWrapMode) { + var pos = this.screenToDocumentPosition(screenRow, Number.MAX_VALUE); + var splits = this.$wrapData[pos.row]; + return splits.length && splits[0] < pos.column ? splits.indent : 0; + } else { + return 0; + } + } + /** * Returns the position (on screen) for the last character in the provided screen row. * @param {Number} screenRow The screen row to check @@ -2201,20 +2234,21 @@ var EditSession = function(text, mode) { line = this.getLine(docRow); foldLine = null; } - + var wrapIndent = 0; if (this.$useWrapMode) { var splits = this.$wrapData[docRow]; if (splits) { var splitIndex = Math.floor(screenRow - row); column = splits[splitIndex]; if(splitIndex > 0 && splits.length) { + wrapIndent = splits.indent; docColumn = splits[splitIndex - 1] || splits[splits.length - 1]; line = line.substring(docColumn); } } } - docColumn += this.$getStringScreenWidth(line, screenColumn)[1]; + docColumn += this.$getStringScreenWidth(line, screenColumn - wrapIndent)[1]; // We remove one character at the end so that the docColumn // position returned is not associated to the next row on the screen. @@ -2306,6 +2340,7 @@ var EditSession = function(text, mode) { textLine = this.getLine(docRow).substring(0, docColumn); foldStartRow = docRow; } + var wrapIndent = 0; // Clamp textLine if in wrapMode. if (this.$useWrapMode) { var wrapRow = this.$wrapData[foldStartRow]; @@ -2318,12 +2353,13 @@ var EditSession = function(text, mode) { textLine = textLine.substring( wrapRow[screenRowOffset - 1] || 0, textLine.length ); + wrapIndent = screenRowOffset > 0 ? wrapRow.indent : 0; } } return { row: screenRow, - column: this.$getStringScreenWidth(textLine)[0] + column: wrapIndent + this.$getStringScreenWidth(textLine)[0] }; }; @@ -2503,6 +2539,7 @@ config.defineOptions(EditSession.prototype, "session", { }, initialValue: "auto" }, + indentedSoftWrap: { initialValue: true }, firstLineNumber: { set: function() {this._signal("changeBreakpoint");}, initialValue: 1 diff --git a/lib/ace/edit_session/bracket_match.js b/lib/ace/edit_session/bracket_match.js index 064ee3dc..62a8499c 100644 --- a/lib/ace/edit_session/bracket_match.js +++ b/lib/ace/edit_session/bracket_match.js @@ -117,7 +117,7 @@ function BracketMatch() { typeRe = new RegExp( "(\\.?" + token.type.replace(".", "\\.").replace("rparen", ".paren") - .replace(/\b(?:end|start|begin)\b/, "") + .replace(/\b(?:end)\b/, "(?:start|begin|end)") + ")+" ); } @@ -174,7 +174,7 @@ function BracketMatch() { typeRe = new RegExp( "(\\.?" + token.type.replace(".", "\\.").replace("lparen", ".paren") - .replace(/\b(?:end|start|begin)\b/, "") + .replace(/\b(?:start|begin)\b/, "(?:start|begin|end)") + ")+" ); } 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 87cc9567..0b904b63 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -280,7 +280,7 @@ module.exports = { session.setUseWrapMode(true); session.setWrapLimitRange(12, 12); session.adjustWrapLimit(80); - + session.setOption("wrapMethod", "text"); assert.position(session.documentToScreenPosition(0, 11), 0, 11); assert.position(session.documentToScreenPosition(0, 12), 1, 0); }, @@ -380,7 +380,8 @@ module.exports = { line = lang.stringTrimRight(line); var tokens = EditSession.prototype.$getDisplayTokens(line); var splits = EditSession.prototype.$computeWrapSplits(tokens, wrapLimit, tabSize); - // console.log("String:", line, "Result:", splits, "Expected:", assertEqual); + console.log("String:", line, "Result:", splits, "Expected:", assertEqual); + assert.ok(splits.length == assertEqual.length); for (var i = 0; i < splits.length; i++) { assert.ok(splits[i] == assertEqual[i]); @@ -388,6 +389,7 @@ module.exports = { } EditSession.prototype.$wrapAsCode = true; + EditSession.prototype.$indentedSoftWrap = false; // Basic splitting. computeAndAssert("foo bar foo bar", [ 12 ]); computeAndAssert("foo bar f bar", [ 12 ]); @@ -413,16 +415,26 @@ module.exports = { computeAndAssert("ぁぁ", [1], 2); computeAndAssert(" ぁぁ", [1, 2], 2); computeAndAssert(" ぁ\tぁ", [1, 3], 2); - computeAndAssert(" ぁぁ\tぁ", [1, 4], 4); + computeAndAssert(" ぁぁ\tぁ", [2, 4], 4); + computeAndAssert("ぁぁ ぁぁ\tぁ", [3, 6], 6); // Test wrapping for punctuation. - computeAndAssert(" ab.c;ef++", [1, 3, 5, 7, 8], 2); + computeAndAssert(" ab.c;ef++", [2, 4, 6, 8], 2); + computeAndAssert(" ab.c;ef++", [3, 5, 8], 3); computeAndAssert(" a.b", [1, 2, 3], 1); computeAndAssert("#>>", [1, 2], 1); // Test wrapping for punctuation in EditSession.prototype.$wrapAsCode = false; computeAndAssert("ab cde, Juhu kinners", [3, 8, 13, 19], 6); + + // test indented wrapping + EditSession.prototype.$indentedSoftWrap = true; + computeAndAssert("foo bar foo bar foo bara foo", [12, 25]); + computeAndAssert("fooooooooooooooooooooooooooo", [12, 24]); + computeAndAssert("\t\tfoo bar fooooooooooobooooooo", [6, 10, 16, 22, 28]); + computeAndAssert("\t\t\tfoo bar fooooooooooobooooooo", [3, 7, 11, 17, 23, 29]); + computeAndAssert("\tfoo \t \t \t \t bar", [6, 12]); // 14 }, "test get longest line" : function() { @@ -430,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); @@ -459,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 4f1c2739..5da8a961 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -150,9 +150,8 @@ var Editor = function(renderer, session) { args: commadEvent.args, scrollTop: this.renderer.scrollTop }; - if (this.curOp.command.name) + if (this.curOp.command.name && this.curOp.command.scrollIntoView !== undefined) this.$blockScrolling++; - // this.selections.push(this.selection.toJSON()); }; this.endOperation = function(e) { @@ -161,10 +160,14 @@ var Editor = function(renderer, session) { return this.curOp = null; this._signal("beforeEndOperation"); var command = this.curOp.command; - if (command.name && this.$blockScrolling) + 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; @@ -182,7 +185,7 @@ var Editor = function(renderer, session) { default: break; } - if (command.scrollIntoView == "animate") + if (scrollIntoView == "animate") this.renderer.animateScrolling(this.curOp.scrollTop); } @@ -275,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) { @@ -374,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}); }; @@ -683,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(); }; @@ -911,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 { @@ -936,7 +942,6 @@ var Editor = function(renderer, session) { this.session.insert(range.start, lines[i]); } } - this.renderer.scrollCursorIntoView(); }; this.execCommand = function(command, args) { @@ -1624,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(); }; @@ -2685,6 +2682,7 @@ config.defineOptions(Editor.prototype, "editor", { useSoftTabs: "session", tabSize: "session", wrap: "session", + indentedSoftWrap: "session", foldStyle: "session", mode: "session" }); 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/menu_tools/generate_settings_menu.js b/lib/ace/ext/menu_tools/generate_settings_menu.js index 89931062..2ee1ae09 100644 --- a/lib/ace/ext/menu_tools/generate_settings_menu.js +++ b/lib/ace/ext/menu_tools/generate_settings_menu.js @@ -93,7 +93,7 @@ module.exports.generateSettingsMenu = function generateSettingsMenu (editor) { }); var el = topmenu.appendChild(document.createElement('div')); - var version = "1.1.8"; + var version = "1.1.9"; el.style.padding = "1em"; el.textContent = "Ace version " + version; diff --git a/lib/ace/ext/modelist.js b/lib/ace/ext/modelist.js index fba3a245..675a4b85 100644 --- a/lib/ace/ext/modelist.js +++ b/lib/ace/ext/modelist.js @@ -47,7 +47,7 @@ var supportedModes = { ActionScript:["as"], ADA: ["ada|adb"], Apache_Conf: ["^htaccess|^htgroups|^htpasswd|^conf|htaccess|htgroups|htpasswd"], - AsciiDoc: ["asciidoc"], + AsciiDoc: ["asciidoc|adoc"], Assembly_x86:["asm"], AutoHotKey: ["ahk"], BatchFile: ["bat|cmd"], @@ -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"], @@ -146,6 +147,7 @@ var supportedModes = { Soy_Template:["soy"], Space: ["space"], SQL: ["sql"], + SQLServer: ["sqlserver"], Stylus: ["styl|stylus"], SVG: ["svg"], Tcl: ["tcl"], diff --git a/lib/ace/ext/static.css b/lib/ace/ext/static.css index cc7da8a6..51986c3f 100644 --- a/lib/ace/ext/static.css +++ b/lib/ace/ext/static.css @@ -1,24 +1,31 @@ .ace_static_highlight { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Droid Sans Mono', monospace; - font-size: 12px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Droid Sans Mono', monospace; + font-size: 12px; + white-space: pre-wrap } .ace_static_highlight .ace_gutter { - width: 25px !important; - float: left; + width: 2em; text-align: right; padding: 0 3px 0 0; margin-right: 3px; - position: static !important; } -.ace_static_highlight .ace_line { clear: both; } +.ace_static_highlight.ace_show_gutter .ace_line { + padding-left: 2.6em; +} + +.ace_static_highlight .ace_line { position: relative; } .ace_static_highlight .ace_gutter-cell { - -moz-user-select: -moz-none; - -khtml-user-select: none; - -webkit-user-select: none; - user-select: none; + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; + top: 0; + bottom: 0; + left: 0; + position: absolute; } diff --git a/lib/ace/ext/static_highlight.js b/lib/ace/ext/static_highlight.js index 9116467b..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); @@ -171,7 +171,8 @@ highlight.renderSync = function(input, mode, theme, lineStart, disableGutter) { // let's prepare the whole html var html = "
" + - "
" + + "
" + stringBuilder.join("") + "
" + "
"; diff --git a/lib/ace/ext/static_highlight_test.js b/lib/ace/ext/static_highlight_test.js index 075cea7b..6271666d 100644 --- a/lib/ace/ext/static_highlight_test.js +++ b/lib/ace/ext/static_highlight_test.js @@ -28,12 +28,12 @@ module.exports = { var mode = new JavaScriptMode(); var result = highlighter.render(snippet, mode, theme); - assert.equal(result.html, "
" - + "
/**\xa0this\xa0is\xa0a\xa0function\n
" + assert.equal(result.html, "
" + + "
/** this is a function\n
" + "
*\n
" + "
*/\n
" - + "
function\xa0hello\xa0(a,\xa0b,\xa0c)\xa0{\n
" - + "
\xa0\xa0\xa0\xa0console.log(a\xa0*\xa0b\xa0+\xa0c\xa0+\xa0'sup$');\n
" + + "
function hello (a, b, c) {\n
" + + "
console.log(a * b + c + 'sup$');\n
" + "
}\n
" + "
"); assert.ok(!!result.css); diff --git a/lib/ace/ext/themelist.js b/lib/ace/ext/themelist.js index 8914c776..0df76b02 100644 --- a/lib/ace/ext/themelist.js +++ b/lib/ace/ext/themelist.js @@ -51,12 +51,14 @@ var themeData = [ ["Dreamweaver" ], ["Eclipse" ], ["GitHub" ], + ["IPlastic" ], ["Solarized Light"], ["TextMate" ], ["Tomorrow" ], ["XCode" ], ["Kuroir"], ["KatzenMilch"], + ["SQL Server" ,"sqlserver" , "light"], ["Ambiance" ,"ambiance" , "dark"], ["Chaos" ,"chaos" , "dark"], ["Clouds Midnight" ,"clouds_midnight" , "dark"], @@ -71,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 38fe0923..4a2bb72c 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -76,6 +76,7 @@ var TextInput = function(parentNode, host) { resetSelection(); }); this.focus = function() { + if (tempStyle) return text.focus(); text.style.position = "fixed"; text.style.top = "-1000px"; text.focus(); @@ -305,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 0641897f..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) { @@ -489,8 +483,12 @@ define(function(require, exports, module) { }; this.scrollInfo = function() { return 0; }; this.scrollIntoView = function(pos, margin) { - if (pos) - this.ace.renderer.scrollCursorIntoView(toAcePos(pos), null, margin); + if (pos) { + var renderer = this.ace.renderer; + var viewMargin = { "top": 0, "bottom": margin }; + renderer.scrollCursorIntoView(toAcePos(pos), + (renderer.lineHeight * 2) / renderer.$size.scrollerHeight, viewMargin); + } }; this.getLine = function(row) { return this.ace.session.getLine(row) }; this.getRange = function(s, e) { @@ -573,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); @@ -650,6 +647,9 @@ define(function(require, exports, module) { this.refresh = function() { return this.ace.resize(true); }; + this.getMode = function() { + return { name : this.getOption("mode") }; + } }).call(CodeMirror.prototype); function toAcePos(cmPos) { return {row: cmPos.line, column: cmPos.ch}; @@ -730,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;\ @@ -759,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(); @@ -807,7 +794,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ if (inp) { if (options.value) { inp.value = options.value; - inp.select(); + if (options.select !== false) inp.select(); } if (options.onInput) @@ -1135,9 +1122,11 @@ dom.importCssString(".normal-mode .ace_cursor{\ } var numberRegex = /[\d]/; - var wordRegexp = [{test: CodeMirror.isWordChar}, {test: function(ch) { - return !CodeMirror.isWordChar(ch) && !/\s/.test(ch); - }}], bigWordRegexp = [(/\S/)]; + var wordCharTest = [CodeMirror.isWordChar, function(ch) { + return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch); + }], bigWordCharTest = [function(ch) { + return /\S/.test(ch); + }]; function makeKeyRange(start, size) { var keys = []; for (var i = start; i < start + size; i++) { @@ -1179,18 +1168,30 @@ dom.importCssString(".normal-mode .ace_cursor{\ } var options = {}; - function defineOption(name, defaultValue, type) { - if (defaultValue === undefined) { throw Error('defaultValue is required'); } + function defineOption(name, defaultValue, type, aliases, callback) { + if (defaultValue === undefined && !callback) { + throw Error('defaultValue is required unless callback is provided'); + } if (!type) { type = 'string'; } options[name] = { type: type, - defaultValue: defaultValue + defaultValue: defaultValue, + callback: callback }; - setOption(name, defaultValue); + if (aliases) { + for (var i = 0; i < aliases.length; i++) { + options[aliases[i]] = options[name]; + } + } + if (defaultValue) { + setOption(name, defaultValue); + } } - function setOption(name, value, cm) { + function setOption(name, value, cm, cfg) { var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; if (!option) { throw Error('Unknown option: ' + name); } @@ -1202,17 +1203,60 @@ dom.importCssString(".normal-mode .ace_cursor{\ value = true; } } - option.value = option.type == 'boolean' ? !!value : value; + if (option.callback) { + if (scope !== 'local') { + option.callback(value, undefined); + } + if (scope !== 'global' && cm) { + option.callback(value, cm); + } + } else { + if (scope !== 'local') { + option.value = option.type == 'boolean' ? !!value : value; + } + if (scope !== 'global' && cm) { + cm.state.vim.options[name] = {value: value}; + } + } } - function getOption(name) { + function getOption(name, cm, cfg) { var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; if (!option) { throw Error('Unknown option: ' + name); } - return option.value; + if (option.callback) { + var local = cm && option.callback(undefined, cm); + if (scope !== 'global' && local !== undefined) { + return local; + } + if (scope !== 'local') { + return option.callback(); + } + return; + } else { + var local = (scope !== 'global') && (cm && cm.state.vim.options[name]); + return (local || (scope !== 'local') && option || {}).value; + } } + defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) { + // Option is local. Do nothing for global. + if (cm === undefined) { + return; + } + // The 'filetype' option proxies to the CodeMirror 'mode' option. + if (name === undefined) { + var mode = cm.getMode().name; + return mode == 'null' ? '' : mode; + } else { + var mode = name == '' ? 'null' : name; + cm.setOption('mode', mode); + } + }); + var createCircularJumpList = function() { var size = 100; var pointer = -1; @@ -1365,8 +1409,9 @@ dom.importCssString(".normal-mode .ace_cursor{\ visualBlock: false, lastSelection: null, lastPastedText: null, - sel: { - } + sel: {}, + // Buffer-local/window-local values of vim options. + options: {} }; } return cm.state.vim; @@ -1417,6 +1462,8 @@ dom.importCssString(".normal-mode .ace_cursor{\ // Testing hook. maybeInitVimState_: maybeInitVimState, + suppressErrorLogging: false, + InsertModeKey: InsertModeKey, map: function(lhs, rhs, ctx) { // Add user defined key bindings. @@ -1426,6 +1473,8 @@ dom.importCssString(".normal-mode .ace_cursor{\ // remove user defined key bindings. exCommandDispatcher.unmap(lhs, ctx); }, + // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace + // them, or somehow make them work with the existing CodeMirror setOption/getOption API. setOption: setOption, getOption: getOption, defineOption: defineOption, @@ -1567,7 +1616,9 @@ dom.importCssString(".normal-mode .ace_cursor{\ // clear VIM state in case it's in a bad state. cm.state.vim = undefined; maybeInitVimState(cm); - console['log'](e); + if (!CodeMirror.Vim.suppressErrorLogging) { + console['log'](e); + } throw e; } return true; @@ -1577,7 +1628,16 @@ dom.importCssString(".normal-mode .ace_cursor{\ }, handleEx: function(cm, input) { exCommandDispatcher.processCommand(cm, input); - } + }, + + defineMotion: defineMotion, + defineAction: defineAction, + defineOperator: defineOperator, + mapCommand: mapCommand, + _mapCommand: _mapCommand, + + exitVisualMode: exitVisualMode, + exitInsertMode: exitInsertMode }; // Represents the current input state. @@ -1827,12 +1887,10 @@ dom.importCssString(".normal-mode .ace_cursor{\ break; case 'search': this.processSearch(cm, vim, command); - clearInputState(cm); break; case 'ex': case 'keyToEx': this.processEx(cm, vim, command); - clearInputState(cm); break; default: break; @@ -1925,6 +1983,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ updateSearchQuery(cm, query, ignoreCase, smartCase); } catch (e) { showConfirm(cm, 'Invalid regex: ' + query); + clearInputState(cm); return; } commandDispatcher.processMotion(cm, vim, { @@ -1967,15 +2026,21 @@ dom.importCssString(".normal-mode .ace_cursor{\ } function onPromptKeyDown(e, query, close) { var keyName = CodeMirror.keyName(e); - if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || + (keyName == 'Backspace' && query == '')) { vimGlobalState.searchHistoryController.pushInput(query); vimGlobalState.searchHistoryController.reset(); updateSearchQuery(cm, originalQuery); clearSearchHighlight(cm); cm.scrollTo(originalScrollPos.left, originalScrollPos.top); CodeMirror.e_stop(e); + clearInputState(cm); close(); cm.focus(); + } else if (keyName == 'Ctrl-U') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); } } switch (command.searchArgs.querySrc) { @@ -2036,10 +2101,12 @@ dom.importCssString(".normal-mode .ace_cursor{\ } function onPromptKeyDown(e, input, close) { var keyName = CodeMirror.keyName(e), up; - if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || + (keyName == 'Backspace' && input == '')) { vimGlobalState.exCommandHistoryController.pushInput(input); vimGlobalState.exCommandHistoryController.reset(); CodeMirror.e_stop(e); + clearInputState(cm); close(); cm.focus(); } @@ -2047,6 +2114,10 @@ dom.importCssString(".normal-mode .ace_cursor{\ up = keyName == 'Up' ? true : false; input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ''; close(input); + } else if (keyName == 'Ctrl-U') { + // Ctrl-U clears input. + CodeMirror.e_stop(e); + close(''); } else { if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') vimGlobalState.exCommandHistoryController.reset(); @@ -2058,7 +2129,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ } else { if (vim.visualMode) { showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>', - onKeyDown: onPromptKeyDown}); + onKeyDown: onPromptKeyDown, select: false}); } else { showPrompt(cm, { onClose: onPromptClose, prefix: ':', onKeyDown: onPromptKeyDown}); @@ -2076,8 +2147,8 @@ dom.importCssString(".normal-mode .ace_cursor{\ var registerName = inputState.registerName; var sel = vim.sel; // TODO: Make sure cm and vim selections are identical outside visual mode. - var origHead = copyCursor(vim.visualMode ? sel.head: cm.getCursor('head')); - var origAnchor = copyCursor(vim.visualMode ? sel.anchor : cm.getCursor('anchor')); + var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head')); + var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor')); var oldHead = copyCursor(origHead); var oldAnchor = copyCursor(origAnchor); var newHead, newAnchor; @@ -2113,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; @@ -2250,7 +2323,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ var operatorMoveTo = operators[operator]( cm, operatorArgs, cmSel.ranges, oldAnchor, newHead); if (vim.visualMode) { - exitVisualMode(cm); + exitVisualMode(cm, operatorMoveTo != null); } if (operatorMoveTo) { cm.setCursor(operatorMoveTo); @@ -2627,6 +2700,10 @@ dom.importCssString(".normal-mode .ace_cursor{\ } }; + function defineMotion(name, fn) { + motions[name] = fn; + } + function fillArray(val, times) { var arr = []; for (var i = 0; i < times; i++) { @@ -2648,10 +2725,11 @@ dom.importCssString(".normal-mode .ace_cursor{\ var anchor = ranges[0].anchor, head = ranges[0].head; text = cm.getRange(anchor, head); - if (!isWhiteSpaceString(text)) { + var lastState = vim.lastEditInputState || {}; + if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) { // Exclude trailing whitespace if the range is not all whitespace. var match = (/\s+$/).exec(text); - if (match) { + if (match && lastState.motionArgs && lastState.motionArgs.forward) { head = offsetCursor(head, 0, - match[0].length); text = text.slice(0, - match[0].length); } @@ -2709,7 +2787,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ vimGlobalState.registerController.pushText( args.registerName, 'delete', text, args.linewise, vim.visualBlock); - return finalHead; + return clipCursorToContent(cm, finalHead); }, indent: function(cm, args, ranges) { var vim = cm.state.vim; @@ -2777,6 +2855,10 @@ dom.importCssString(".normal-mode .ace_cursor{\ } }; + function defineOperator(name, fn) { + operators[name] = fn; + } + var actions = { jumpListWalk: function(cm, actionArgs, vim) { if (vim.visualMode) { @@ -2790,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) { @@ -2993,6 +3076,11 @@ dom.importCssString(".normal-mode .ace_cursor{\ if (vim.visualMode) { curStart = cm.getCursor('anchor'); curEnd = cm.getCursor('head'); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curEnd; + curEnd = curStart; + curStart = tmp; + } curEnd.ch = lineLength(cm, curEnd.line) - 1; } else { // Repeat is the number of lines to join. Minimum 2 lines. @@ -3011,10 +3099,10 @@ dom.importCssString(".normal-mode .ace_cursor{\ cm.replaceRange(text, curStart, tmp); } var curFinalPos = Pos(curStart.line, finalCh); - cm.setCursor(curFinalPos); if (vim.visualMode) { - exitVisualMode(cm); + exitVisualMode(cm, false); } + cm.setCursor(curFinalPos); }, newLineAndEnterInsertMode: function(cm, actionArgs, vim) { vim.insertMode = true; @@ -3177,7 +3265,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ } } if (vim.visualMode) { - exitVisualMode(cm); + exitVisualMode(cm, false); } cm.setCursor(curPosFinal); }, @@ -3235,7 +3323,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? selections[0].anchor : selections[0].head; cm.setCursor(curStart); - exitVisualMode(cm); + exitVisualMode(cm, false); } else { cm.setCursor(offsetCursor(curEnd, 0, -1)); } @@ -3283,6 +3371,10 @@ dom.importCssString(".normal-mode .ace_cursor{\ exitInsertMode: exitInsertMode }; + function defineAction(name, fn) { + actions[name] = fn; + } + /* * Below are miscellaneous utility functions used by vim.js */ @@ -3412,9 +3504,6 @@ dom.importCssString(".normal-mode .ace_cursor{\ function lineLength(cm, lineNum) { return cm.getLine(lineNum).length; } - function reverse(s){ - return s.split('').reverse().join(''); - } function trim(s) { if (s.trim) { return s.trim(); @@ -3728,59 +3817,38 @@ dom.importCssString(".normal-mode .ace_cursor{\ // Seek to first word or non-whitespace character, depending on if // noSymbol is true. - var textAfterIdx = line.substring(idx); - var firstMatchedChar; - if (noSymbol) { - firstMatchedChar = textAfterIdx.search(/\w/); - } else { - firstMatchedChar = textAfterIdx.search(/\S/); + var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0]; + while (!test(line.charAt(idx))) { + idx++; + if (idx >= line.length) { return null; } } - if (firstMatchedChar == -1) { - return null; - } - idx += firstMatchedChar; - textAfterIdx = line.substring(idx); - var textBeforeIdx = line.substring(0, idx); - var matchRegex; - // Greedy matchers for the "word" we are trying to expand. if (bigWord) { - matchRegex = /^\S+/; + test = bigWordCharTest[0]; } else { - if ((/\w/).test(line.charAt(idx))) { - matchRegex = /^\w+/; - } else { - matchRegex = /^[^\w\s]+/; + test = wordCharTest[0]; + if (!test(line.charAt(idx))) { + test = wordCharTest[1]; } } - var wordAfterRegex = matchRegex.exec(textAfterIdx); - var wordStart = idx; - var wordEnd = idx + wordAfterRegex[0].length; - // TODO: Find a better way to do this. It will be slow on very long lines. - var revTextBeforeIdx = reverse(textBeforeIdx); - var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx); - if (wordBeforeRegex) { - wordStart -= wordBeforeRegex[0].length; - } + var end = idx, start = idx; + while (test(line.charAt(end)) && end < line.length) { end++; } + while (test(line.charAt(start)) && start >= 0) { start--; } + start++; if (inclusive) { - // If present, trim all whitespace after word. - // Otherwise, trim all whitespace before word. - var textAfterWordEnd = line.substring(wordEnd); - var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length; - if (whitespacesAfterWord > 0) { - wordEnd += whitespacesAfterWord; - } else { - var revTrim = revTextBeforeIdx.length - wordStart; - var textBeforeWordStart = revTextBeforeIdx.substring(revTrim); - var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length; - wordStart -= whitespacesBeforeWord; + // If present, include all whitespace after word. + // Otherwise, include all whitespace before word, except indentation. + var wordEnd = end; + while (/\s/.test(line.charAt(end)) && end < line.length) { end++; } + if (wordEnd == end) { + var wordStart = start; + while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; } + if (!start) { start = wordStart; } } } - - return { start: Pos(cur.line, wordStart), - end: Pos(cur.line, wordEnd) }; + return { start: Pos(cur.line, start), end: Pos(cur.line, end) }; } function recordJumpPosition(cm, oldCur, newCur) { @@ -3938,7 +4006,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ var pos = cur.ch; var line = cm.getLine(lineNum); var dir = forward ? 1 : -1; - var regexps = bigWord ? bigWordRegexp : wordRegexp; + var charTests = bigWord ? bigWordCharTest: wordCharTest; if (emptyLineIsWord && line == '') { lineNum += dir; @@ -3958,11 +4026,11 @@ dom.importCssString(".normal-mode .ace_cursor{\ // Find bounds of next word. while (pos != stop) { var foundWord = false; - for (var i = 0; i < regexps.length && !foundWord; ++i) { - if (regexps[i].test(line.charAt(pos))) { + for (var i = 0; i < charTests.length && !foundWord; ++i) { + if (charTests[i](line.charAt(pos))) { wordStart = pos; // Advance to end of word. - while (pos != stop && regexps[i].test(line.charAt(pos))) { + while (pos != stop && charTests[i](line.charAt(pos))) { pos += dir; } wordEnd = pos; @@ -4279,6 +4347,12 @@ dom.importCssString(".normal-mode .ace_cursor{\ }, setReversed: function(reversed) { vimGlobalState.isReversed = reversed; + }, + getScrollbarAnnotate: function() { + return this.annotate; + }, + setScrollbarAnnotate: function(annotate) { + this.annotate = annotate; } }; function getSearchState(cm) { @@ -4288,7 +4362,8 @@ dom.importCssString(".normal-mode .ace_cursor{\ function dialog(cm, template, shortText, onClose, options) { if (cm.openDialog) { cm.openDialog(template, onClose, { bottom: true, value: options.value, - onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp }); + onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp, + selectValueOnOpen: false}); } else { onClose(prompt(shortText, '')); @@ -4557,14 +4632,21 @@ dom.importCssString(".normal-mode .ace_cursor{\ }; } function highlightSearchMatches(cm, query) { - var overlay = getSearchState(cm).getOverlay(); + var searchState = getSearchState(cm); + var overlay = searchState.getOverlay(); if (!overlay || query != overlay.query) { if (overlay) { cm.removeOverlay(overlay); } overlay = searchOverlay(query); cm.addOverlay(overlay); - getSearchState(cm).setOverlay(overlay); + if (cm.showMatchesOnScrollbar) { + if (searchState.getScrollbarAnnotate()) { + searchState.getScrollbarAnnotate().clear(); + } + searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query)); + } + searchState.setOverlay(overlay); } } function findNext(cm, prev, query, repeat) { @@ -4589,8 +4671,13 @@ dom.importCssString(".normal-mode .ace_cursor{\ }); } function clearSearchHighlight(cm) { + var state = getSearchState(cm); cm.removeOverlay(getSearchState(cm).getOverlay()); - getSearchState(cm).setOverlay(null); + state.setOverlay(null); + if (state.getScrollbarAnnotate()) { + state.getScrollbarAnnotate().clear(); + state.setScrollbarAnnotate(null); + } } /** * Check if pos is in the specified range, INCLUSIVE. @@ -4631,6 +4718,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ // pair of commands that have a shared prefix, at least one of their // shortNames must not match the prefix of the other command. var defaultExCommandMap = [ + { name: 'colorscheme', shortName: 'colo' }, { name: 'map' }, { name: 'imap', shortName: 'im' }, { name: 'nmap', shortName: 'nm' }, @@ -4639,7 +4727,10 @@ dom.importCssString(".normal-mode .ace_cursor{\ { name: 'write', shortName: 'w' }, { name: 'undo', shortName: 'u' }, { name: 'redo', shortName: 'red' }, - { name: 'set', shortName: 'set' }, + { name: 'set', shortName: 'se' }, + { name: 'set', shortName: 'se' }, + { name: 'setlocal', shortName: 'setl' }, + { name: 'setglobal', shortName: 'setg' }, { name: 'sort', shortName: 'sor' }, { name: 'substitute', shortName: 's', possiblyAsync: true }, { name: 'nohlsearch', shortName: 'noh' }, @@ -4652,6 +4743,13 @@ dom.importCssString(".normal-mode .ace_cursor{\ }; ExCommandDispatcher.prototype = { processCommand: function(cm, input, opt_params) { + var that = this; + cm.operation(function () { + cm.curOp.isVimOp = true; + that._processCommand(cm, input, opt_params); + }); + }, + _processCommand: function(cm, input, opt_params) { var vim = cm.state.vim; var commandHistoryRegister = vimGlobalState.registerController.getRegister(':'); var previousCommand = commandHistoryRegister.toString(); @@ -4864,6 +4962,13 @@ dom.importCssString(".normal-mode .ace_cursor{\ }; var exCommands = { + colorscheme: function(cm, params) { + if (!params.args || params.args.length < 1) { + showConfirm(cm, cm.getOption('theme')); + return; + } + cm.setOption('theme', params.args[0]); + }, map: function(cm, params, ctx) { var mapArgs = params.args; if (!mapArgs || mapArgs.length < 2) { @@ -4897,6 +5002,9 @@ dom.importCssString(".normal-mode .ace_cursor{\ }, set: function(cm, params) { var setArgs = params.args; + // Options passed through to the setOption/getOption calls. May be passed in by the + // local/global versions of the set command + var setCfg = params.setCfg || {}; if (!setArgs || setArgs.length < 1) { if (cm) { showConfirm(cm, 'Invalid mapping: ' + params.input); @@ -4920,24 +5028,35 @@ dom.importCssString(".normal-mode .ace_cursor{\ optionName = optionName.substring(2); value = false; } + var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean'; if (optionIsBoolean && value == undefined) { // Calling set with a boolean option sets it to true. value = true; } - if (!optionIsBoolean && !value || forceGet) { - var oldValue = getOption(optionName); - // If no value is provided, then we assume this is a get. + // If no value is provided, then we assume this is a get. + if (!optionIsBoolean && value === undefined || forceGet) { + var oldValue = getOption(optionName, cm, setCfg); if (oldValue === true || oldValue === false) { showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName); } else { showConfirm(cm, ' ' + optionName + '=' + oldValue); } } else { - setOption(optionName, value, cm); + setOption(optionName, value, cm, setCfg); } }, - registers: function(cm,params) { + setlocal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'local'}; + this.set(cm, params); + }, + setglobal: function (cm, params) { + // setCfg is passed through to setOption + params.setCfg = {scope: 'global'}; + this.set(cm, params); + }, + registers: function(cm, params) { var regArgs = params.args; var registers = vimGlobalState.registerController.registers; var regInfo = '----------Registers----------

'; @@ -5417,6 +5536,19 @@ dom.importCssString(".normal-mode .ace_cursor{\ } } + function _mapCommand(command) { + defaultKeymap.push(command); + } + + function mapCommand(keys, type, name, args, extra) { + var command = {keys: keys, type: type}; + command[type] = name; + command[type + "Args"] = args; + for (var key in extra) + command[key] = extra[key]; + _mapCommand(command); + } + // The timeout in milliseconds for the two-character ESC keymap should be // adjusted according to your typing speed to prevent false positives. defineOption('insertModeEscKeysTimeout', 200, 'number'); @@ -5546,7 +5678,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ } function updateFakeCursor(cm) { var vim = cm.state.vim; - var from = copyCursor(vim.sel.head); + var from = clipCursorToContent(cm, copyCursor(vim.sel.head)); var to = offsetCursor(from, 0, 1); if (vim.fakeCursor) { vim.fakeCursor.clear(); @@ -5557,7 +5689,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ var anchor = cm.getCursor('anchor'); var head = cm.getCursor('head'); // Enter or exit visual mode to match mouse selection. - if (vim.visualMode && cursorEqual(head, anchor) && lineLength(cm, head.line) > head.ch) { + if (vim.visualMode && !cm.somethingSelected()) { exitVisualMode(cm, false); } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) { vim.visualMode = true; @@ -5597,6 +5729,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ var macroModeState = vimGlobalState.macroModeState; var lastChange = macroModeState.lastInsertModeChanges; var keyName = CodeMirror.keyName(e); + if (!keyName) { return; } function onKeyFound() { lastChange.changes.push(new InsertModeKey(keyName)); return true; @@ -5790,7 +5923,7 @@ dom.importCssString(".normal-mode .ace_cursor{\ }, true); } return isHandled; - }; + } exports.CodeMirror = CodeMirror; var getVim = Vim.maybeInitVimState_; exports.handler = { @@ -5804,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; @@ -5970,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; @@ -6021,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); @@ -6040,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/keyboard/vim_test.js b/lib/ace/keyboard/vim_test.js index 8b726a33..127f0706 100644 --- a/lib/ace/keyboard/vim_test.js +++ b/lib/ace/keyboard/vim_test.js @@ -34,6 +34,7 @@ editor.session.setMode(new JavaScriptMode()); function CodeMirror(place, opts) { if (opts.value != null) editor.session.setValue(opts.value); + editor.setOption("indentedSoftWrap", false); editor.setOption("wrap", opts.lineWrapping); editor.setOption("useSoftTabs", !opts.indentWithTabs); editor.setKeyboardHandler(null); @@ -58,6 +59,7 @@ function CodeMirror(place, opts) { cm.setSize(500, 300); return cm; } +CodeMirror.defineMode = function() {} for (var key in vim.CodeMirror) CodeMirror[key] = vim.CodeMirror[key]; var editor; @@ -73,6 +75,9 @@ function test(name, fn) { } vim.CodeMirror.Vim.unmap("Y"); +vim.CodeMirror.Vim.defineEx('write', 'w', function(cm) { + CodeMirror.commands.save(cm); +}); @@ -592,7 +597,7 @@ testVim('{', function(cm, vim, helpers) { helpers.doKeys('6', '{'); helpers.assertCursorAt(0, 0); }, { value: 'a\n\nb\nc\n\nd' }); -testVim('paragraph motions', function(cm, vim, helpers) { +testVim('paragraph_motions', function(cm, vim, helpers) { cm.setCursor(10, 0); helpers.doKeys('{'); helpers.assertCursorAt(4, 0); @@ -685,7 +690,7 @@ testVim('dl_eol', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq(' ', register.toString()); is(!register.linewise); - helpers.assertCursorAt(0, 6); + helpers.assertCursorAt(0, 5); }, { value: ' word1 ' }); testVim('dl_repeat', function(cm, vim, helpers) { var curStart = makeCursor(0, 0); @@ -767,6 +772,16 @@ testVim('dw_word', function(cm, vim, helpers) { is(!register.linewise); eqPos(curStart, cm.getCursor()); }, { value: ' word1 word2' }); +testVim('dw_unicode_word', function(cm, vim, helpers) { + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 10); + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 6); + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 5); + helpers.doKeys('d', 'e'); + eq(cm.getValue().length, 2); +}, { value: ' \u0562\u0561\u0580\u0587\xbbe\xb5g ' }); testVim('dw_only_word', function(cm, vim, helpers) { // Test that if there is only 1 word left, dw deletes till the end of the // line. @@ -776,7 +791,7 @@ testVim('dw_only_word', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq('word1 ', register.toString()); is(!register.linewise); - helpers.assertCursorAt(0, 1); + helpers.assertCursorAt(0, 0); }, { value: ' word1 ' }); testVim('dw_eol', function(cm, vim, helpers) { // Assert that dw does not delete the newline if last word to delete is at end @@ -787,7 +802,7 @@ testVim('dw_eol', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq('word1', register.toString()); is(!register.linewise); - helpers.assertCursorAt(0, 1); + helpers.assertCursorAt(0, 0); }, { value: ' word1\nword2' }); testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) { // Assert that dw does not delete the newline if last word to delete is at end @@ -798,7 +813,7 @@ testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq('word1', register.toString()); is(!register.linewise); - helpers.assertCursorAt(0, 1); + helpers.assertCursorAt(0, 0); }, { value: ' word1\n\nword2' }); testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) { cm.setCursor(0, 0); @@ -844,7 +859,7 @@ testVim('dw_repeat', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq('word1\nword2', register.toString()); is(!register.linewise); - helpers.assertCursorAt(0, 1); + helpers.assertCursorAt(0, 0); }, { value: ' word1\nword2' }); testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) { cm.setCursor(0, 0); @@ -1062,6 +1077,17 @@ testVim('cc_multiply_repeat', function(cm, vim, helpers) { is(register.linewise); eq('vim-insert', cm.getOption('keyMap')); }); +testVim('ct', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('c', 't', 'w'); + eq(' word1 word3', cm.getValue()); + helpers.doKeys('', 'c', '|'); + eq(' word3', cm.getValue()); + helpers.assertCursorAt(0, 0); + helpers.doKeys('', '2', 'u', 'w', 'h'); + helpers.doKeys('c', '2', 'g', 'e'); + eq(' wordword3', cm.getValue()); +}, { value: ' word1 word2 word3'}); testVim('cc_should_not_append_to_document', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount(); cm.setCursor(cm.lastLine(), 0); @@ -1371,7 +1397,7 @@ testVim('D', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq('rd1', register.toString()); is(!register.linewise); - helpers.assertCursorAt(0, 3); + helpers.assertCursorAt(0, 2); }, { value: ' word1\nword2\n word3' }); testVim('C', function(cm, vim, helpers) { var curStart = makeCursor(0, 3); @@ -1962,8 +1988,11 @@ testVim('visual_block_move_to_eol', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('', 'G', '$'); var selections = cm.getSelections().join(); - console.log(selections); - eq("123,45,6", selections); + eq('123,45,6', selections); + // Checks that with cursor at Infinity, finding words backwards still works. + helpers.doKeys('2', 'k', 'b'); + selections = cm.getSelections().join(); + eq('1', selections); }, {value: '123\n45\n6'}); testVim('visual_block_different_line_lengths', function(cm, vim, helpers) { // test the block selection with lines of different length @@ -2053,6 +2082,11 @@ testVim('visual_join', function(cm, vim, helpers) { eq(' 1 2 3\n 4\n 5', cm.getValue()); is(!vim.visualMode); }, { value: ' 1\n 2\n 3\n 4\n 5' }); +testVim('visual_join_2', function(cm, vim, helpers) { + helpers.doKeys('G', 'V', 'g', 'g', 'J'); + eq('1 2 3 4 5 6 ', cm.getValue()); + is(!vim.visualMode); +}, { value: '1\n2\n3\n4\n5\n6\n'}); testVim('visual_blank', function(cm, vim, helpers) { helpers.doKeys('v', 'k'); eq(vim.visualMode, true); @@ -2260,6 +2294,15 @@ testVim('S_visual', function(cm, vim, helpers) { eq('\ncc', cm.getValue()); }, { value: 'aa\nbb\ncc'}); +testVim('d_/', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('2', 'd', '/'); + helpers.assertCursorAt(0, 0); + eq('match \n next', cm.getValue()); + cm.openDialog = helpers.fakeOpenDialog('2'); + helpers.doKeys('d', ':'); + // TODO eq(' next', cm.getValue()); +}, { value: 'text match match \n next' }); testVim('/ and n/N', function(cm, vim, helpers) { cm.openDialog = helpers.fakeOpenDialog('match'); helpers.doKeys('/'); @@ -2777,6 +2820,44 @@ testVim('exCommand_history', function(cm, vim, helpers) { onKeyDown({keyCode: keyCodes.Up}, input, close); eq(input, 'sort'); }, {value: ''}); +testVim('search_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys('/'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); +testVim('exCommand_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys(':'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); testVim('.', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('2', 'd', 'w'); @@ -3204,7 +3285,7 @@ testVim('scrollMotion', function(cm, vim, helpers){ cm.refresh(); //ace! prevScrollInfo = cm.getScrollInfo(); helpers.doKeys(''); - eq(prevCursor.line - 1, cm.getCursor().line); + eq(prevCursor.line - 1, cm.getCursor().line, "Y"); is(prevScrollInfo.top > cm.getScrollInfo().top); }, { value: scrollMotionSandbox}); @@ -3727,17 +3808,111 @@ testVim('set_string', function(cm, vim, helpers) { eq('c', CodeMirror.Vim.getOption('testoption')); }); testVim('ex_set_string', function(cm, vim, helpers) { - CodeMirror.Vim.defineOption('testoption', 'a', 'string'); + CodeMirror.Vim.defineOption('testopt', 'a', 'string'); // Test default value is set. - eq('a', CodeMirror.Vim.getOption('testoption')); + eq('a', CodeMirror.Vim.getOption('testopt')); try { - // Test fail to set 'notestoption' - helpers.doEx('set notestoption=b'); + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); fail(); } catch (expected) {}; // Test setOption - helpers.doEx('set testoption=c') - eq('c', CodeMirror.Vim.getOption('testoption')); + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt')); + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}); +testVim('ex_set_callback', function(cm, vim, helpers) { + var global; + + function cb(val, cm, cfg) { + if (val === undefined) { + // Getter + if (cm) { + return cm._local; + } else { + return global; + } + } else { + // Setter + if (cm) { + cm._local = val; + } else { + global = val; + } + } + } + + CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testopt')); + try { + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); + fail(); + } catch (expected) {}; + // Test setOption (Identical to the string tests, but via callback instead) + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}) +testVim('ex_set_filetype', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + CodeMirror.defineMode('test_mode_2', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + // Test mode is set. + helpers.doEx('set filetype=test_mode'); + eq('test_mode', cm.getMode().name); + // Test 'ft' alias also sets mode. + helpers.doEx('set ft=test_mode_2'); + eq('test_mode_2', cm.getMode().name); +}); +testVim('ex_set_filetype_null', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + cm.setOption('mode', 'test_mode'); + // Test mode is set to null. + helpers.doEx('set filetype='); + eq('null', cm.getMode().name); }); // TODO: Reset key maps after each test. testVim('ex_map_key2key', function(cm, vim, helpers) { 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 37e038b5..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,27 +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 lineRange = new Range( - row, range.start.column, - row, this.session.getScreenLastRowColumn(row) - ); - this.drawSingleLineMarker(stringBuilder, lineRange, clazz + " ace_start", layerConfig, 1, extraStyle); - - // selection end - row = range.end.row; - lineRange = new Range(row, 0, 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.end.row = row; - lineRange.end.column = this.session.getScreenLastRowColumn(row); - this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1, extraStyle); + var session = this.session; + 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); } }; @@ -134,7 +137,7 @@ var Marker = function(parentEl) { extraStyle = extraStyle || ""; stringBuilder.push( - "
" - + this.TAB_CHAR - + lang.stringRepeat("\xa0", i - 1) + + lang.stringRepeat(this.TAB_CHAR, i) + ""); } else { - tabStr.push(lang.stringRepeat("\xa0", i)); + tabStr.push(lang.stringRepeat(" ", i)); } } if (this.displayIndentGuides) { @@ -145,9 +144,9 @@ 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("\xa0", this.tabSize - 1); + var tabContent = lang.stringRepeat(this.TAB_CHAR, this.tabSize); } else{ - var spaceContent = lang.stringRepeat("\xa0", this.tabSize); + var spaceContent = lang.stringRepeat(" ", this.tabSize); var tabContent = spaceContent; } @@ -325,9 +324,9 @@ var Text = function(parentEl) { var replaceReg = /\t|&|<|( +)|([\x00-\x1f\x80-\xa0\xad\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\u3000\uFEFF])|[\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]/g; var replaceFunc = function(c, a, b, tabIdx, idx4) { if (a) { - return self.showInvisibles ? - "" + lang.stringRepeat(self.SPACE_CHAR, c.length) + "" : - lang.stringRepeat("\xa0", c.length); + return self.showInvisibles + ? "" + lang.stringRepeat(self.SPACE_CHAR, c.length) + "" + : c; } else if (c == "&") { return "&"; } else if (c == "<") { @@ -420,6 +419,8 @@ var Text = function(parentEl) { ); } + stringBuilder.push(lang.stringRepeat("\xa0", splits.indent)); + split ++; screenColumn = 0; splitChars = splits[split] || Number.MAX_VALUE; diff --git a/lib/ace/layer/text_test.js b/lib/ace/layer/text_test.js index 57601207..3946ec66 100644 --- a/lib/ace/layer/text_test.js +++ b/lib/ace/layer/text_test.js @@ -89,9 +89,9 @@ module.exports = { "test rendering of indent guides" : function() { var textLayer = this.textLayer var EOL = "" + textLayer.EOL_CHAR + ""; - var SPACE = function(i) {return Array(i+1).join("\xa0")} + 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/lib/keys.js b/lib/ace/lib/keys.js index 9a8d7f58..465d4a8d 100644 --- a/lib/ace/lib/keys.js +++ b/lib/ace/lib/keys.js @@ -104,8 +104,8 @@ var Keys = (function() { 73: 'i', 74: 'j', 75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o', 80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y', 90: 'z', 107: '+', 109: '-', 110: '.', - 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`', 219: '[', - 220: '\\',221: ']', 222: '\'' + 186: ';', 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`', + 219: '[', 220: '\\',221: ']', 222: '\'' } }; diff --git a/lib/ace/lib/lang.js b/lib/ace/lib/lang.js index 863bbb55..921dee1d 100644 --- a/lib/ace/lib/lang.js +++ b/lib/ace/lib/lang.js @@ -81,20 +81,24 @@ exports.copyArray = function(array){ return copy; }; -exports.deepCopy = function (obj) { +exports.deepCopy = function deepCopy(obj) { if (typeof obj !== "object" || !obj) return obj; + var copy; + if (Array.isArray(obj)) { + copy = []; + for (var key = 0; key < obj.length; key++) { + copy[key] = deepCopy(obj[key]); + } + return copy; + } var cons = obj.constructor; if (cons === RegExp) return obj; - var copy = cons(); + copy = cons(); for (var key in obj) { - if (typeof obj[key] === "object") { - copy[key] = exports.deepCopy(obj[key]); - } else { - copy[key] = obj[key]; - } + copy[key] = deepCopy(obj[key]); } return copy; }; 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/_test/tokens_rust.json b/lib/ace/mode/_test/tokens_rust.json index 6592575b..8c59a3aa 100644 --- a/lib/ace/mode/_test/tokens_rust.json +++ b/lib/ace/mode/_test/tokens_rust.json @@ -10,10 +10,9 @@ ],[ "start", ["keyword.source.rust","fn"], - ["meta.function.source.rust"," "], + ["text"," "], ["entity.name.function.source.rust","main"], - ["meta.function.source.rust","("], - ["text",") {"] + ["text","() {"] ],[ "start", ["text"," "], @@ -88,10 +87,14 @@ ],[ "start", ["keyword.source.rust","fn"], - ["meta.function.source.rust"," "], - ["entity.name.function.source.rust","map"], - ["meta.function.source.rust","("], - ["text","vector: &[T]"], + ["text"," "], + ["entity.name.function.source.rust","map"], + ["keyword.operator","<"], + ["text","T"], + ["keyword.operator",","], + ["text"," U"], + ["keyword.operator",">"], + ["text","(vector: &[T]"], ["keyword.operator",","], ["text"," function: &fn(v: &T) "], ["keyword.operator","->"], diff --git a/lib/ace/mode/_test/tokens_vbscript.json b/lib/ace/mode/_test/tokens_vbscript.json index a49f976d..05d5dd22 100644 --- a/lib/ace/mode/_test/tokens_vbscript.json +++ b/lib/ace/mode/_test/tokens_vbscript.json @@ -199,7 +199,5 @@ ["string.quoted.double.asp"," ... updated.\""] ],[ "start", - ["support.function.asp","End"], - ["text"," "], - ["storage.type.asp","Sub"] + ["storage.type.asp","End Sub"] ]] \ No newline at end of file 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 c67f1130..dd1b0d14 100644 --- a/lib/ace/mode/behaviour/cstyle.js +++ b/lib/ace/mode/behaviour/cstyle.js @@ -63,6 +63,19 @@ var initContext = function(editor) { }; }; +var getWrapped = function(selection, selected, opening, closing) { + var rowDiff = selection.end.row - selection.start.row; + return { + text: opening + selected + closing, + selection: [ + 0, + selection.start.column + 1, + rowDiff, + selection.end.column + (rowDiff ? 0 : 1) + ] + }; +}; + var CstyleBehaviour = function() { this.add("braces", "insertion", function(state, action, editor, session, text) { var cursor = editor.getCursorPosition(); @@ -72,10 +85,7 @@ var CstyleBehaviour = function() { var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) { - return { - text: '{' + selected + '}', - selection: false - }; + return getWrapped(selection, selected, '{', '}'); } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { if (/[\]\}\)]/.test(line[cursor.column]) || editor.inMultiSelectMode) { CstyleBehaviour.recordAutoInsert(editor, session, "}"); @@ -155,10 +165,7 @@ var CstyleBehaviour = function() { var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && editor.getWrapBehavioursEnabled()) { - return { - text: '(' + selected + ')', - selection: false - }; + return getWrapped(selection, selected, '(', ')'); } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { CstyleBehaviour.recordAutoInsert(editor, session, ")"); return { @@ -203,10 +210,7 @@ var CstyleBehaviour = function() { var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && editor.getWrapBehavioursEnabled()) { - return { - text: '[' + selected + ']', - selection: false - }; + return getWrapped(selection, selected, '[', ']'); } else if (CstyleBehaviour.isSaneInsertion(editor, session)) { CstyleBehaviour.recordAutoInsert(editor, session, "]"); return { @@ -252,10 +256,7 @@ var CstyleBehaviour = function() { var selection = editor.getSelectionRange(); var selected = session.doc.getTextRange(selection); if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) { - return { - text: quote + selected + quote, - selection: false - }; + return getWrapped(selection, selected, quote, quote); } else if (!selected) { var cursor = editor.getCursorPosition(); var line = session.doc.getLine(cursor.row); @@ -268,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/folding/cstyle.js b/lib/ace/mode/folding/cstyle.js index a98807ab..6a6abca7 100644 --- a/lib/ace/mode/folding/cstyle.js +++ b/lib/ace/mode/folding/cstyle.js @@ -53,7 +53,7 @@ oop.inherits(FoldMode, BaseFoldMode); this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/; this.singleLineBlockCommentRe= /^\s*(\/\*).*\*\/\s*$/; this.tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/; - this.startRegionRe = /^\s*(\/\*|\/\/)#region\b/; + this.startRegionRe = /^\s*(\/\*|\/\/)#?region\b/; //prevent naming conflict with any modes that inherit from cstyle and override this (like csharp) this._getFoldWidgetBase = this.getFoldWidget; @@ -69,6 +69,8 @@ oop.inherits(FoldMode, BaseFoldMode); * * @example tripleStarFoldingSection * /*** this folds even though 1 line because it has 3 stars ***[/] + * + * @note the pound symbol for region tags is optional */ this.getFoldWidget = function(session, foldStyle, row) { var line = session.getLine(row); @@ -158,12 +160,16 @@ oop.inherits(FoldMode, BaseFoldMode); return new Range(startRow, startColumn, endRow, session.getLine(endRow).length); }; + /** + * gets comment region block with end region assumed to be start of comment in any cstyle mode or SQL mode (--) which inherits from this. + * There may optionally be a pound symbol before the region/endregion statement + */ this.getCommentRegionBlock = function(session, line, row) { var startColumn = line.search(/\s*$/); var maxRow = session.getLength(); var startRow = row; - var re = /^\s*(?:\/\*|\/\/)#(end)?region\b/; + var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/; var depth = 1; while (++row < maxRow) { line = session.getLine(row); diff --git a/lib/ace/mode/folding/sqlserver.js b/lib/ace/mode/folding/sqlserver.js new file mode 100644 index 00000000..b3fd4e93 --- /dev/null +++ b/lib/ace/mode/folding/sqlserver.js @@ -0,0 +1,111 @@ +/* ***** 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 Range = require("../../range").Range; +var BaseFoldMode = require("./cstyle").FoldMode; + +var FoldMode = exports.FoldMode = function() {}; + +oop.inherits(FoldMode, BaseFoldMode); + +(function() { + /** + * Inheriting cstyle folding because it handles the region comment folding + * and special block comment folding appropriately. + * + * Cstyle's getCommentRegionBlock() contains the sql comment characters '--' for end region block. + */ + + this.foldingStartMarker = /(\bCASE\b|\bBEGIN\b)|^\s*(\/\*)/i; + // this.foldingStopMarker = /(\bEND\b)|^[\s\*]*(\*\/)/i; + this.startRegionRe = /^\s*(\/\*|--)#?region\b/; + + this.getFoldWidgetRange = function(session, foldStyle, row, forceMultiline) { + var line = session.getLine(row); + + if (this.startRegionRe.test(line)) return this.getCommentRegionBlock(session, line, row); + + var match = line.match(this.foldingStartMarker); + if (match) { + var i = match.index; + if (match[1]) return this.getBeginEndBlock(session, row, i, match[1]); + + var range = session.getCommentFoldRange(row, i + match[0].length, 1); + if (range && !range.isMultiLine()) { + if (forceMultiline) { + range = this.getSectionRange(session, row); + } + else if (foldStyle != "all") range = null; + } + + return range; + } + + if (foldStyle === "markbegin") return; + //TODO: add support for end folding markers + return; + }; + + /** + * @returns {range} folding block for sequence that starts with 'CASE' or 'BEGIN' and ends with 'END' + * @param {string} matchSequence - the sequence of charaters that started the fold widget, which should remain visible when the fold widget is folded + */ + this.getBeginEndBlock = function(session, row, column, matchSequence) { + var start = { + row: row, + column: column + matchSequence.length + }; + var maxRow = session.getLength(); + var line; + + var depth = 1; + var re = /(\bCASE\b|\bBEGIN\b)|(\bEND\b)/i; + while (++row < maxRow) { + line = session.getLine(row); + var m = re.exec(line); + if (!m) continue; + if (m[1]) depth++; + else depth--; + + if (!depth) break; + } + var endRow = row; + if (endRow > start.row) { + return new Range(start.row, start.column, endRow, line.length); + } + }; + +}).call(FoldMode.prototype); + +}); 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/html_highlight_rules.js b/lib/ace/mode/html_highlight_rules.js index 9c9cc36f..4effa2aa 100644 --- a/lib/ace/mode/html_highlight_rules.js +++ b/lib/ace/mode/html_highlight_rules.js @@ -65,7 +65,7 @@ var HtmlHighlightRules = function() { include : "tag_whitespace" }, { token : "entity.other.attribute-name.xml", - regex : "[-_a-zA-Z0-9:]+" + regex : "[-_a-zA-Z0-9:.]+" }, { token : "keyword.operator.attribute-equals.xml", regex : "=", @@ -89,7 +89,7 @@ var HtmlHighlightRules = function() { return ["meta.tag.punctuation." + (start == "<" ? "" : "end-") + "tag-open.xml", "meta.tag" + (group ? "." + group : "") + ".tag-name.xml"]; }, - regex : "(= 0 && length <= MAX_ARRAY_INDEX; + }; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return obj; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, length = obj.length; i < length; i++) { - if (iterator.call(context, obj[i], i, obj) === breaker) return; + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); - for (var i = 0, length = keys.length; i < length; i++) { - if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } return results; }; - var reduceError = 'Reduce of empty array with no initial value'; + // Create a reducing function iterating left or right. + function createReduce(dir) { + // Optimized iterator function as using arguments.length + // in the main function will deoptimize the, see #1991. + function iterator(obj, iteratee, memo, keys, index, length) { + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + } + + return function(obj, iteratee, memo, context) { + iteratee = optimizeCb(iteratee, context, 4); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + // Determine the initial value if none is provided. + if (arguments.length < 3) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + return iterator(obj, iteratee, memo, keys, index, length); + }; + } // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; + // or `foldl`. + _.reduce = _.foldl = _.inject = createReduce(1); // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var length = obj.length; - if (length !== +length) { - var keys = _.keys(obj); - length = keys.length; - } - each(obj, function(value, index, list) { - index = keys ? keys[--length] : --length; - if (!initial) { - memo = obj[index]; - initial = true; - } else { - memo = iterator.call(context, memo, obj[index], index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; + _.reduceRight = _.foldr = createReduce(-1); // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { - var result; - any(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; + var key; + if (isArrayLike(obj)) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context); - each(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) results.push(value); + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { - return _.filter(obj, function(value, index, list) { - return !predicate.call(context, value, index, list); - }, context); + return _.filter(obj, _.negate(cb(predicate)), context); }; // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. _.every = _.all = function(obj, predicate, context) { - predicate || (predicate = _.identity); - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context); - each(obj, function(value, index, list) { - if (!(result = result && predicate.call(context, value, index, list))) return breaker; - }); - return !!result; + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; }; // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. - var any = _.some = _.any = function(obj, predicate, context) { - predicate || (predicate = _.identity); - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); - each(obj, function(value, index, list) { - if (result || (result = predicate.call(context, value, index, list))) return breaker; - }); - return !!result; + _.some = _.any = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; }; // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { - if (obj == null) return false; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - return any(obj, function(value) { - return value === target; - }); + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, target, fromIndex) { + if (!isArrayLike(obj)) obj = _.values(obj); + return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. @@ -256,7 +293,8 @@ module.exports = { var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) { - return (isFunc ? method : value[method]).apply(value, args); + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); }); }; @@ -268,60 +306,76 @@ module.exports = { // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs) { - return _.filter(obj, _.matches(attrs)); + return _.filter(obj, _.matcher(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { - return _.find(obj, _.matches(attrs)); + return _.find(obj, _.matcher(attrs)); }; - // Return the maximum element or (element-based computation). - // Can't optimize arrays of integers longer than 65,535 elements. - // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.max.apply(Math, obj); - } - var result = -Infinity, lastComputed = -Infinity; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - if (computed > lastComputed) { - result = value; - lastComputed = computed; + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value > result) { + result = value; + } } - }); + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = value; + lastComputed = computed; + } + }); + } return result; }; // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.min.apply(Math, obj); - } - var result = Infinity, lastComputed = Infinity; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - if (computed < lastComputed) { - result = value; - lastComputed = computed; + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value < result) { + result = value; + } } - }); + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = value; + lastComputed = computed; + } + }); + } return result; }; - // Shuffle an array, using the modern version of the + // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { - var rand; - var index = 0; - var shuffled = []; - each(obj, function(value) { - rand = _.random(index++); - shuffled[index - 1] = shuffled[rand]; - shuffled[rand] = value; - }); + var set = isArrayLike(obj) ? obj : _.values(obj); + var length = set.length; + var shuffled = Array(length); + for (var index = 0, rand; index < length; index++) { + rand = _.random(0, index); + if (rand !== index) shuffled[index] = shuffled[rand]; + shuffled[rand] = set[index]; + } return shuffled; }; @@ -330,27 +384,20 @@ module.exports = { // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (n == null || guard) { - if (obj.length !== +obj.length) obj = _.values(obj); + if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); }; - // An internal function to generate lookup iterators. - var lookupIterator = function(value) { - if (value == null) return _.identity; - if (_.isFunction(value)) return value; - return _.property(value); - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - iterator = lookupIterator(iterator); + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, - criteria: iterator.call(context, value, index, list) + criteria: iteratee(value, index, list) }; }).sort(function(left, right) { var a = left.criteria; @@ -365,12 +412,12 @@ module.exports = { // An internal function used for aggregate "group by" operations. var group = function(behavior) { - return function(obj, iterator, context) { + return function(obj, iteratee, context) { var result = {}; - iterator = lookupIterator(iterator); - each(obj, function(value, index) { - var key = iterator.call(context, value, index, obj); - behavior(result, key, value); + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); }); return result; }; @@ -378,48 +425,46 @@ module.exports = { // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. - _.groupBy = group(function(result, key, value) { - _.has(result, key) ? result[key].push(value) : result[key] = [value]; + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. - _.indexBy = group(function(result, key, value) { + _.indexBy = group(function(result, value, key) { result[key] = value; }); // Counts instances of an object that group by a certain criterion. Pass // either a string attribute to count by, or a function that returns the // criterion. - _.countBy = group(function(result, key) { - _.has(result, key) ? result[key]++ : result[key] = 1; + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; }); - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator, context) { - iterator = lookupIterator(iterator); - var value = iterator.call(context, obj); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >>> 1; - iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; - } - return low; - }; - // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); - if (obj.length === +obj.length) return _.map(obj, _.identity); + if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; - return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(obj, predicate, context) { + predicate = cb(predicate, context); + var pass = [], fail = []; + _.each(obj, function(value, key, obj) { + (predicate(value, key, obj) ? pass : fail).push(value); + }); + return [pass, fail]; }; // Array Functions @@ -430,33 +475,30 @@ module.exports = { // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; - if ((n == null) || guard) return array[0]; - if (n < 0) return []; - return slice.call(array, 0, n); + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. + // the array, excluding the last N. _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); }; // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. + // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; - if ((n == null) || guard) return array[array.length - 1]; - return slice.call(array, Math.max(array.length - n, 0)); + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return - // the rest N values in the array. The **guard** - // check allows it to work with `_.map`. + // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, (n == null) || guard ? 1 : n); + return slice.call(array, n == null || guard ? 1 : n); }; // Trim out all falsy values from an array. @@ -465,23 +507,28 @@ module.exports = { }; // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, output) { - if (shallow && _.every(input, _.isArray)) { - return concat.apply(output, input); - } - each(input, function(value) { - if (_.isArray(value) || _.isArguments(value)) { - shallow ? push.apply(output, value) : flatten(value, shallow, output); - } else { - output.push(value); + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0; + for (var i = startIndex || 0, length = input && input.length; i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; } - }); + } return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { - return flatten(array, shallow, []); + return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). @@ -489,79 +536,93 @@ module.exports = { return _.difference(array, slice.call(arguments, 1)); }; - // Split an array into two arrays: one whose elements all satisfy the given - // predicate, and one whose elements all do not satisfy the predicate. - _.partition = function(array, predicate) { - var pass = [], fail = []; - each(array, function(elem) { - (predicate(elem) ? pass : fail).push(elem); - }); - return [pass, fail]; - }; - // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator, context) { - if (_.isFunction(isSorted)) { - context = iterator; - iterator = isSorted; + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (array == null) return []; + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; isSorted = false; } - var initial = iterator ? _.map(array, iterator, context) : array; - var results = []; + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; var seen = []; - each(initial, function(value, index) { - if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { - seen.push(value); - results.push(array[index]); + for (var i = 0, length = array.length; i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); } - }); - return results; + } + return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(_.flatten(arguments, true)); + return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.contains(other, item); - }); - }); + if (array == null) return []; + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = array.length; i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + for (var j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { - var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); - return _.filter(array, function(value){ return !_.contains(rest, value); }); + var rest = flatten(arguments, true, true, 1); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { - var length = _.max(_.pluck(arguments, 'length').concat(0)); - var results = new Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(arguments, '' + i); + return _.unzip(arguments); + }; + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + var length = array && _.max(array, 'length').length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); } - return results; + return result; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { - if (list == null) return {}; var result = {}; - for (var i = 0, length = list.length; i < length; i++) { + for (var i = 0, length = list && list.length; i < length; i++) { if (values) { result[list[i]] = values[i]; } else { @@ -571,40 +632,68 @@ module.exports = { return result; }; - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i = 0, length = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } + var i = 0, length = array && array.length; + if (typeof isSorted == 'number') { + i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; + } else if (isSorted && length) { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + if (item !== item) { + return _.findIndex(slice.call(array, i), _.isNaN); } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); for (; i < length; i++) if (array[i] === item) return i; return -1; }; - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item, from) { - if (array == null) return -1; - var hasIndex = from != null; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { - return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + var idx = array ? array.length : 0; + if (typeof from == 'number') { + idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); } - var i = (hasIndex ? from : array.length); - while (i--) if (array[i] === item) return i; + if (item !== item) { + return _.findLastIndex(slice.call(array, 0, idx), _.isNaN); + } + while (--idx >= 0) if (array[idx] === item) return idx; return -1; }; + // Generator function to create the findIndex and findLastIndex functions + function createIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = array != null && array.length; + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a predicate test + _.findIndex = createIndexFinder(1); + + _.findLastIndex = createIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = array.length; + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). @@ -613,15 +702,13 @@ module.exports = { stop = start || 0; start = 0; } - step = arguments[2] || 1; + step = step || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(length); + var range = Array(length); - while(idx < length) { - range[idx++] = start; - start += step; + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; } return range; @@ -630,26 +717,27 @@ module.exports = { // Function (ahem) Functions // ------------------ - // Reusable constructor function for prototype setting. - var ctor = function(){}; + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { - var args, bound; if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - ctor.prototype = null; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var args = slice.call(arguments, 2); + var bound = function() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; + return bound; }; // Partially apply a function by creating a version that has had some of its @@ -657,49 +745,55 @@ module.exports = { // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); - return function() { - var position = 0; - var args = boundArgs.slice(); - for (var i = 0, length = args.length; i < length; i++) { - if (args[i] === _) args[i] = arguments[position++]; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); - return func.apply(this, args); + return executeBound(func, bound, this, this, args); }; + return bound; }; // Bind a number of an object's methods to that object. Remaining arguments // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length === 0) throw new Error('bindAll must be passed function names'); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + var i, length = arguments.length, key; + if (length <= 1) throw new Error('bindAll must be passed function names'); + for (i = 1; i < length; i++) { + key = arguments[i]; + obj[key] = _.bind(obj[key], obj); + } return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; }; + memoize.cache = {}; + return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); + return setTimeout(function(){ + return func.apply(null, args); + }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; + _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run @@ -710,12 +804,12 @@ module.exports = { var context, args, result; var timeout = null; var previous = 0; - options || (options = {}); + if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; }; return function() { var now = _.now(); @@ -723,12 +817,14 @@ module.exports = { var remaining = wait - (now - previous); context = this; args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } previous = now; result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } @@ -745,13 +841,14 @@ module.exports = { var later = function() { var last = _.now() - timestamp; - if (last < wait) { + + if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; } } }; @@ -761,9 +858,7 @@ module.exports = { args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; - if (!timeout) { - timeout = setTimeout(later, wait); - } + if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; @@ -773,19 +868,6 @@ module.exports = { }; }; - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - memo = func.apply(this, arguments); - func = null; - return memo; - }; - }; - // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. @@ -793,20 +875,27 @@ module.exports = { return _.partial(wrapper, func); }; - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = arguments; + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; + return !predicate.apply(this, arguments); }; }; - // Returns a function that will only be executed after being called N times. + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + }; + + // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { @@ -815,16 +904,66 @@ module.exports = { }; }; + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + // Object Functions // ---------------- - // Retrieve the names of an object's properties. + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; @@ -832,18 +971,33 @@ module.exports = { _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; - var values = new Array(length); + var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; }; + // Returns the results of applying the iteratee to each element of the object + // In contrast to _.map it returns an object + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}, + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; - var pairs = new Array(length); + var pairs = Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } @@ -871,48 +1025,57 @@ module.exports = { }; // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - } - }); - return obj; + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } }; // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - each(keys, function(key) { - if (key in obj) copy[key] = obj[key]; - }); - return copy; + _.pick = function(object, oiteratee, context) { + var result = {}, obj = object, iteratee, keys; + if (obj == null) return result; + if (_.isFunction(oiteratee)) { + keys = _.allKeys(obj); + iteratee = optimizeCb(oiteratee, context); + } else { + keys = flatten(arguments, false, false, 1); + iteratee = function(value, key, obj) { return key in obj; }; + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; }; // Return a copy of the object without the blacklisted properties. - _.omit = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - for (var key in obj) { - if (!_.contains(keys, key)) copy[key] = obj[key]; + _.omit = function(obj, iteratee, context) { + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + } else { + var keys = _.map(flatten(arguments, false, false, 1), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; } - return copy; + return _.pick(obj, iteratee, context); }; // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - if (obj[prop] === void 0) obj[prop] = source[prop]; - } - } - }); - return obj; - }; + _.defaults = createAssigner(_.allKeys, true); // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { @@ -928,11 +1091,24 @@ module.exports = { return obj; }; + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) return a !== 0 || 1 / a == 1 / b; + if (a === b) return a !== 0 || 1 / a === 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. @@ -940,98 +1116,98 @@ module.exports = { if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); - if (className != toString.call(b)) return false; + if (className !== toString.call(b)) return false; switch (className) { - // Strings, numbers, dates, and booleans are compared by value. + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. - return a == String(b); + return '' + a === '' + b; case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + return +a === +b; + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } } - if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. - if (aStack[length] == a) return bStack[length] == b; - } - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor)) - && ('constructor' in a && 'constructor' in b)) { - return false; + if (aStack[length] === a) return bStack[length] === b; } + // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); - var size = 0, result = true; + // Recursively compare objects and arrays. - if (className == '[object Array]') { + if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); - return result; + return true; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { - return eq(a, b, [], []); + return eq(a, b); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; }; // Is a given value a DOM element? @@ -1042,33 +1218,35 @@ module.exports = { // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; + return toString.call(obj) === '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { - return obj === Object(obj); + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; }; - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { - return toString.call(obj) == '[object ' + name + ']'; + return toString.call(obj) === '[object ' + name + ']'; }; }); - // Define a fallback version of the method in browsers (ahem, IE), where + // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); + return _.has(obj, 'callee'); }; } - // Optimize `isFunction` if appropriate. - if (typeof (/./) !== 'function') { + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), and in Safari 8 (#1929). + if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { - return typeof obj === 'function'; + return typeof obj == 'function' || false; }; } @@ -1079,12 +1257,12 @@ module.exports = { // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { - return _.isNumber(obj) && obj != +obj; + return _.isNumber(obj) && obj !== +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; // Is a given value equal to null? @@ -1100,7 +1278,7 @@ module.exports = { // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); + return obj != null && hasOwnProperty.call(obj, key); }; // Utility Functions @@ -1113,39 +1291,47 @@ module.exports = { return this; }; - // Keep the identity function around for default iterators. + // Keep the identity function around for default iteratees. _.identity = function(value) { return value; }; + // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { - return function () { + return function() { return value; }; }; + _.noop = function(){}; + _.property = function(key) { return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { return obj[key]; }; }; - // Returns a predicate for checking whether an object has a given set of `key:value` pairs. - _.matches = function(attrs) { + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); return function(obj) { - if (obj === attrs) return true; //avoid comparing an object to itself. - for (var key in attrs) { - if (attrs[key] !== obj[key]) - return false; - } - return true; - } + return _.isMatch(obj, attrs); + }; }; // Run a function **n** times. - _.times = function(n, iterator, context) { + _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); - for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; @@ -1159,56 +1345,48 @@ module.exports = { }; // A (possibly faster) way to get the current timestamp as an integer. - _.now = Date.now || function() { return new Date().getTime(); }; - - // List of HTML entities for escaping. - var entityMap = { - escape: { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - } + _.now = Date.now || function() { + return new Date().getTime(); }; - entityMap.unescape = _.invert(entityMap.escape); - // Regexes containing the keys and values listed immediately above. - var entityRegexes = { - escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), - unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' }; + var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. - _.each(['escape', 'unescape'], function(method) { - _[method] = function(string) { - if (string == null) return ''; - return ('' + string).replace(entityRegexes[method], function(match) { - return entityMap[method][match]; - }); + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; }; - }); + // Regexes for identifying a key that needs to be escaped + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. - _.result = function(object, property) { - if (object == null) return void 0; - var value = object[property]; + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } return _.isFunction(value) ? value.call(object) : value; }; - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - each(_.functions(obj), function(name) { - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result.call(this, func.apply(_, args)); - }; - }); - }; - // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; @@ -1237,22 +1415,26 @@ module.exports = { '\\': '\\', '\r': 'r', '\n': 'n', - '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + var escaper = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - var render; + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ + var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source @@ -1262,19 +1444,18 @@ module.exports = { var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); + source += text.slice(index, offset).replace(escaper, escapeChar); + index = offset + match.length; if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } - if (interpolate) { + } else if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } - if (evaluate) { + } else if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } - index = offset + match.length; + + // Adobe VMs need the match returned to produce the correct offest. return match; }); source += "';\n"; @@ -1284,29 +1465,31 @@ module.exports = { source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; + source + 'return __p;\n'; try { - render = new Function(settings.variable || 'obj', '_', source); + var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } - if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; return template; }; - // Add a "chain" function, which will delegate to the wrapper. + // Add a "chain" function. Start chaining a wrapped Underscore object. _.chain = function(obj) { - return _(obj).chain(); + var instance = _(obj); + instance._chain = true; + return instance; }; // OOP @@ -1316,46 +1499,56 @@ module.exports = { // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result(this, func.apply(_, args)); + }; + }); }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); - if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { + _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); + return result(this, method.apply(this._wrapped, arguments)); }; }); - _.extend(_.prototype, { + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; - // Start chaining a wrapped Underscore object. - chain: function() { - this._chain = true; - return this; - }, - - // Extracts the result from a wrapped and chained object. - value: function() { - return this._wrapped; - } - - }); + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general @@ -1369,7 +1562,7 @@ module.exports = { return _; }); } -}).call(this); +}.call(this)); }, {}], @@ -1422,7 +1615,7 @@ var options = _dereq_("./options.js"); // variable. That function will be invoked immediately, and its return value is // the JSHINT function itself. -var JSHINT = (function () { +var JSHINT = (function() { "use strict"; var api, // Extension API @@ -1533,14 +1726,14 @@ var JSHINT = (function () { } function supplant(str, data) { - return str.replace(/\{([^{}]*)\}/g, function (a, b) { + return str.replace(/\{([^{}]*)\}/g, function(a, b) { var r = data[b]; return typeof r === "string" || typeof r === "number" ? r : a; }); } function combine(dest, src) { - Object.keys(src).forEach(function (name) { + Object.keys(src).forEach(function(name) { if (_.has(JSHINT.blacklist, name)) return; dest[name] = src[name]; }); @@ -1551,7 +1744,7 @@ var JSHINT = (function () { for (var enforceopt in options.bool.enforcing) { if (state.option[enforceopt] === undefined) { state.option[enforceopt] = true; - } + } } for (var relaxopt in options.bool.relaxing) { if (state.option[relaxopt] === undefined) { @@ -1564,8 +1757,12 @@ var JSHINT = (function () { function assume() { processenforceall(); + if (!state.option.es3) { + combine(predefined, vars.ecmaIdentifiers[5]); + } + if (state.option.esnext) { - combine(predefined, vars.newEcmaIdentifiers); + combine(predefined, vars.ecmaIdentifiers[6]); } if (state.option.couch) { @@ -1658,19 +1855,19 @@ var JSHINT = (function () { // Let's assume that chronologically ES3 < ES5 < ES6/ESNext < Moz - state.option.inMoz = function (strict) { + state.option.inMoz = function(strict) { return state.option.moz; }; - state.option.inESNext = function (strict) { + state.option.inESNext = function(strict) { return state.option.moz || state.option.esnext; }; - state.option.inES5 = function (/* strict */) { + state.option.inES5 = function(/* strict */) { return !state.option.es3; }; - state.option.inES3 = function (strict) { + state.option.inES3 = function(strict) { if (strict) { return !state.option.moz && !state.option.esnext && state.option.es3; } @@ -1694,14 +1891,16 @@ var JSHINT = (function () { } function isundef(scope, code, token, a) { - return JSHINT.undefs.push([scope, code, token, a]); + if (!state.ignored[code] && state.option.undef !== false) { + JSHINT.undefs.push([scope, code, token, a]); + } } function removeIgnoredMessages() { var ignored = state.ignoredLines; if (_.isEmpty(ignored)) return; - JSHINT.errors = _.reject(JSHINT.errors, function (err) { return ignored[err.line] }); + JSHINT.errors = _.reject(JSHINT.errors, function(err) { return ignored[err.line] }); } function warning(code, t, a, b, c, d) { @@ -1861,7 +2060,7 @@ var JSHINT = (function () { var predef = {}; if (nt.type === "globals") { - body.forEach(function (g) { + body.forEach(function(g) { g = g.split(":"); var key = (g[0] || "").trim(); var val = (g[1] || "").trim(); @@ -1887,7 +2086,7 @@ var JSHINT = (function () { } if (nt.type === "exported") { - body.forEach(function (e) { + body.forEach(function(e) { exported[e] = true; }); } @@ -1895,7 +2094,7 @@ var JSHINT = (function () { if (nt.type === "members") { membersOnly = membersOnly || {}; - body.forEach(function (m) { + body.forEach(function(m) { var ch1 = m.charAt(0); var ch2 = m.charAt(m.length - 1); @@ -1920,7 +2119,7 @@ var JSHINT = (function () { ]; if (nt.type === "jshint" || nt.type === "jslint") { - body.forEach(function (g) { + body.forEach(function(g) { g = g.split(":"); var key = (g[0] || "").trim(); var val = (g[1] || "").trim(); @@ -2138,14 +2337,7 @@ var JSHINT = (function () { error("E020", state.tokens.next, id, t.id, t.line, state.tokens.next.value); } } else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) { - // parameter destructuring with rest operator - if (state.tokens.next.value === "...") { - if (!state.option.esnext) { - warning("W119", state.tokens.next, "spread/rest operator"); - } - } else { - warning("W116", state.tokens.next, id, state.tokens.next.value); - } + warning("W116", state.tokens.next, id, state.tokens.next.value); } } @@ -2177,7 +2369,7 @@ var JSHINT = (function () { } function isInfix(token) { - return token.infix || (!token.identifier && !!token.led); + return token.infix || (!token.identifier && !token.template && !!token.led); } function isEndOfExpr() { @@ -2187,11 +2379,15 @@ var JSHINT = (function () { return true; } if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.option.inMoz(true))) { - return curr.line !== next.line; + return curr.line !== startLine(next); } return false; } + function isBeginOfExpr(prev) { + return !prev.left && prev.arity !== "unary"; + } + // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is // like .nud except that it is only used on the first token of a statement. @@ -2207,8 +2403,7 @@ var JSHINT = (function () { // They are elements of the parsing method called Top Down Operator Precedence. function expression(rbp, initial) { - var left, isArray = false, isObject = false, isLetExpr = false, - isFatArrowBody = state.tokens.curr.value === "=>"; + var left, isArray = false, isObject = false, isLetExpr = false; state.nameStack.push(); @@ -2222,20 +2417,16 @@ var JSHINT = (function () { funct["(blockscope)"].stack(); advance("let"); advance("("); - state.syntax["let"].fud.call(state.syntax["let"].fud, false); + state.tokens.prev.fud(); advance(")"); } if (state.tokens.next.id === "(end)") error("E006", state.tokens.curr); - if (state.tokens.next.type === "(template)") { - doTemplateLiteral(); - } - var isDangerous = state.option.asi && - state.tokens.prev.line < state.tokens.curr.line && + state.tokens.prev.line !== startLine(state.tokens.curr) && _.contains(["]", ")"], state.tokens.prev.id) && _.contains(["[", "("], state.tokens.curr.id); @@ -2246,6 +2437,7 @@ var JSHINT = (function () { if (initial) { funct["(verb)"] = state.tokens.curr.value; + state.tokens.curr.beginsStmt = true; } if (initial === true && state.tokens.curr.fud) { @@ -2257,7 +2449,9 @@ var JSHINT = (function () { error("E030", state.tokens.curr, state.tokens.curr.id); } - while (rbp < state.tokens.next.lbp && !isEndOfExpr()) { + // TODO: use pratt mechanics rather than special casing template tokens + while ((rbp < state.tokens.next.lbp || state.tokens.next.type === "(template)") && + !isEndOfExpr()) { isArray = state.tokens.curr.value === "Array"; isObject = state.tokens.curr.value === "Object"; @@ -2300,11 +2494,6 @@ var JSHINT = (function () { funct["(blockscope)"].unstack(); } - if (state.option.singleGroups && left && left.paren && !left.exprs && - !isFatArrowBody && !left.triggerFnExpr) { - warning("W126"); - } - state.nameStack.pop(); return left; @@ -2313,23 +2502,27 @@ var JSHINT = (function () { // Functions for conformance of style. + function startLine(token) { + return token.startLine || token.line; + } + function nobreaknonadjacent(left, right) { left = left || state.tokens.curr; right = right || state.tokens.next; - if (!state.option.laxbreak && left.line !== right.line) { + if (!state.option.laxbreak && left.line !== startLine(right)) { warning("W014", right, right.value); } } function nolinebreak(t) { t = t || state.tokens.curr; - if (t.line !== state.tokens.next.line) { + if (t.line !== startLine(state.tokens.next)) { warning("E022", t, t.value); } } function nobreakcomma(left, right) { - if (left.line !== right.line) { + if (left.line !== startLine(right)) { if (!state.option.laxcomma) { if (comma.first) { warning("I001"); @@ -2343,10 +2536,6 @@ var JSHINT = (function () { function comma(opts) { opts = opts || {}; - if (state.option.nocomma) { - warning("W127"); - } - if (!opts.peek) { nobreakcomma(state.tokens.curr, state.tokens.next); advance(","); @@ -2416,7 +2605,9 @@ var JSHINT = (function () { } function delim(s) { - return symbol(s, 0); + var x = symbol(s, 0); + x.delim = true; + return x; } function stmt(s, f) { @@ -2444,9 +2635,9 @@ var JSHINT = (function () { var x = symbol(s, 150); reserveName(x); - x.nud = (typeof f === "function") ? f : function () { - this.right = expression(150); + x.nud = (typeof f === "function") ? f : function() { this.arity = "unary"; + this.right = expression(150); if (this.id === "++" || this.id === "--") { if (state.option.plusplus) { @@ -2478,7 +2669,7 @@ var JSHINT = (function () { } function FutureReservedWord(name, meta) { - var x = type(name, (meta && meta.nud) || function () { + var x = type(name, (meta && meta.nud) || function() { return this; }); @@ -2494,7 +2685,7 @@ var JSHINT = (function () { } function reservevar(s, v) { - return reserve(s, function () { + return reserve(s, function() { if (typeof v === "function") { v(this); } @@ -2506,7 +2697,7 @@ var JSHINT = (function () { var x = symbol(s, p); reserveName(x); x.infix = true; - x.led = function (left) { + x.led = function(left) { if (!w) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); } @@ -2528,11 +2719,11 @@ var JSHINT = (function () { function application(s) { var x = symbol(s, 42); - x.led = function (left) { + x.led = function(left) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); this.left = left; - this.right = doFunction(undefined, undefined, false, { loneArg: left }); + this.right = doFunction({ type: "arrow", loneArg: left }); return this; }; return x; @@ -2541,7 +2732,7 @@ var JSHINT = (function () { function relation(s, f) { var x = symbol(s, 100); - x.led = function (left) { + x.led = function(left) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); var right = expression(100); @@ -2593,7 +2784,7 @@ var JSHINT = (function () { var values = [ "undefined", "object", "boolean", "number", - "string", "function", "xml", "object", "unknown" + "string", "function", "xml", "object", "unknown", "symbol" ]; if (right.type === "(identifier)" && right.value === "typeof" && left.type === "(string)") @@ -2602,6 +2793,27 @@ var JSHINT = (function () { return false; } + function isGlobalEval(left, state, funct) { + var isGlobal = false; + + // permit methods to refer to an "eval" key in their own context + if (left.type === "this" && funct["(context)"] === null) { + isGlobal = true; + } + // permit use of "eval" members of objects + else if (left.type === "(identifier)") { + if (state.option.node && left.value === "global") { + isGlobal = true; + } + + else if (state.option.browser && (left.value === "window" || left.value === "document")) { + isGlobal = true; + } + } + + return isGlobal; + } + function findNativePrototype(left) { var natives = [ "Array", "ArrayBuffer", "Boolean", "Collator", "DataView", "Date", @@ -2631,7 +2843,7 @@ var JSHINT = (function () { } function assignop(s, f, p) { - var x = infix(s, typeof f === "function" ? f : function (left, that) { + var x = infix(s, typeof f === "function" ? f : function(left, that) { that.left = left; if (left) { @@ -2664,7 +2876,7 @@ var JSHINT = (function () { return that; } else if (left.id === "[") { if (state.tokens.curr.left.first) { - state.tokens.curr.left.first.forEach(function (t) { + state.tokens.curr.left.first.forEach(function(t) { if (t && funct[t.value] === "const") { error("E013", t, t.value); } @@ -2705,7 +2917,7 @@ var JSHINT = (function () { function bitwise(s, f, p) { var x = symbol(s, p); reserveName(x); - x.led = (typeof f === "function") ? f : function (left) { + x.led = (typeof f === "function") ? f : function(left) { if (state.option.bitwise) { warning("W016", this, this.id); } @@ -2718,7 +2930,7 @@ var JSHINT = (function () { function bitwiseassignop(s) { - return assignop(s, function (left, that) { + return assignop(s, function(left, that) { if (state.option.bitwise) { warning("W016", that, that.id); } @@ -2742,7 +2954,7 @@ var JSHINT = (function () { function suffix(s) { var x = symbol(s, 150); - x.led = function (left) { + x.led = function(left) { if (state.option.plusplus) { warning("W016", this, this.id); } else if ((!left.identifier || isReserved(left)) && left.id !== "." && left.id !== "[") { @@ -2758,9 +2970,8 @@ var JSHINT = (function () { // fnparam means that this identifier is being defined as a function // argument (see identifier()) // prop means that this identifier is that of an object property - // exported means that the identifier is part of a valid ES6 `export` declaration - function optionalidentifier(fnparam, prop, preserve, exported) { + function optionalidentifier(fnparam, prop, preserve) { if (!state.tokens.next.identifier) { return; } @@ -2772,10 +2983,6 @@ var JSHINT = (function () { var curr = state.tokens.curr; var val = state.tokens.curr.value; - if (exported) { - state.tokens.curr.exported = true; - } - if (!isReserved(curr)) { return val; } @@ -2797,9 +3004,8 @@ var JSHINT = (function () { // fnparam means that this identifier is being defined as a function // argument // prop means that this identifier is that of an object property - // `exported` means that the identifier token should be exported. - function identifier(fnparam, prop, exported) { - var i = optionalidentifier(fnparam, prop, false, exported); + function identifier(fnparam, prop) { + var i = optionalidentifier(fnparam, prop, false); if (i) { return i; } @@ -2809,6 +3015,21 @@ var JSHINT = (function () { if (!state.option.esnext) { warning("W119", state.tokens.next, "spread/rest operator"); } + advance(); + + if (checkPunctuators(state.tokens.next, ["..."])) { + warning("E024", state.tokens.next, "..."); + while (checkPunctuators(state.tokens.next, ["..."])) { + advance(); + } + } + + if (!state.tokens.next.identifier) { + warning("E024", state.tokens.curr, "..."); + return; + } + + return identifier(fnparam, prop); } else { error("E030", state.tokens.next, state.tokens.next.value); @@ -2853,12 +3074,14 @@ var JSHINT = (function () { function parseFinalSemicolon() { if (state.tokens.next.id !== ";") { + // don't complain about unclosed templates / strings + if (state.tokens.next.isUnclosed) return advance(); if (!state.option.asi) { // If this is the last statement in a block that ends on // the same line *and* option lastsemic is on, ignore the warning. // Otherwise, complain about missing semicolon. if (!state.option.lastsemic || state.tokens.next.id !== "}" || - state.tokens.next.line !== state.tokens.curr.line) { + startLine(state.tokens.next) !== state.tokens.curr.line) { warningAt("W033", state.tokens.curr.line, state.tokens.curr.character); } } @@ -2868,7 +3091,6 @@ var JSHINT = (function () { } function statement() { - var values; var i = indent, r, s = scope, t = state.tokens.next; if (t.id === ";") { @@ -2905,22 +3127,6 @@ var JSHINT = (function () { } } - // detect a destructuring assignment - if (_.has(["[", "{"], t.value)) { - if (lookupBlockType().isDestAssign) { - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "destructuring expression"); - } - values = destructuringExpression(); - values.forEach(function (tok) { - isundef(funct, "W117", tok.token, tok.id); - }); - advance("="); - destructuringExpressionMatch(values, expression(10, true)); - advance(";"); - return; - } - } if (t.identifier && !res && peek().id === ":") { advance(); advance(":"); @@ -3222,11 +3428,11 @@ var JSHINT = (function () { // Build the syntax table by declaring the syntactic elements of the language. - type("(number)", function () { + type("(number)", function() { return this; }); - type("(string)", function () { + type("(string)", function() { return this; }); @@ -3235,7 +3441,7 @@ var JSHINT = (function () { lbp: 0, identifier: true, - nud: function () { + nud: function() { var v = this.value; var s = scope[v]; var f; @@ -3359,27 +3565,44 @@ var JSHINT = (function () { return this; }, - led: function () { + led: function() { error("E033", state.tokens.next, state.tokens.next.value); } }; - state.syntax["(template)"] = { - type: "(template)", + var baseTemplateSyntax = { lbp: 0, identifier: false, - fud: doTemplateLiteral + template: true, }; + state.syntax["(template)"] = _.extend({ + type: "(template)", + nud: doTemplateLiteral, + led: doTemplateLiteral, + noSubst: false + }, baseTemplateSyntax); - type("(template middle)", function () { - return this; - }); + state.syntax["(template middle)"] = _.extend({ + type: "(template middle)", + middle: true, + noSubst: false + }, baseTemplateSyntax); - type("(template tail)", function () { - return this; - }); + state.syntax["(template tail)"] = _.extend({ + type: "(template tail)", + tail: true, + noSubst: false + }, baseTemplateSyntax); - type("(regexp)", function () { + state.syntax["(no subst template)"] = _.extend({ + type: "(template)", + nud: doTemplateLiteral, + led: doTemplateLiteral, + noSubst: true, + tail: true // mark as tail, since it's always the last component + }, baseTemplateSyntax); + + type("(regexp)", function() { return this; }); @@ -3403,7 +3626,7 @@ var JSHINT = (function () { reserve("catch"); reserve("default").reach = true; reserve("finally"); - reservevar("arguments", function (x) { + reservevar("arguments", function(x) { if (state.directive["use strict"] && funct["(global)"]) { warning("E008", x); } @@ -3412,7 +3635,7 @@ var JSHINT = (function () { reservevar("false"); reservevar("Infinity"); reservevar("null"); - reservevar("this", function (x) { + reservevar("this", function(x) { if (state.directive["use strict"] && !isMethod() && !state.option.validthis && ((funct["(statement)"] && funct["(name)"].charAt(0) > "Z") || funct["(global)"])) { @@ -3426,7 +3649,7 @@ var JSHINT = (function () { assignop("+=", "assignadd", 20); assignop("-=", "assignsub", 20); assignop("*=", "assignmult", 20); - assignop("/=", "assigndiv", 20).nud = function () { + assignop("/=", "assigndiv", 20).nud = function() { error("E014"); }; assignop("%=", "assignmod", 20); @@ -3437,14 +3660,19 @@ var JSHINT = (function () { bitwiseassignop("<<="); bitwiseassignop(">>="); bitwiseassignop(">>>="); - infix(",", function (left, that) { + infix(",", function(left, that) { var expr; that.exprs = [left]; - if (!comma({peek: true})) { + + if (state.option.nocomma) { + warning("W127"); + } + + if (!comma({ peek: true })) { return that; } while (true) { - if (!(expr = expression(10))) { + if (!(expr = expression(10))) { break; } that.exprs.push(expr); @@ -3455,7 +3683,7 @@ var JSHINT = (function () { return that; }, 10, true); - infix("?", function (left, that) { + infix("?", function(left, that) { increaseComplexityCount(); that.left = left; that.right = expression(10); @@ -3465,7 +3693,7 @@ var JSHINT = (function () { }, 30); var orPrecendence = 40; - infix("||", function (left, that) { + infix("||", function(left, that) { increaseComplexityCount(); that.left = left; that.right = expression(orPrecendence); @@ -3475,7 +3703,7 @@ var JSHINT = (function () { bitwise("|", "bitor", 70); bitwise("^", "bitxor", 80); bitwise("&", "bitand", 90); - relation("==", function (left, right) { + relation("==", function(left, right) { var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null"); switch (true) { @@ -3499,7 +3727,7 @@ var JSHINT = (function () { return this; }); - relation("===", function (left, right) { + relation("===", function(left, right) { if (isTypoTypeof(right, left)) { warning("W122", this, right.value); } else if (isTypoTypeof(left, right)) { @@ -3507,7 +3735,7 @@ var JSHINT = (function () { } return this; }); - relation("!=", function (left, right) { + relation("!=", function(left, right) { var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null"); @@ -3525,7 +3753,7 @@ var JSHINT = (function () { } return this; }); - relation("!==", function (left, right) { + relation("!==", function(left, right) { if (isTypoTypeof(right, left)) { warning("W122", this, right.value); } else if (isTypoTypeof(left, right)) { @@ -3542,8 +3770,11 @@ var JSHINT = (function () { bitwise(">>>", "shiftrightunsigned", 120); infix("in", "in", 120); infix("instanceof", "instanceof", 120); - infix("+", function (left, that) { - var right = expression(130); + infix("+", function(left, that) { + var right; + that.left = left; + that.right = right = expression(130); + if (left && right && left.id === "(string)" && right.id === "(string)") { left.value += right.value; left.character = right.character; @@ -3552,18 +3783,17 @@ var JSHINT = (function () { } return left; } - that.left = left; - that.right = right; + return that; }, 130); prefix("+", "num"); - prefix("+++", function () { + prefix("+++", function() { warning("W007"); - this.right = expression(150); this.arity = "unary"; + this.right = expression(150); return this; }); - infix("+++", function (left) { + infix("+++", function(left) { warning("W007"); this.left = left; this.right = expression(130); @@ -3571,13 +3801,13 @@ var JSHINT = (function () { }, 130); infix("-", "sub", 130); prefix("-", "neg"); - prefix("---", function () { + prefix("---", function() { warning("W006"); - this.right = expression(150); this.arity = "unary"; + this.right = expression(150); return this; }); - infix("---", function (left) { + infix("---", function(left) { warning("W006"); this.left = left; this.right = expression(130); @@ -3594,9 +3824,13 @@ var JSHINT = (function () { suffix("--"); prefix("--", "predec"); state.syntax["--"].exps = true; - prefix("delete", function () { + prefix("delete", function() { var p = expression(10); - if (!p || (p.id !== "." && p.id !== "[")) { + if (!p) { + return this; + } + + if (p.id !== "." && p.id !== "[") { warning("W051"); } this.first = p; @@ -3609,28 +3843,64 @@ var JSHINT = (function () { return this; }).exps = true; - prefix("~", function () { + prefix("~", function() { if (state.option.bitwise) { - warning("W052", this, "~"); + warning("W016", this, "~"); } + this.arity = "unary"; expression(150); return this; }); - prefix("...", function () { + prefix("...", function() { if (!state.option.esnext) { warning("W119", this, "spread/rest operator"); } - if (!state.tokens.next.identifier) { + + // TODO: Allow all AssignmentExpression + // once parsing permits. + // + // How to handle eg. number, boolean when the built-in + // prototype of may have an @@iterator definition? + // + // Number.prototype[Symbol.iterator] = function * () { + // yield this.valueOf(); + // }; + // + // var a = [ ...1 ]; + // console.log(a); // [1]; + // + // for (let n of [...10]) { + // console.log(n); + // } + // // 10 + // + // + // Boolean.prototype[Symbol.iterator] = function * () { + // yield this.valueOf(); + // }; + // + // var a = [ ...true ]; + // console.log(a); // [true]; + // + // for (let n of [...false]) { + // console.log(n); + // } + // // false + // + if (!state.tokens.next.identifier && + state.tokens.next.type !== "(string)" && + !checkPunctuators(state.tokens.next, ["[", "("])) { + error("E030", state.tokens.next, state.tokens.next.value); } expression(150); return this; }); - prefix("!", function () { - this.right = expression(150); + prefix("!", function() { this.arity = "unary"; + this.right = expression(150); if (!this.right) { // '!' followed by nothing? Give up. quit("E041", this.line || 0); @@ -3642,7 +3912,7 @@ var JSHINT = (function () { return this; }); - prefix("typeof", (function () { + prefix("typeof", (function() { var p = expression(150); this.first = p; @@ -3653,7 +3923,7 @@ var JSHINT = (function () { } return this; })); - prefix("new", function () { + prefix("new", function() { var c = expression(155), i; if (c && c.id !== "function") { if (c.identifier) { @@ -3707,7 +3977,7 @@ var JSHINT = (function () { prefix("void").exps = true; - infix(".", function (left, that) { + infix(".", function(left, that) { var m = identifier(false, true); if (typeof m === "string") { @@ -3732,13 +4002,15 @@ var JSHINT = (function () { } if (!state.option.evil && (m === "eval" || m === "execScript")) { - warning("W061"); + if (isGlobalEval(left, state, funct)) { + warning("W061"); + } } return that; }, 160, true); - infix("(", function (left, that) { + infix("(", function(left, that) { if (state.option.immed && left && !left.immed && left.id === "function") { warning("W062"); } @@ -3812,11 +4084,13 @@ var JSHINT = (function () { return that; }, 155, true).exps = true; - prefix("(", function () { - var bracket, brackets = []; + prefix("(", function() { var pn = state.tokens.next, pn1, i = -1; - var ret, triggerFnExpr; + var ret, triggerFnExpr, first, last; var parens = 1; + var opening = state.tokens.curr; + var preceeding = state.tokens.prev; + var isNecessary = !state.option.singleGroups; do { if (pn.value === "(") { @@ -3828,8 +4102,7 @@ var JSHINT = (function () { i += 1; pn1 = pn; pn = peek(i); - } while (!(parens === 0 && pn1.value === ")") && - pn.value !== ";" && pn.type !== "(end)"); + } while (!(parens === 0 && pn1.value === ")") && pn.value !== ";" && pn.type !== "(end)"); if (state.tokens.next.id === "function") { triggerFnExpr = state.tokens.next.immed = true; @@ -3839,26 +4112,23 @@ var JSHINT = (function () { // current token marks the beginning of a "fat arrow" function and parsing // should proceed accordingly. if (pn.value === "=>") { - return doFunction(null, null, null, { parsedParen: true }); + return doFunction({ type: "arrow", parsedOpening: true }); } var exprs = []; if (state.tokens.next.id !== ")") { for (;;) { - if (pn.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) { - bracket = state.tokens.next; - bracket.left = destructuringExpression(); - brackets.push(bracket); - for (var t in bracket.left) { - exprs.push(bracket.left[t].token); - } - } else { - exprs.push(expression(10)); - } + exprs.push(expression(10)); + if (state.tokens.next.id !== ",") { break; } + + if (state.option.nocomma) { + warning("W127"); + } + comma(); } } @@ -3871,32 +4141,64 @@ var JSHINT = (function () { } } - if (state.tokens.next.value === "=>") { - return exprs; - } if (!exprs.length) { return; } if (exprs.length > 1) { ret = Object.create(state.syntax[","]); ret.exprs = exprs; + + first = exprs[0]; + last = exprs[exprs.length - 1]; + + if (!isNecessary) { + isNecessary = preceeding.assign || preceeding.delim; + } } else { - ret = exprs[0]; + ret = first = last = exprs[0]; + + if (!isNecessary) { + isNecessary = + // Used to distinguish from an ExpressionStatement which may not + // begin with the `{` and `function` tokens + (opening.beginsStmt && (ret.id === "{" || triggerFnExpr || isFunctor(ret))) || + // Used as the return value of a single-statement arrow function + (ret.id === "{" && preceeding.id === "=>") || + // Used to prevent left-to-right application of adjacent addition + // operators (the order of which effect type) + (first.id === "+" && preceeding.id === "+"); + } } + if (ret) { + // The operator may be necessary to override the default binding power of + // neighboring operators (whenever there is an operator in use within the + // first expression *or* the current group contains multiple expressions) + if (!isNecessary && (first.left || ret.exprs)) { + isNecessary = + (!isBeginOfExpr(preceeding) && first.lbp < preceeding.lbp) || + (!isEndOfExpr() && last.lbp < state.tokens.next.lbp); + } + + if (!isNecessary) { + warning("W126", opening); + } + ret.paren = true; - ret.triggerFnExpr = triggerFnExpr; } + return ret; }); application("=>"); - infix("[", function (left, that) { + infix("[", function(left, that) { var e = expression(10), s; if (e && e.type === "(string)") { if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) { - warning("W061", that); + if (isGlobalEval(left, state, funct)) { + warning("W061"); + } } countMember(e.value); @@ -3971,7 +4273,7 @@ var JSHINT = (function () { return res; } - prefix("[", function () { + prefix("[", function() { var blocktype = lookupBlockType(); if (blocktype.isCompArray) { if (!state.option.inESNext()) { @@ -3981,7 +4283,7 @@ var JSHINT = (function () { } else if (blocktype.isDestAssign && !state.option.inESNext()) { warning("W104", state.tokens.curr, "destructuring assignment"); } - var b = state.tokens.curr.line !== state.tokens.next.line; + var b = state.tokens.curr.line !== startLine(state.tokens.next); this.first = []; if (b) { indent += state.option.indent; @@ -4000,7 +4302,7 @@ var JSHINT = (function () { warning("W128"); do { advance(","); - } while(state.tokens.next.id === ","); + } while (state.tokens.next.id === ","); continue; } } @@ -4075,14 +4377,23 @@ var JSHINT = (function () { return id; } - function functionparams(fatarrow) { + /** + * @param {Object} [options] + * @param {token} [options.loneArg] The argument to the function in cases + * where it was defined using the + * single-argument shorthand. + * @param {bool} [options.parsedOpening] Whether the opening parenthesis has + * already been parsed. + */ + function functionparams(options) { var next; var params = []; var ident; var tokens = []; var t; var pastDefault = false; - var loneArg = fatarrow && fatarrow.loneArg; + var pastRest = false; + var loneArg = options && options.loneArg; if (loneArg && loneArg.identifier === true) { addlabel(loneArg.value, { type: "unused", token: loneArg }); @@ -4091,7 +4402,7 @@ var JSHINT = (function () { next = state.tokens.next; - if (!fatarrow || !fatarrow.parsedParen) { + if (!options || !options.parsedOpening) { advance("("); } @@ -4110,18 +4421,16 @@ var JSHINT = (function () { addlabel(t.id, { type: "unused", token: t.token }); } } - } else if (state.tokens.next.value === "...") { - if (!state.option.esnext) { - warning("W119", state.tokens.next, "spread/rest operator"); - } - advance("..."); - ident = identifier(true); - params.push(ident); - addlabel(ident, { type: "unused", token: state.tokens.curr }); } else { + if (checkPunctuators(state.tokens.next, ["..."])) pastRest = true; ident = identifier(true); - params.push(ident); - addlabel(ident, { type: "unused", token: state.tokens.curr }); + if (ident) { + params.push(ident); + addlabel(ident, { type: "unused", token: state.tokens.curr }); + } else { + // Skip invalid parameter. + while (!checkPunctuators(state.tokens.next, [",", ")"])) advance(); + } } // it is a syntax error to have a regular argument after a default argument @@ -4139,6 +4448,9 @@ var JSHINT = (function () { expression(10); } if (state.tokens.next.id === ",") { + if (pastRest) { + warning("W131", state.tokens.next); + } comma(); } else { advance(")", next); @@ -4203,39 +4515,76 @@ var JSHINT = (function () { return funct; } - function doTemplateLiteral() { - while (state.tokens.next.type !== "(template tail)" && state.tokens.next.id !== "(end)") { - advance(); - if (state.tokens.next.type === "(template tail)") { - break; - } else if (state.tokens.next.type !== "(template middle)" && - state.tokens.next.type !== "(end)") { - expression(10); // should probably have different rbp? + function isFunctor(token) { + return "(scope)" in token; + } + + function doTemplateLiteral(left) { + // ASSERT: this.type === "(template)" + // jshint validthis: true + var ctx = this.context; + var noSubst = this.noSubst; + var depth = this.depth; + + if (!noSubst) { + while (!end() && state.tokens.next.id !== "(end)") { + if (!state.tokens.next.template || state.tokens.next.depth > depth) { + expression(0); // should probably have different rbp? + } else { + // skip template start / middle + advance(); + } } } + return { id: "(template)", - type: "(template)" + type: "(template)", + tag: left }; + + function end() { + if (state.tokens.curr.template && state.tokens.curr.tail && + state.tokens.curr.context === ctx) return true; + var complete = (state.tokens.next.template && state.tokens.next.tail && + state.tokens.next.context === ctx); + if (complete) advance(); + return complete || state.tokens.next.isUnclosed; + } } /** - * @param {Object} [fatarrow] In the case that the function being parsed - * takes the "fat arrow" form, this object will - * contain details about the in-progress parsing - * operation. - * @param {Token} [fatarrow.loneArg] The argument to the function in cases - * where it was defined using the single- - * argument shorthand. - * @param {bool} [fatarrow.parsedParen] Whether the opening parenthesis has - * already been parsed. + * @param {Object} [options] + * @param {token} [options.name] The identifier belonging to the function (if + * any) + * @param {boolean} [options.statement] The statement that triggered creation + * of the current function. + * @param {string} [options.type] If specified, either "generator" or "arrow" + * @param {token} [options.loneArg] The argument to the function in cases + * where it was defined using the + * single-argument shorthand + * @param {bool} [options.parsedOpening] Whether the opening parenthesis has + * already been parsed + * @param {token} [options.classExprBinding] Define a function with this + * identifier in the new function's + * scope, mimicking the bahavior of + * class expression names within + * the body of member functions. */ - function doFunction(name, statement, generator, fatarrow) { - var f; + function doFunction(options) { + var f, name, statement, classExprBinding, isGenerator, isArrow; var oldOption = state.option; var oldIgnored = state.ignored; var oldScope = scope; + if (options) { + name = options.name; + statement = options.statement; + classExprBinding = options.classExprBinding; + isGenerator = options.type === "generator"; + isArrow = options.type === "arrow"; + } + state.option = Object.create(state.option); state.ignored = Object.create(state.ignored); scope = Object.create(scope); @@ -4243,7 +4592,7 @@ var JSHINT = (function () { funct = functor(name || state.nameStack.infer(), state.tokens.next, scope, { "(statement)": statement, "(context)": funct, - "(generator)": generator ? true : null + "(generator)": isGenerator }); f = funct; @@ -4255,22 +4604,26 @@ var JSHINT = (function () { addlabel(name, { type: "function" }); } - funct["(params)"] = functionparams(fatarrow); + if (classExprBinding) { + addlabel(classExprBinding, { type: "function" }); + } + + funct["(params)"] = functionparams(options); funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]); - if (fatarrow) { + if (isArrow) { if (!state.option.esnext) { warning("W119", state.tokens.curr, "arrow function syntax (=>)"); } - if (!fatarrow.loneArg) { + if (!options.loneArg) { advance("=>"); } } - block(false, true, true, !!fatarrow); + block(false, true, true, isArrow); - if (!state.option.noyield && generator && + if (!state.option.noyield && isGenerator && funct["(generator)"] !== "yielded") { warning("W124", state.tokens.curr); } @@ -4285,7 +4638,7 @@ var JSHINT = (function () { funct["(last)"] = state.tokens.curr.line; funct["(lastcharacter)"] = state.tokens.curr.character; - _.map(Object.keys(funct), function (key) { + _.map(Object.keys(funct), function(key) { if (key[0] === "(") return; funct["(blockscope)"].unshadow(key); }); @@ -4301,14 +4654,14 @@ var JSHINT = (function () { nestedBlockDepth: -1, ComplexityCount: 1, - verifyMaxStatementsPerFunction: function () { + verifyMaxStatementsPerFunction: function() { if (state.option.maxstatements && this.statementCount > state.option.maxstatements) { warning("W071", functionStartToken, this.statementCount); } }, - verifyMaxParametersPerFunction: function (params) { + verifyMaxParametersPerFunction: function(params) { params = params || []; if (state.option.maxparams && params.length > state.option.maxparams) { @@ -4316,7 +4669,7 @@ var JSHINT = (function () { } }, - verifyMaxNestedBlockDepthPerFunction: function () { + verifyMaxNestedBlockDepthPerFunction: function() { if (state.option.maxdepth && this.nestedBlockDepth > 0 && this.nestedBlockDepth === state.option.maxdepth + 1) { @@ -4324,7 +4677,7 @@ var JSHINT = (function () { } }, - verifyMaxComplexityPerFunction: function () { + verifyMaxComplexityPerFunction: function() { var max = state.option.maxcomplexity; var cc = this.ComplexityCount; if (max && cc > max) { @@ -4382,12 +4735,12 @@ var JSHINT = (function () { } } - (function (x) { - x.nud = function () { + (function(x) { + x.nud = function() { var b, f, i, p, t, g, nextVal; var props = {}; // All properties, including accessors - b = state.tokens.curr.line !== state.tokens.next.line; + b = state.tokens.curr.line !== startLine(state.tokens.next); if (b) { indent += state.option.indent; if (state.tokens.next.from === indent + state.option.indent) { @@ -4434,7 +4787,6 @@ var JSHINT = (function () { warning("W077", t, i); } } else { - g = false; if (state.tokens.next.value === "*" && state.tokens.next.type === "(punctuator)") { if (!state.option.inESNext()) { warning("W104", state.tokens.next, "generator functions"); @@ -4469,7 +4821,7 @@ var JSHINT = (function () { if (!state.option.inESNext()) { warning("W104", state.tokens.curr, "concise methods"); } - doFunction(null, undefined, g); + doFunction({ type: g ? "generator" : null }); } else { advance(":"); expression(10); @@ -4499,7 +4851,7 @@ var JSHINT = (function () { return this; }; - x.fud = function () { + x.fud = function() { error("E036", state.tokens.curr); }; }(delim("{"))); @@ -4510,47 +4862,68 @@ var JSHINT = (function () { if (!state.option.inESNext()) { warning("W104", state.tokens.curr, "destructuring expression"); } - var nextInnerDE = function () { + var nextInnerDE = function() { var ident; - if (_.contains(["[", "{"], state.tokens.next.value)) { + if (checkPunctuators(state.tokens.next, ["[", "{"])) { ids = destructuringExpression(); for (var id in ids) { id = ids[id]; identifiers.push({ id: id.id, token: id.token }); } - } else if (state.tokens.next.value === ",") { + } else if (checkPunctuators(state.tokens.next, [","])) { identifiers.push({ id: null, token: state.tokens.curr }); - } else if (state.tokens.next.value === "(") { + } else if (checkPunctuators(state.tokens.next, ["("])) { advance("("); nextInnerDE(); advance(")"); } else { + var is_rest = checkPunctuators(state.tokens.next, ["..."]); ident = identifier(); if (ident) identifiers.push({ id: ident, token: state.tokens.curr }); + return is_rest; } + return false; }; - if (state.tokens.next.value === "[") { + if (checkPunctuators(state.tokens.next, ["["])) { advance("["); - nextInnerDE(); - while (state.tokens.next.value !== "]") { + var element_after_rest = false; + if (nextInnerDE() && checkPunctuators(state.tokens.next, [","]) && + !element_after_rest) { + warning("W130", state.tokens.next); + element_after_rest = true; + } + while (!checkPunctuators(state.tokens.next, ["]"])) { advance(","); - nextInnerDE(); + if (checkPunctuators(state.tokens.next, ["]"])) { + // Trailing comma + break; + } + if (nextInnerDE() && checkPunctuators(state.tokens.next, [","]) && + !element_after_rest) { + warning("W130", state.tokens.next); + element_after_rest = true; + } } advance("]"); - } else if (state.tokens.next.value === "{") { + } else if (checkPunctuators(state.tokens.next, ["{"])) { advance("{"); id = identifier(); - if (state.tokens.next.value === ":") { + if (checkPunctuators(state.tokens.next, [":"])) { advance(":"); nextInnerDE(); } else { identifiers.push({ id: id, token: state.tokens.curr }); } - while (state.tokens.next.value !== "}") { + while (!checkPunctuators(state.tokens.next, ["}"])) { advance(","); + if (checkPunctuators(state.tokens.next, ["}"])) { + // Trailing comma + // ObjectBindingPattern: { BindingPropertyList , } + break; + } id = identifier(); - if (state.tokens.next.value === ":") { + if (checkPunctuators(state.tokens.next, [":"])) { advance(":"); nextInnerDE(); } else { @@ -4568,7 +4941,7 @@ var JSHINT = (function () { if (!first) return; - _.zip(tokens, Array.isArray(first) ? first : [ first ]).forEach(function (val) { + _.zip(tokens, Array.isArray(first) ? first : [ first ]).forEach(function(val) { var token = val[0]; var value = val[1]; @@ -4579,7 +4952,9 @@ var JSHINT = (function () { }); } - var conststatement = stmt("const", function (prefix) { + var conststatement = stmt("const", function(context) { + var prefix = context && context.prefix; + var inexport = context && context.inexport; var tokens; var value; var lone; // State variable to know if it is a lone identifier, or a destructuring statement. @@ -4596,6 +4971,10 @@ var JSHINT = (function () { } else { tokens = [ { id: identifier(), token: state.tokens.curr } ]; lone = true; + if (inexport) { + exported[state.tokens.curr.value] = true; + state.tokens.curr.exported = true; + } } for (var t in tokens) { if (tokens.hasOwnProperty(t)) { @@ -4647,9 +5026,11 @@ var JSHINT = (function () { }); conststatement.exps = true; - var varstatement = stmt("var", function (prefix) { + var varstatement = stmt("var", function(context) { // JavaScript does not have block scope. It only has function scope. So, // declaring a variable in a block can have unexpected consequences. + var prefix = context && context.prefix; + var inexport = context && context.inexport; var tokens, lone, value; this.first = []; @@ -4661,6 +5042,10 @@ var JSHINT = (function () { } else { tokens = [ { id: identifier(), token: state.tokens.curr } ]; lone = true; + if (inexport) { + exported[state.tokens.curr.value] = true; + state.tokens.curr.exported = true; + } } for (var t in tokens) { if (tokens.hasOwnProperty(t)) { @@ -4668,8 +5053,15 @@ var JSHINT = (function () { if (state.option.inESNext() && funct[t.id] === "const") { warning("E011", null, t.id); } - if (funct["(global)"] && predefined[t.id] === false) { - warning("W079", t.token, t.id); + if (funct["(global)"]) { + if (predefined[t.id] === false) { + warning("W079", t.token, t.id); + } else if (state.option.futurehostile === false) { + if ((!state.option.inES5() && vars.ecmaIdentifiers[5][t.id] === false) || + (!state.option.inESNext() && vars.ecmaIdentifiers[6][t.id] === false)) { + warning("W129", t.token, t.id); + } + } } if (t.id) { addlabel(t.id, { type: "unused", token: t.token }); @@ -4711,7 +5103,9 @@ var JSHINT = (function () { }); varstatement.exps = true; - var letstatement = stmt("let", function (prefix) { + var letstatement = stmt("let", function(context) { + var prefix = context && context.prefix; + var inexport = context && context.inexport; var tokens, lone, value, letblock; if (!state.option.inESNext()) { @@ -4738,6 +5132,10 @@ var JSHINT = (function () { } else { tokens = [ { id: identifier(), token: state.tokens.curr.value } ]; lone = true; + if (inexport) { + exported[state.tokens.curr.value] = true; + state.tokens.curr.exported = true; + } } for (var t in tokens) { if (tokens.hasOwnProperty(t)) { @@ -4792,22 +5190,24 @@ var JSHINT = (function () { }); letstatement.exps = true; - blockstmt("class", function () { + blockstmt("class", function() { return classdef.call(this, true); }); - function classdef(stmt) { + function classdef(isStatement) { + /*jshint validthis:true */ if (!state.option.inESNext()) { warning("W104", state.tokens.curr, "class"); } - if (stmt) { + if (isStatement) { // BindingIdentifier this.name = identifier(); addlabel(this.name, { type: "unused", token: state.tokens.curr }); } else if (state.tokens.next.identifier && state.tokens.next.value !== "extends") { // BindingIdentifier(opt) this.name = identifier(); + this.namedExpr = true; } else { this.name = state.nameStack.infer(); } @@ -4836,6 +5236,7 @@ var JSHINT = (function () { function classbody(c) { var name; var isStatic; + var isGenerator; var getset; var props = {}; var staticProps = {}; @@ -4843,7 +5244,13 @@ var JSHINT = (function () { for (var i = 0; state.tokens.next.id !== "}"; ++i) { name = state.tokens.next; isStatic = false; + isGenerator = false; getset = null; + if (name.id === "*") { + isGenerator = true; + advance("*"); + name = state.tokens.next; + } if (name.id === "[") { name = computedPropertyName(); } else if (isPropertyName(name)) { @@ -4851,6 +5258,10 @@ var JSHINT = (function () { advance(); computed = false; if (name.identifier && name.value === "static") { + if (checkPunctuators(state.tokens.next, ["*"])) { + isGenerator = true; + advance("*"); + } if (isPropertyName(state.tokens.next) || state.tokens.next.id === "[") { computed = state.tokens.next.id === "["; isStatic = true; @@ -4885,7 +5296,7 @@ var JSHINT = (function () { advance(); } if (state.tokens.next.value !== "(") { - doFunction(undefined, c, false, null); + doFunction({ statement: c }); } } @@ -4913,13 +5324,17 @@ var JSHINT = (function () { propertyName(name); - doFunction(null, c, false, null); + doFunction({ + statement: c, + type: isGenerator ? "generator" : null, + classExprBinding: c.namedExpr ? c.name : null + }); } checkProperties(props); } - blockstmt("function", function () { + blockstmt("function", function() { var generator = false; if (state.tokens.next.value === "*") { advance("*"); @@ -4944,14 +5359,18 @@ var JSHINT = (function () { } addlabel(i, { type: "unction", token: state.tokens.curr }); - doFunction(i, { statement: true }, generator); + doFunction({ + name: i, + statement: this, + type: generator ? "generator" : null + }); if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) { error("E039"); } return this; }); - prefix("function", function () { + prefix("function", function() { var generator = false; if (state.tokens.next.value === "*") { @@ -4963,7 +5382,7 @@ var JSHINT = (function () { } var i = optionalidentifier(); - var fn = doFunction(i, undefined, generator); + var fn = doFunction({ name: i, type: generator ? "generator" : null }); function isVariable(name) { return name[0] !== "("; } function isLocal(name) { return fn[name] === "var"; } @@ -4972,14 +5391,14 @@ var JSHINT = (function () { // If the function we just parsed accesses any non-local variables // trigger a warning. Otherwise, the function is safe even within // a loop. - if (_.some(fn, function (val, name) { return isVariable(name) && !isLocal(name); })) { + if (_.some(fn, function(val, name) { return isVariable(name) && !isLocal(name); })) { warning("W083"); } } return this; }); - blockstmt("if", function () { + blockstmt("if", function() { var t = state.tokens.next; increaseComplexityCount(); state.condition = true; @@ -5023,7 +5442,7 @@ var JSHINT = (function () { return this; }); - blockstmt("try", function () { + blockstmt("try", function() { var b; function doCatch() { @@ -5101,7 +5520,7 @@ var JSHINT = (function () { return this; }); - blockstmt("while", function () { + blockstmt("while", function() { var t = state.tokens.next; funct["(breakage)"] += 1; funct["(loopage)"] += 1; @@ -5115,7 +5534,7 @@ var JSHINT = (function () { return this; }).labelled = true; - blockstmt("with", function () { + blockstmt("with", function() { var t = state.tokens.next; if (state.directive["use strict"]) { error("E010", state.tokens.curr); @@ -5131,7 +5550,7 @@ var JSHINT = (function () { return this; }); - blockstmt("switch", function () { + blockstmt("switch", function() { var t = state.tokens.next; var g = false; var noindent = false; @@ -5242,15 +5661,15 @@ var JSHINT = (function () { } }).labelled = true; - stmt("debugger", function () { + stmt("debugger", function() { if (!state.option.debug) { warning("W087", this); } return this; }).exps = true; - (function () { - var x = stmt("do", function () { + (function() { + var x = stmt("do", function() { funct["(breakage)"] += 1; funct["(loopage)"] += 1; increaseComplexityCount(); @@ -5269,7 +5688,7 @@ var JSHINT = (function () { x.exps = true; }()); - blockstmt("for", function () { + blockstmt("for", function() { var s, t = state.tokens.next; var letscope = false; var foreachtok = null; @@ -5294,8 +5713,7 @@ var JSHINT = (function () { do { nextop = peek(i); ++i; - } while (!_.contains(inof, nextop.value) && nextop.value !== ";" && - nextop.type !== "(end)"); + } while (!_.contains(inof, nextop.value) && nextop.value !== ";" && nextop.type !== "(end)"); // if we're in a for (… in|of …) statement if (_.contains(inof, nextop.value)) { @@ -5305,13 +5723,13 @@ var JSHINT = (function () { if (state.tokens.next.id === "var") { advance("var"); - state.syntax["var"].fud.call(state.syntax["var"].fud, true); + state.tokens.curr.fud({ prefix: true }); } else if (state.tokens.next.id === "let") { advance("let"); // create a new block scope letscope = true; funct["(blockscope)"].stack(); - state.syntax["let"].fud.call(state.syntax["let"].fud, true); + state.tokens.curr.fud({ prefix: true }); } else if (!state.tokens.next.identifier) { error("E030", state.tokens.next, state.tokens.next.type); advance(); @@ -5325,8 +5743,9 @@ var JSHINT = (function () { default: var ident = state.tokens.next.value; if (!funct["(blockscope)"].getlabel(ident) && - !(scope[ident] || {})[ident]) + !(scope[ident] || {})[ident]) { warning("W088", state.tokens.next, state.tokens.next.value); + } } advance(); } @@ -5377,13 +5796,13 @@ var JSHINT = (function () { if (state.tokens.next.id !== ";") { if (state.tokens.next.id === "var") { advance("var"); - state.syntax["var"].fud.call(state.syntax["var"].fud); + state.tokens.curr.fud(); } else if (state.tokens.next.id === "let") { advance("let"); // create a new block scope letscope = true; funct["(blockscope)"].stack(); - state.syntax["let"].fud.call(state.syntax["let"].fud); + state.tokens.curr.fud(); } else { for (;;) { expression(0, "for"); @@ -5427,7 +5846,7 @@ var JSHINT = (function () { }).labelled = true; - stmt("break", function () { + stmt("break", function() { var v = state.tokens.next.value; if (funct["(breakage)"] === 0) @@ -5437,7 +5856,7 @@ var JSHINT = (function () { nolinebreak(this); if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { - if (state.tokens.curr.line === state.tokens.next.line) { + if (state.tokens.curr.line === startLine(state.tokens.next)) { if (funct[v] !== "label") { warning("W090", state.tokens.next, v); } else if (scope[v] !== funct) { @@ -5454,7 +5873,7 @@ var JSHINT = (function () { }).exps = true; - stmt("continue", function () { + stmt("continue", function() { var v = state.tokens.next.value; if (funct["(breakage)"] === 0) @@ -5464,7 +5883,7 @@ var JSHINT = (function () { nolinebreak(this); if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { - if (state.tokens.curr.line === state.tokens.next.line) { + if (state.tokens.curr.line === startLine(state.tokens.next)) { if (funct[v] !== "label") { warning("W090", state.tokens.next, v); } else if (scope[v] !== funct) { @@ -5483,8 +5902,8 @@ var JSHINT = (function () { }).exps = true; - stmt("return", function () { - if (this.line === state.tokens.next.line) { + stmt("return", function() { + if (this.line === startLine(state.tokens.next)) { if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { this.first = expression(0); @@ -5506,10 +5925,10 @@ var JSHINT = (function () { return this; }).exps = true; - (function (x) { + (function(x) { x.exps = true; x.lbp = 25; - }(prefix("yield", function () { + }(prefix("yield", function() { var prev = state.tokens.prev; if (state.option.inESNext(true) && !funct["(generator)"]) { // If it's a yield within a catch clause inside a generator then that's ok @@ -5527,7 +5946,7 @@ var JSHINT = (function () { advance("*"); } - if (this.line === state.tokens.next.line || !state.option.inMoz(true)) { + if (this.line === startLine(state.tokens.next) || !state.option.inMoz(true)) { if (delegatingYield || (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud)) { @@ -5551,7 +5970,7 @@ var JSHINT = (function () { }))); - stmt("throw", function () { + stmt("throw", function() { nolinebreak(this); this.first = expression(20); @@ -5560,7 +5979,7 @@ var JSHINT = (function () { return this; }).exps = true; - stmt("import", function () { + stmt("import", function() { if (!state.option.inESNext()) { warning("W119", state.tokens.curr, "import"); } @@ -5575,7 +5994,22 @@ var JSHINT = (function () { // ImportClause :: ImportedDefaultBinding this.name = identifier(); addlabel(this.name, { type: "unused", token: state.tokens.curr }); - } else if (state.tokens.next.id === "*") { + if (state.tokens.next.value === ",") { + // ImportClause :: ImportedDefaultBinding , NameSpaceImport + // ImportClause :: ImportedDefaultBinding , NamedImports + advance(","); + // At this point, we intentionally fall through to continue matching + // either NameSpaceImport or NamedImports. + // Discussion: + // https://github.com/jshint/jshint/pull/2144#discussion_r23978406 + } else { + advance("from"); + advance("(string)"); + return this; + } + } + + if (state.tokens.next.id === "*") { // ImportClause :: NameSpaceImport advance("*"); advance("as"); @@ -5584,6 +6018,7 @@ var JSHINT = (function () { addlabel(this.name, { type: "unused", token: state.tokens.curr }); } } else { + // ImportClause :: NamedImports advance("{"); for (;;) { if (state.tokens.next.value === "}") { @@ -5621,8 +6056,11 @@ var JSHINT = (function () { return this; }).exps = true; - stmt("export", function () { + stmt("export", function() { var ok = true; + var token; + var identifier; + if (!state.option.inESNext()) { warning("W119", state.tokens.curr, "export"); ok = false; @@ -5634,6 +6072,7 @@ var JSHINT = (function () { } if (state.tokens.next.value === "*") { + // ExportDeclaration :: export * FromClause advance("*"); advance("from"); advance("(string)"); @@ -5641,23 +6080,50 @@ var JSHINT = (function () { } if (state.tokens.next.type === "default") { + // ExportDeclaration :: export default HoistableDeclaration + // ExportDeclaration :: export default ClassDeclaration state.nameStack.set(state.tokens.next); advance("default"); if (state.tokens.next.id === "function" || state.tokens.next.id === "class") { this.block = true; } - this.exportee = expression(10); + + token = peek(); + + expression(10); + + if (state.tokens.next.id === "class") { + identifier = token.name; + } else { + identifier = token.value; + } + + addlabel(identifier, { + type: "function", token: token + }); return this; } if (state.tokens.next.value === "{") { + // ExportDeclaration :: export ExportClause advance("{"); + var exportedTokens = []; for (;;) { - var id; - exported[id = identifier(false, false, ok)] = ok; - if (ok) { - funct["(blockscope)"].setExported(id); + if (!state.tokens.next.identifier) { + error("E030", state.tokens.next, state.tokens.next.value); + } + advance(); + + state.tokens.curr.exported = ok; + exportedTokens.push(state.tokens.curr); + + if (state.tokens.next.value === "as") { + advance("as"); + if (!state.tokens.next.identifier) { + error("E030", state.tokens.next, state.tokens.next.value); + } + advance(); } if (state.tokens.next.value === ",") { @@ -5670,31 +6136,43 @@ var JSHINT = (function () { break; } } + if (state.tokens.next.value === "from") { + // ExportDeclaration :: export ExportClause FromClause + advance("from"); + advance("(string)"); + } else if (ok) { + exportedTokens.forEach(function(token) { + if (!funct[token.value]) { + isundef(funct, "W117", token, token.value); + } + exported[token.value] = true; + funct["(blockscope)"].setExported(token.value); + }); + } return this; } if (state.tokens.next.id === "var") { + // ExportDeclaration :: export VariableStatement advance("var"); - exported[state.tokens.next.value] = ok; - state.tokens.next.exported = true; - state.syntax["var"].fud.call(state.syntax["var"].fud); + state.tokens.curr.fud({ inexport:true }); } else if (state.tokens.next.id === "let") { + // ExportDeclaration :: export VariableStatement advance("let"); - exported[state.tokens.next.value] = ok; - state.tokens.next.exported = true; - state.syntax["let"].fud.call(state.syntax["let"].fud); + state.tokens.curr.fud({ inexport:true }); } else if (state.tokens.next.id === "const") { + // ExportDeclaration :: export VariableStatement advance("const"); - exported[state.tokens.next.value] = ok; - state.tokens.next.exported = true; - state.syntax["const"].fud.call(state.syntax["const"].fud); + state.tokens.curr.fud({ inexport:true }); } else if (state.tokens.next.id === "function") { + // ExportDeclaration :: export Declaration this.block = true; advance("function"); exported[state.tokens.next.value] = ok; state.tokens.next.exported = true; state.syntax["function"].fud(); } else if (state.tokens.next.id === "class") { + // ExportDeclaration :: export Declaration this.block = true; advance("class"); exported[state.tokens.next.value] = ok; @@ -5741,7 +6219,7 @@ var JSHINT = (function () { // this function is used to determine whether a squarebracket or a curlybracket // expression is a comprehension array, destructuring assignment or a json value. - var lookupBlockType = function () { + var lookupBlockType = function() { var pn, pn1; var i = -1; var bracketStack = 0; @@ -5776,7 +6254,7 @@ var JSHINT = (function () { ret.isBlock = true; ret.notJson = true; } - } while (bracketStack > 0 && pn.id !== "(end)" && i < 15); + } while (bracketStack > 0 && pn.id !== "(end)"); return ret; }; @@ -5876,15 +6354,15 @@ var JSHINT = (function () { // * "define" which will define the variables local to the list comprehension // * "filter" which will help filter out values - var arrayComprehension = function () { - var CompArray = function () { + var arrayComprehension = function() { + var CompArray = function() { this.mode = "use"; this.variables = []; }; var _carrays = []; var _current; function declare(v) { - var l = _current.variables.filter(function (elt) { + var l = _current.variables.filter(function(elt) { // if it has, change its undef state if (elt.value === v) { elt.undef = false; @@ -5894,7 +6372,7 @@ var JSHINT = (function () { return l !== 0; } function use(v) { - var l = _current.variables.filter(function (elt) { + var l = _current.variables.filter(function(elt) { // and if it has been defined if (elt.value === v && !elt.undef) { if (elt.unused === true) { @@ -5906,12 +6384,12 @@ var JSHINT = (function () { // otherwise we warn about it return (l === 0); } - return {stack: function () { + return { stack: function() { _current = new CompArray(); _carrays.push(_current); }, - unstack: function () { - _current.variables.filter(function (v) { + unstack: function() { + _current.variables.filter(function(v) { if (v.unused) warning("W098", v.token, v.raw_text || v.value); if (v.undef) @@ -5920,11 +6398,11 @@ var JSHINT = (function () { _carrays.splice(-1, 1); _current = _carrays[_carrays.length - 1]; }, - setState: function (s) { + setState: function(s) { if (_.contains(["use", "define", "generate", "filter"], s)) _current.mode = s; }, - check: function (v) { + check: function(v) { if (!_current) { return; } @@ -5991,7 +6469,7 @@ var JSHINT = (function () { warning("W095", state.tokens.next, state.tokens.next.value); } if (o[state.tokens.next.value] === true) { - warning("W075", state.tokens.next, state.tokens.next.value); + warning("W075", state.tokens.next, "key", state.tokens.next.value); } else if ((state.tokens.next.value === "__proto__" && !state.option.proto) || (state.tokens.next.value === "__iterator__" && !state.option.iterator)) { @@ -6057,7 +6535,7 @@ var JSHINT = (function () { } } - var blockScope = function () { + var blockScope = function() { var _current = {}; var _variables = [_current]; @@ -6079,18 +6557,18 @@ var JSHINT = (function () { } return { - stack: function () { + stack: function() { _current = {}; _variables.push(_current); }, - unstack: function () { + unstack: function() { _checkBlockLabels(); _variables.splice(_variables.length - 1, 1); _current = _.last(_variables); }, - getlabel: function (l) { + getlabel: function(l) { for (var i = _variables.length - 1 ; i >= 0; --i) { if (_.has(_variables[i], l) && !_variables[i][l]["(shadowed)"]) { return _variables[i]; @@ -6098,7 +6576,7 @@ var JSHINT = (function () { } }, - shadow: function (name) { + shadow: function(name) { for (var i = _variables.length - 1; i >= 0; i--) { if (_.has(_variables[i], name)) { _variables[i][name]["(shadowed)"] = true; @@ -6106,7 +6584,7 @@ var JSHINT = (function () { } }, - unshadow: function (name) { + unshadow: function(name) { for (var i = _variables.length - 1; i >= 0; i--) { if (_.has(_variables[i], name)) { _variables[i][name]["(shadowed)"] = false; @@ -6114,11 +6592,11 @@ var JSHINT = (function () { } }, - atTop: function () { + atTop: function() { return _variables.length === 1; }, - setExported: function (id) { + setExported: function(id) { if (funct["(blockscope)"].atTop()) { var item = _current[id]; if (item && item["(token)"]) { @@ -6128,11 +6606,11 @@ var JSHINT = (function () { }, current: { - has: function (t) { + has: function(t) { return _.has(_current, t); }, - add: function (t, type, tok) { + add: function(t, type, tok) { _current[t] = { "(type)" : type, "(token)": tok, "(shadowed)": false }; } } @@ -6144,7 +6622,7 @@ var JSHINT = (function () { }; // The actual JSHINT function itself. - var itself = function (s, o, g) { + var itself = function(s, o, g) { var i, k, x, reIgnoreStr, reIgnore; var optionKeys; var newOptionObj = {}; @@ -6164,7 +6642,7 @@ var JSHINT = (function () { } predefined = Object.create(null); - combine(predefined, vars.ecmaIdentifiers); + combine(predefined, vars.ecmaIdentifiers[3]); combine(predefined, vars.reservedVars); combine(predefined, g || {}); @@ -6183,7 +6661,7 @@ var JSHINT = (function () { } if (o) { - each(o.predef || null, function (item) { + each(o.predef || null, function(item) { var slice, prop; if (item[0] === "-") { @@ -6197,7 +6675,7 @@ var JSHINT = (function () { } }); - each(o.exported || null, function (item) { + each(o.exported || null, function(item) { exported[item] = true; }); @@ -6254,31 +6732,31 @@ var JSHINT = (function () { return state.jsonMode; }, - getOption: function (name) { + getOption: function(name) { return state.option[name] || null; }, - getCache: function (name) { + getCache: function(name) { return state.cache[name]; }, - setCache: function (name, value) { + setCache: function(name, value) { state.cache[name] = value; }, - warn: function (code, data) { + warn: function(code, data) { warningAt.apply(null, [ code, data.line, data.char ].concat(data.data)); }, - on: function (names, listener) { - names.split(" ").forEach(function (name) { + on: function(names, listener) { + names.split(" ").forEach(function(name) { emitter.on(name, listener); }.bind(this)); } }; emitter.removeAllListeners(); - (extraModules || []).forEach(function (func) { + (extraModules || []).forEach(function(func) { func(api); }); @@ -6290,7 +6768,7 @@ var JSHINT = (function () { o.ignoreDelimiters = [o.ignoreDelimiters]; } - o.ignoreDelimiters.forEach(function (delimiterPair) { + o.ignoreDelimiters.forEach(function(delimiterPair) { if (!delimiterPair.start || !delimiterPair.end) return; @@ -6308,27 +6786,27 @@ var JSHINT = (function () { lex = new Lexer(s); - lex.on("warning", function (ev) { + lex.on("warning", function(ev) { warningAt.apply(null, [ ev.code, ev.line, ev.character].concat(ev.data)); }); - lex.on("error", function (ev) { + lex.on("error", function(ev) { errorAt.apply(null, [ ev.code, ev.line, ev.character ].concat(ev.data)); }); - lex.on("fatal", function (ev) { + lex.on("fatal", function(ev) { quit("E041", ev.line, ev.from); }); - lex.on("Identifier", function (ev) { + lex.on("Identifier", function(ev) { emitter.emit("Identifier", ev); }); - lex.on("String", function (ev) { + lex.on("String", function(ev) { emitter.emit("String", ev); }); - lex.on("Number", function (ev) { + lex.on("Number", function(ev) { emitter.emit("Number", ev); }); @@ -6372,7 +6850,7 @@ var JSHINT = (function () { advance((state.tokens.next && state.tokens.next.value !== ".") ? "(end)" : undefined); funct["(blockscope)"].unstack(); - var markDefined = function (name, context) { + var markDefined = function(name, context) { do { if (typeof context[name] === "string") { // JSHINT marks unused variables as 'unused' and @@ -6395,7 +6873,7 @@ var JSHINT = (function () { return false; }; - var clearImplied = function (name, line) { + var clearImplied = function(name, line) { if (!implied[name]) return; @@ -6411,7 +6889,7 @@ var JSHINT = (function () { implied[name] = newImplied; }; - var warnUnused = function (name, tkn, type, unused_opt) { + var warnUnused = function(name, tkn, type, unused_opt) { var line = tkn.line; var chr = tkn.from; var raw_name = tkn.raw_text || name; @@ -6445,7 +6923,7 @@ var JSHINT = (function () { }); }; - var checkUnused = function (func, key) { + var checkUnused = function(func, key) { var type = func[key]; var tkn = func["(tokens)"][key]; @@ -6481,7 +6959,7 @@ var JSHINT = (function () { } } - functions.forEach(function (func) { + functions.forEach(function(func) { if (func["(unusedOption)"] === false) { return; } @@ -6504,7 +6982,7 @@ var JSHINT = (function () { unused_opt = func["(unusedOption)"] || state.option.unused; unused_opt = unused_opt === true ? "last-param" : unused_opt; - // 'undefined' is a special case for (function (window, undefined) { ... })(); + // 'undefined' is a special case for (function(window, undefined) { ... })(); // patterns. if (param === "undefined") @@ -6558,14 +7036,14 @@ var JSHINT = (function () { }; // Modules. - itself.addModule = function (func) { + itself.addModule = function(func) { extraModules.push(func); }; itself.addModule(style.register); // Data summary. - itself.data = function () { + itself.data = function() { var data = { functions: [], options: state.option @@ -6694,7 +7172,13 @@ var Token = { RegExp: 9, TemplateHead: 10, TemplateMiddle: 11, - TemplateTail: 12 + TemplateTail: 12, + NoSubstTemplate: 13 +}; + +var Context = { + Block: 1, + Template: 2 }; // Object that handles postponed lexing verifications that checks the parsed @@ -6704,11 +7188,11 @@ function asyncTrigger() { var _checks = []; return { - push: function (fn) { + push: function(fn) { _checks.push(fn); }, - check: function () { + check: function() { for (var check = 0; check < _checks.length; ++check) { _checks[check](); } @@ -6733,7 +7217,7 @@ function asyncTrigger() { * to token() method returning the next token, the Lexer object also * emits events. * - * lex.on("Identifier", function (data) { + * lex.on("Identifier", function(data) { * if (data.name.indexOf("_") >= 0) { * // Produce a warning. * } @@ -6774,9 +7258,8 @@ function Lexer(source) { this.from = 1; this.input = ""; this.inComment = false; - this.inTemplate = false; - this.templateLine = null; - this.templateChar = null; + this.context = []; + this.templateStarts = []; for (var i = 0; i < state.option.indent; i += 1) { state.tab += " "; @@ -6786,12 +7269,32 @@ function Lexer(source) { Lexer.prototype = { _lines: [], - getLines: function () { + inContext: function(ctxType) { + return this.context.length > 0 && this.context[this.context.length - 1].type === ctxType; + }, + + pushContext: function(ctxType) { + this.context.push({ type: ctxType }); + }, + + popContext: function() { + return this.context.pop(); + }, + + isContext: function(context) { + return this.context.length > 0 && this.context[this.context.length - 1] === context; + }, + + currentContext: function() { + return this.context.length > 0 && this.context[this.context.length - 1]; + }, + + getLines: function() { this._lines = state.lines; return this._lines; }, - setLines: function (val) { + setLines: function(val) { this._lines = val; state.lines = this._lines; }, @@ -6800,14 +7303,14 @@ Lexer.prototype = { * Return the next i character without actually moving the * char pointer. */ - peek: function (i) { + peek: function(i) { return this.input.charAt(i || 0); }, /* * Move the char pointer forward i times. */ - skip: function (i) { + skip: function(i) { i = i || 1; this.char += i; this.input = this.input.slice(i); @@ -6818,12 +7321,12 @@ Lexer.prototype = { * Underscore.js i.e. you can subscribe to multiple events with * one call: * - * lex.on("Identifier Number", function (data) { + * lex.on("Identifier Number", function(data) { * // ... * }); */ - on: function (names, listener) { - names.split(" ").forEach(function (name) { + on: function(names, listener) { + names.split(" ").forEach(function(name) { this.emitter.on(name, listener); }.bind(this)); }, @@ -6832,7 +7335,7 @@ Lexer.prototype = { * Trigger a token event. All arguments will be passed to each * listener. */ - trigger: function () { + trigger: function() { this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments)); }, @@ -6843,8 +7346,8 @@ Lexer.prototype = { * by the parser. This avoids parser's peek() to give the lexer * a false context. */ - triggerAsync: function (type, args, checks, fn) { - checks.push(function () { + triggerAsync: function(type, args, checks, fn) { + checks.push(function() { if (fn()) { this.trigger(type, args); } @@ -6858,7 +7361,7 @@ Lexer.prototype = { * This method's implementation was heavily influenced by the * scanPunctuator function in the Esprima parser's source code. */ - scanPunctuator: function () { + scanPunctuator: function() { var ch1 = this.peek(); var ch2, ch3, ch4; @@ -6879,8 +7382,6 @@ Lexer.prototype = { case ")": case ";": case ",": - case "{": - case "}": case "[": case "]": case ":": @@ -6891,6 +7392,24 @@ Lexer.prototype = { value: ch1 }; + // A block/object opener + case "{": + this.pushContext(Context.Block); + return { + type: Token.Punctuator, + value: ch1 + }; + + // A block/object closer + case "}": + if (this.inContext(Context.Block)) { + this.popContext(); + } + return { + type: Token.Punctuator, + value: ch1 + }; + // A pound sign (for Node shebangs) case "#": return { @@ -7015,7 +7534,7 @@ Lexer.prototype = { * also recognizes JSHint- and JSLint-specific comments such as * /*jshint, /*jslint, /*globals and so on. */ - scanComments: function () { + scanComments: function() { var ch1 = this.peek(); var ch2 = this.peek(1); var rest = this.input.substr(2); @@ -7039,7 +7558,7 @@ Lexer.prototype = { body = body.replace(/\n/g, " "); - special.forEach(function (str) { + special.forEach(function(str) { if (isSpecial) { return; } @@ -7155,7 +7674,7 @@ Lexer.prototype = { * Extract a keyword out of the next sequence of characters or * return 'null' if its not possible. */ - scanKeyword: function () { + scanKeyword: function() { var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input); var keywords = [ "if", "in", "do", "var", "for", "new", @@ -7184,7 +7703,7 @@ Lexer.prototype = { * to Identifier this method can also produce BooleanLiteral * (true/false) and NullLiteral (null). */ - scanIdentifier: function () { + scanIdentifier: function() { var id = ""; var index = 0; var type, char; @@ -7229,7 +7748,7 @@ Lexer.prototype = { return null; }.bind(this); - var getIdentifierStart = function () { + var getIdentifierStart = function() { /*jshint validthis:true */ var chr = this.peek(index); var code = chr.charCodeAt(0); @@ -7255,7 +7774,7 @@ Lexer.prototype = { return null; }.bind(this); - var getIdentifierPart = function () { + var getIdentifierPart = function() { /*jshint validthis:true */ var chr = this.peek(index); var code = chr.charCodeAt(0); @@ -7332,7 +7851,7 @@ Lexer.prototype = { * This method's implementation was heavily influenced by the * scanNumericLiteral function in the Esprima parser's source code. */ - scanNumericLiteral: function () { + scanNumericLiteral: function() { var index = 0; var value = ""; var length = this.input.length; @@ -7543,7 +8062,7 @@ Lexer.prototype = { // Assumes previously parsed character was \ (=== '\\') and was not skipped. - scanEscapeSequence: function (checks) { + scanEscapeSequence: function(checks) { var allowNewLine = false; var jump = 1; this.skip(); @@ -7556,7 +8075,7 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "\\'" ] - }, checks, function () {return state.jsonMode; }); + }, checks, function() {return state.jsonMode; }); break; case "b": char = "\\b"; @@ -7584,10 +8103,20 @@ Lexer.prototype = { line: this.line, character: this.char }, checks, - function () { return n >= 0 && n <= 7 && state.directive["use strict"]; }); + function() { return n >= 0 && n <= 7 && state.directive["use strict"]; }); break; case "u": - char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16)); + var hexCode = this.input.substr(1, 4); + var code = parseInt(hexCode, 16); + if (isNaN(code)) { + this.trigger("warning", { + code: "W052", + line: this.line, + character: this.char, + data: [ "u" + hexCode ] + }); + } + char = String.fromCharCode(code); jump = 5; break; case "v": @@ -7596,7 +8125,7 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "\\v" ] - }, checks, function () { return state.jsonMode; }); + }, checks, function() { return state.jsonMode; }); char = "\v"; break; @@ -7608,7 +8137,7 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "\\x-" ] - }, checks, function () { return state.jsonMode; }); + }, checks, function() { return state.jsonMode; }); char = String.fromCharCode(x); jump = 3; @@ -7627,7 +8156,7 @@ Lexer.prototype = { break; } - return {char: char, jump: jump, allowNewLine: allowNewLine}; + return { char: char, jump: jump, allowNewLine: allowNewLine }; }, /* @@ -7636,78 +8165,91 @@ Lexer.prototype = { * literals can span across multiple lines, this method has to move * the char pointer. */ - scanTemplateLiteral: function (checks) { + scanTemplateLiteral: function(checks) { var tokenType; - var value = ''; + var value = ""; var ch; + var startLine = this.line; + var startChar = this.char; + var depth = this.templateStarts.length; - // String must start with a backtick. - if (!this.inTemplate) { - if (!state.option.esnext || this.peek() !== "`") { - return null; - } - this.templateLine = this.line; - this.templateChar = this.char; + if (!state.option.esnext) { + // Only lex template strings in ESNext mode. + return null; + } else if (this.peek() === "`") { + // Template must start with a backtick. + tokenType = Token.TemplateHead; + this.templateStarts.push({ line: this.line, char: this.char }); + depth = this.templateStarts.length; this.skip(1); - } else if (this.peek() !== '}') { - // If we're in a template, and we don't have a '}', lex something else instead. + this.pushContext(Context.Template); + } else if (this.inContext(Context.Template) && this.peek() === "}") { + // If we're in a template context, and we have a '}', lex a TemplateMiddle. + tokenType = Token.TemplateMiddle; + } else { + // Go lex something else. return null; } while (this.peek() !== "`") { while ((ch = this.peek()) === "") { + value += "\n"; if (!this.nextLine()) { - // Unclosed template literal --- point to the starting line, or the EOF? - tokenType = this.inTemplate ? Token.TemplateHead : Token.TemplateMiddle; - this.inTemplate = false; + // Unclosed template literal --- point to the starting "`" + var startPos = this.templateStarts.pop(); this.trigger("error", { code: "E052", - line: this.templateLine, - character: this.templateChar + line: startPos.line, + character: startPos.char }); return { type: tokenType, value: value, - isUnclosed: true + startLine: startLine, + startChar: startChar, + isUnclosed: true, + depth: depth, + context: this.popContext() }; } } if (ch === '$' && this.peek(1) === '{') { value += '${'; - tokenType = value.charAt(0) === '}' ? Token.TemplateMiddle : Token.TemplateHead; - // Either TokenHead or TokenMiddle --- depending on if the initial value - // is '}' or not. this.skip(2); - this.inTemplate = true; return { type: tokenType, value: value, - isUnclosed: false + startLine: startLine, + startChar: startChar, + isUnclosed: false, + depth: depth, + context: this.currentContext() }; } else if (ch === '\\') { var escape = this.scanEscapeSequence(checks); value += escape.char; this.skip(escape.jump); - } else if (ch === '`') { - break; - } else { + } else if (ch !== '`') { // Otherwise, append the value and continue. value += ch; this.skip(1); } } - // Final value is either TokenTail or NoSubstititionTemplate --- essentially a string - tokenType = this.inTemplate ? Token.TemplateTail : Token.StringLiteral; - this.inTemplate = false; + // Final value is either NoSubstTemplate or TemplateTail + tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail; this.skip(1); + this.templateStarts.pop(); return { type: tokenType, value: value, + startLine: startLine, + startChar: startChar, isUnclosed: false, - quote: "`" + depth: depth, + context: this.popContext() }; }, @@ -7722,7 +8264,7 @@ Lexer.prototype = { * var str = "hello\ * world"; */ - scanStringLiteral: function (checks) { + scanStringLiteral: function(checks) { /*jshint loopfunc:true */ var quote = this.peek(); @@ -7736,7 +8278,7 @@ Lexer.prototype = { code: "W108", line: this.line, character: this.char // +1? - }, checks, function () { return state.jsonMode && quote !== "\""; }); + }, checks, function() { return state.jsonMode && quote !== "\""; }); var value = ""; var startLine = this.line; @@ -7771,13 +8313,13 @@ Lexer.prototype = { code: "W043", line: this.line, character: this.char - }, checks, function () { return !state.option.multistr; }); + }, checks, function() { return !state.option.multistr; }); this.triggerAsync("warning", { code: "W042", line: this.line, character: this.char - }, checks, function () { return state.jsonMode && state.option.multistr; }); + }, checks, function() { return state.jsonMode && state.option.multistr; }); } // If we get an EOF inside of an unclosed string, show an @@ -7793,6 +8335,8 @@ Lexer.prototype = { return { type: Token.StringLiteral, value: value, + startLine: startLine, + startChar: startChar, isUnclosed: true, quote: quote }; @@ -7833,6 +8377,8 @@ Lexer.prototype = { return { type: Token.StringLiteral, value: value, + startLine: startLine, + startChar: startChar, isUnclosed: false, quote: quote }; @@ -7848,7 +8394,7 @@ Lexer.prototype = { * rare edge cases where one JavaScript engine complains about * your regular expression while others don't. */ - scanRegExp: function () { + scanRegExp: function() { var index = 0; var length = this.input.length; var char = this.peek(); @@ -7859,7 +8405,7 @@ Lexer.prototype = { var isCharSet = false; var terminated; - var scanUnexpectedChars = function () { + var scanUnexpectedChars = function() { // Unexpected control character if (char < " ") { malformed = true; @@ -8010,7 +8556,7 @@ Lexer.prototype = { * can be mistakenly typed on OS X with option-space. Non UTF-8 web * pages with non-breaking pages produce syntax errors. */ - scanNonBreakingSpaces: function () { + scanNonBreakingSpaces: function() { return state.option.nonbsp ? this.input.search(/(\u00A0)/) : -1; }, @@ -8018,7 +8564,7 @@ Lexer.prototype = { /* * Scan for characters that get silently deleted by one or more browsers. */ - scanUnsafeChars: function () { + scanUnsafeChars: function() { return this.input.search(reg.unsafeChars); }, @@ -8026,7 +8572,7 @@ Lexer.prototype = { * Produce the next raw token or return 'null' if no tokens can be matched. * This method skips over all space characters. */ - next: function (checks) { + next: function(checks) { this.from = this.char; // Move to the next non-space character. @@ -8074,7 +8620,7 @@ Lexer.prototype = { * Switch to the next line and reset all char pointers. Once * switched, this method also checks for other minor warnings. */ - nextLine: function () { + nextLine: function() { var char; if (this.line >= this.getLines().length) { @@ -8088,14 +8634,14 @@ Lexer.prototype = { var inputTrimmed = this.input.trim(); - var startsWith = function () { - return _.some(arguments, function (prefix) { + var startsWith = function() { + return _.some(arguments, function(prefix) { return inputTrimmed.indexOf(prefix) === 0; }); }; - var endsWith = function () { - return _.some(arguments, function (suffix) { + var endsWith = function() { + return _.some(arguments, function(suffix) { return inputTrimmed.indexOf(suffix, inputTrimmed.length - suffix.length) !== -1; }); }; @@ -8142,7 +8688,7 @@ Lexer.prototype = { * This is simply a synonym for nextLine() method with a friendlier * public name. */ - start: function () { + start: function() { this.nextLine(); }, @@ -8150,7 +8696,7 @@ Lexer.prototype = { * Produce the next token. This function is called by advance() to get * the next token. It returns a token in a JSLint-compatible format. */ - token: function () { + token: function() { /*jshint loopfunc:true */ var checks = asyncTrigger(); var token; @@ -8185,7 +8731,7 @@ Lexer.prototype = { } // Produce a token object. - var create = function (type, value, isProperty, token) { + var create = function(type, value, isProperty, token) { /*jshint validthis:true */ var obj; @@ -8237,6 +8783,21 @@ Lexer.prototype = { obj.character = this.char; obj.from = this.from; if (obj.identifier && token) obj.raw_text = token.text || token.value; + if (token && token.startLine && token.startLine !== this.line) { + obj.startLine = token.startLine; + } + if (token && token.context) { + // Context of current token + obj.context = token.context; + } + if (token && token.depth) { + // Nested template depth + obj.depth = token.depth; + } + if (token && token.isUnclosed) { + // Mark token as unclosed string / template literal + obj.isUnclosed = token.isUnclosed; + } if (isProperty && obj.identifier) { obj.isProperty = isProperty; @@ -8276,38 +8837,57 @@ Lexer.prototype = { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value, quote: token.quote - }, checks, function () { return true; }); + }, checks, function() { return true; }); - return create("(string)", token.value); + return create("(string)", token.value, null, token); case Token.TemplateHead: this.trigger("TemplateHead", { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value }); - return create("(template)", token.value); + return create("(template)", token.value, null, token); case Token.TemplateMiddle: this.trigger("TemplateMiddle", { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value }); - return create("(template middle)", token.value); + return create("(template middle)", token.value, null, token); case Token.TemplateTail: this.trigger("TemplateTail", { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value }); - return create("(template tail)", token.value); + return create("(template tail)", token.value, null, token); + + case Token.NoSubstTemplate: + this.trigger("NoSubstTemplate", { + line: this.line, + char: this.char, + from: this.from, + startLine: token.startLine, + startChar: token.startChar, + value: token.value + }); + return create("(no subst template)", token.value, null, token); case Token.Identifier: this.trigger("Identifier", { @@ -8340,13 +8920,13 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "0x-" ] - }, checks, function () { return token.base === 16 && state.jsonMode; }); + }, checks, function() { return token.base === 16 && state.jsonMode; }); this.triggerAsync("warning", { code: "W115", line: this.line, character: this.char - }, checks, function () { + }, checks, function() { return state.directive["use strict"] && token.base === 8 && token.isLegacy; }); @@ -8393,6 +8973,7 @@ Lexer.prototype = { }; exports.Lexer = Lexer; +exports.Context = Context; }, {"../data/ascii-identifier-data.js":1,"./reg.js":8,"./state.js":9,"events":12,"underscore":2}], @@ -8599,9 +9180,13 @@ var warnings = { W123: "'{a}' is already defined in outer scope.", W124: "A generator function shall contain a yield statement.", W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp", - W126: "Grouping operator is unnecessary for lone expressions.", + W126: "Unnecessary grouping operator.", W127: "Unexpected use of a comma operator.", - W128: "Empty array elements require elision=true." + W128: "Empty array elements require elision=true.", + W129: "'{a}' is defined in a future version of JavaScript. Use a " + + "different variable name to avoid migration issues.", + W130: "Invalid element after rest element.", + W131: "Invalid parameter after rest parameter." }; var info = { @@ -8614,15 +9199,15 @@ exports.errors = {}; exports.warnings = {}; exports.info = {}; -_.each(errors, function (desc, code) { +_.each(errors, function(desc, code) { exports.errors[code] = { code: code, desc: desc }; }); -_.each(warnings, function (desc, code) { +_.each(warnings, function(desc, code) { exports.warnings[code] = { code: code, desc: desc }; }); -_.each(info, function (desc, code) { +_.each(info, function(desc, code) { exports.info[code] = { code: code, desc: desc }; }); @@ -8734,6 +9319,11 @@ exports.bool = { /** * This option allows you to force all variable names to use either * camelCase style or UPPER_CASE with underscores. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ camelcase : true, @@ -8765,6 +9355,14 @@ exports.bool = { */ eqeqeq : true, + /** + * This option enables warnings about the use of identifiers which are + * defined in future versions of JavaScript. Although overwriting them has + * no effect in contexts where they are not implemented, this practice can + * cause issues when migrating codebases to newer versions of the language. + */ + futurehostile: true, + /** * This option suppresses warnings about invalid `typeof` operator values. * This operator has only [a limited set of possible return @@ -8852,6 +9450,11 @@ exports.bool = { * wrapping them in parentheses. Wrapping parentheses assists readers of * your code in understanding that the expression is the result of a * function, and not the function itself. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ immed : true, @@ -8874,6 +9477,11 @@ exports.bool = { * important because when the function that was intended to be used with * `new` is used without it, `this` will point to the global object instead * of a new object. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ newcap : true, @@ -8898,6 +9506,11 @@ exports.bool = { * originally warning for all empty blocks and we simply made it optional. * There were no studies reporting that empty blocks in JavaScript break * your code in any way. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ noempty : true, @@ -8938,19 +9551,23 @@ exports.bool = { undef : true, /** - * This option prohibits the use of the grouping operator for - * single-expression statements. This unecessary usage commonly reflects - * a misunderstanding of unary operators, for example: + * This option prohibits the use of the grouping operator when it is not + * strictly required. Such usage commonly reflects a misunderstanding of + * unary operators, for example: * * // jshint singleGroups: true * - * delete(obj.attr); // Warning: Grouping operator is unnecessary for lone expressions. + * delete(obj.attr); // Warning: Unnecessary grouping operator. */ singleGroups: false, /** * This option is a short hand for the most strict JSHint configuration. It * enables all enforcing options and disables all relaxing options. + * + * @deprecated The option automatically opts users in to new features which + * can lead to unexpected warnings/errors in when upgrading + * between minor versions of JSHint. */ enforceall : false }, @@ -8992,6 +9609,11 @@ exports.bool = { * * text = "Hello\ * World"; // Warning, there is a space after \ + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ multistr : true, @@ -9069,6 +9691,11 @@ exports.bool = { /** * This option suppresses warnings about using `[]` notation when it can be * expressed in dot notation: `person['name']` vs. `person.name`. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ sub : true, @@ -9090,6 +9717,11 @@ exports.bool = { * This option suppresses most of the warnings about possibly unsafe line * breakings in your code. It doesn't suppress warnings about comma-first * coding style. To suppress those you have to use `laxcomma` (see below). + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ laxbreak : true, @@ -9101,6 +9733,11 @@ exports.bool = { * , handle: 'valueof' * , role: 'SW Engineer' * }; + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ laxcomma : true, @@ -9374,11 +10011,19 @@ exports.val = { /** * This option lets you set the maximum length of a line. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. If + * you would like to enforce rules relating to code style, check + * out [the JSCS project](https://github.com/jscs-dev/node-jscs). */ maxlen : false, /** * This option sets a specific tab width for your code. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. If + * you would like to enforce rules relating to code style, check + * out [the JSCS project](https://github.com/jscs-dev/node-jscs). */ indent : false, @@ -9412,6 +10057,10 @@ exports.val = { * one particular style but want some consistency, `"single"` if you want to * allow only single quotes and `"double"` if you want to allow only double * quotes. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. If + * you would like to enforce rules relating to code style, check + * out [the JSCS project](https://github.com/jscs-dev/node-jscs). */ quotmark : false, @@ -9642,7 +10291,7 @@ var NameStack = _dereq_("./name-stack.js"); var state = { syntax: {}, - reset: function () { + reset: function() { this.tokens = { prev: null, next: null, @@ -9673,7 +10322,7 @@ exports.state = state; 10:[function(_dereq_,module,exports){ "use strict"; -exports.register = function (linter) { +exports.register = function(linter) { // Check for properties named __proto__. This special property was // deprecated and then re-introduced for ES6. @@ -9729,18 +10378,12 @@ exports.register = function (linter) { linter.on("String", function style_scanQuotes(data) { var quotmark = linter.getOption("quotmark"); - var esnext = linter.getOption("esnext"); var code; if (!quotmark) { return; } - // If quotmark is enabled, return if this is a template literal. - if (esnext && data.quote === "`") { - return; - } - // If quotmark is set to 'single' warn about all double-quotes. if (quotmark === "single" && data.quote !== "'") { @@ -9836,51 +10479,47 @@ exports.reservedVars = { }; exports.ecmaIdentifiers = { - Array : false, - Boolean : false, - Date : false, - decodeURI : false, - decodeURIComponent : false, - encodeURI : false, - encodeURIComponent : false, - Error : false, - "eval" : false, - EvalError : false, - Function : false, - hasOwnProperty : false, - isFinite : false, - isNaN : false, - JSON : false, - Map : false, - Math : false, - Number : false, - Object : false, - Proxy : false, - Promise : false, - parseInt : false, - parseFloat : false, - RangeError : false, - ReferenceError : false, - RegExp : false, - Set : false, - String : false, - SyntaxError : false, - TypeError : false, - URIError : false, - WeakMap : false, - WeakSet : false -}; - -exports.newEcmaIdentifiers = { - Set : false, - Map : false, - WeakMap : false, - WeakSet : false, - Proxy : false, - Promise : false, - Reflect : false, - Symbol : false, - System : false + 3: { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + "eval" : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + Math : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false + }, + 5: { + JSON : false + }, + 6: { + Map : false, + Promise : false, + Proxy : false, + Reflect : false, + Set : false, + Symbol : false, + WeakMap : false, + WeakSet : false + } }; // Global variables commonly provided by a web browser environment. @@ -9902,11 +10541,13 @@ exports.browser = { clearTimeout : false, close : false, closed : false, + Comment : false, CustomEvent : false, DOMParser : false, defaultStatus : false, Document : false, document : false, + DocumentFragment : false, Element : false, ElementTimeControl : false, Event : false, @@ -9966,6 +10607,7 @@ exports.browser = { HTMLTableElement : false, HTMLTableRowElement : false, HTMLTableSectionElement: false, + HTMLTemplateElement : false, HTMLTextAreaElement : false, HTMLTitleElement : false, HTMLUListElement : false, @@ -9988,6 +10630,7 @@ exports.browser = { Node : false, NodeFilter : false, NodeList : false, + Notification : false, navigator : false, onbeforeunload : true, onblur : true, @@ -10002,6 +10645,7 @@ exports.browser = { Option : false, parent : false, print : false, + Range : false, requestAnimationFrame : false, removeEventListener : false, resizeBy : false, @@ -10170,6 +10814,7 @@ exports.browser = { SVGViewElement : false, SVGViewSpec : false, SVGZoomAndPan : false, + Text : false, TextDecoder : false, TextEncoder : false, TimeEvent : false, @@ -10495,7 +11140,11 @@ exports.yui = { exports.mocha = { // BDD describe : false, + xdescribe : false, it : false, + xit : false, + context : false, + xcontext : false, before : false, after : false, beforeEach : false, 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/rust_highlight_rules.js b/lib/ace/mode/rust_highlight_rules.js index d1290fa3..056e7a55 100644 --- a/lib/ace/mode/rust_highlight_rules.js +++ b/lib/ace/mode/rust_highlight_rules.js @@ -36,6 +36,7 @@ define(function(require, exports, module) { var oop = require("../lib/oop"); var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; +var stringEscape = /\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\})/.source; var RustHighlightRules = function() { // regexp must not have capturing parentheses. Use (?:) instead. // regexps are ordered -> the first match is used @@ -44,13 +45,7 @@ var RustHighlightRules = function() { [ { token: 'variable.other.source.rust', regex: '\'[a-zA-Z_][a-zA-Z0-9_]*[^\\\']' }, { token: 'string.quoted.single.source.rust', - regex: '\'', - push: - [ { token: 'string.quoted.single.source.rust', - regex: '\'', - next: 'pop' }, - { include: '#rust_escaped_character' }, - { defaultToken: 'string.quoted.single.source.rust' } ] }, + regex: "'(?:[^'\\\\]|" + stringEscape + ")'" }, { stateName: "bracketedComment", onMatch : function(value, currentState, stack){ @@ -86,14 +81,14 @@ var RustHighlightRules = function() { [ { token: 'string.quoted.double.source.rust', regex: '"', next: 'pop' }, - { include: '#rust_escaped_character' }, + { token: 'constant.character.escape.source.rust', + regex: stringEscape }, { defaultToken: 'string.quoted.double.source.rust' } ] }, - { token: [ 'keyword.source.rust', 'meta.function.source.rust', - 'entity.name.function.source.rust', 'meta.function.source.rust' ], - regex: '\\b(fn)(\\s+)([a-zA-Z_][a-zA-Z0-9_][\\w\\:,+ \\\'<>]*)(\\s*\\()' }, + { token: [ 'keyword.source.rust', 'text', 'entity.name.function.source.rust' ], + regex: '\\b(fn)(\\s+)([a-zA-Z_][a-zA-Z0-9_]*)' }, { token: 'support.constant', regex: '\\b[a-zA-Z_][\\w\\d]*::' }, { token: 'keyword.source.rust', - regex: '\\b(?:as|assert|break|claim|const|copy|Copy|do|drop|else|extern|fail|for|if|impl|in|let|log|loop|match|mod|module|move|mut|Owned|priv|pub|pure|ref|return|unchecked|unsafe|use|while|mod|Send|static|trait|class|struct|enum|type)\\b' }, + regex: '\\b(?:as|assert|break|claim|const|do|drop|else|extern|fail|for|if|impl|in|let|log|loop|match|mod|module|move|mut|Owned|priv|pub|pure|ref|return|unchecked|unsafe|use|while|mod|Send|static|trait|class|struct|enum|type)\\b' }, { token: 'storage.type.source.rust', regex: '\\b(?:Self|m32|m64|m128|f80|f16|f128|int|uint|isize|usize|float|char|bool|u8|u16|u32|u64|f32|f64|i8|i16|i32|i64|str|option|either|c_float|c_double|c_void|FILE|fpos_t|DIR|dirent|c_char|c_schar|c_uchar|c_short|c_ushort|c_int|c_uint|c_long|c_ulong|size_t|ptrdiff_t|clock_t|time_t|c_longlong|c_ulonglong|intptr_t|uintptr_t|off_t|dev_t|ino_t|pid_t|mode_t|ssize_t)\\b' }, { token: 'variable.language.source.rust', regex: '\\bself\\b' }, @@ -137,10 +132,7 @@ var RustHighlightRules = function() { { token: 'comment.end.block.source.rust', regex: '\\*/', next: 'pop' }, - { defaultToken: 'comment.block.source.rust' } ] } ], - '#rust_escaped_character': - [ { token: 'constant.character.escape.source.rust', - regex: '\\\\(?:x[\\da-fA-F]{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)' } ] } + { defaultToken: 'comment.block.source.rust' } ] } ] } this.normalizeRules(); }; 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/sql_highlight_rules.js b/lib/ace/mode/sql_highlight_rules.js index f9ff39d9..31e77c82 100644 --- a/lib/ace/mode/sql_highlight_rules.js +++ b/lib/ace/mode/sql_highlight_rules.js @@ -38,21 +38,28 @@ var SqlHighlightRules = function() { var keywords = ( "select|insert|update|delete|from|where|and|or|group|by|order|limit|offset|having|as|case|" + - "when|else|end|type|left|right|join|on|outer|desc|asc|union" + "when|else|end|type|left|right|join|on|outer|desc|asc|union|create|table|primary|key|if|" + + "foreign|not|references|default|null|inner|cross|natural|database|drop|grant" ); var builtinConstants = ( - "true|false|null" + "true|false" ); var builtinFunctions = ( "count|min|max|avg|sum|rank|now|coalesce" ); + var dataTypes = ( + "int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|" + + "money|real|number|integer" + ); + var keywordMapper = this.createKeywordMapper({ "support.function": builtinFunctions, "keyword": keywords, - "constant.language": builtinConstants + "constant.language": builtinConstants, + "storage.type": dataTypes }, "identifier", true); this.$rules = { diff --git a/lib/ace/mode/sqlserver.js b/lib/ace/mode/sqlserver.js new file mode 100644 index 00000000..5f24730c --- /dev/null +++ b/lib/ace/mode/sqlserver.js @@ -0,0 +1,62 @@ +/* ***** 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 SqlServerHighlightRules = require("./sqlserver_highlight_rules").SqlHighlightRules; +var Range = require("../range").Range; +var SqlServerFoldMode = require("./folding/sqlserver").FoldMode; + +var Mode = function() { + this.HighlightRules = SqlServerHighlightRules; + this.foldingRules = new SqlServerFoldMode(); +}; +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); + +exports.Mode = Mode; + +}); diff --git a/lib/ace/mode/sqlserver_highlight_rules.js b/lib/ace/mode/sqlserver_highlight_rules.js new file mode 100644 index 00000000..c914c0ae --- /dev/null +++ b/lib/ace/mode/sqlserver_highlight_rules.js @@ -0,0 +1,232 @@ +/* ***** 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 DocCommentHighlightRules = require("./doc_comment_highlight_rules").DocCommentHighlightRules; +var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; + +var SqlServerHighlightRules = function() { + /** + * Transact-SQL Syntax Conventions: https://msdn.microsoft.com/en-us/library/ms177563.aspx + * Goal: make this imitate SSMS (SQL Server Managment Studio) + */ + + // https://msdn.microsoft.com/en-us/library/ms189773.aspx + var logicalOperators = "ALL|AND|ANY|BETWEEN|EXISTS|IN|LIKE|NOT|OR|SOME"; + logicalOperators += "|NULL|IS|APPLY|INNER|OUTER|LEFT|RIGHT|JOIN|CROSS"; //SSMS colors these gray too + //note: manually removed LEFT and RIGHT from built in functions below to color it same way SSMS does + + + var builtinFunctions = ( + /* https://msdn.microsoft.com/en-us/library/ms187957.aspx */ + "OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|" + + /* https://msdn.microsoft.com/en-us/library/ms173454.aspx */ + "AVG|CHECKSUM_AGG|COUNT|COUNT_BIG|GROUPING|GROUPING_ID|MAX|MIN|STDEV|STDEVP|SUM|VAR|VARP|" + + /* https://msdn.microsoft.com/en-us/library/ms189798.aspx */ + "DENSE_RANK|NTILE|RANK|ROW_NUMBER" + + /* https://msdn.microsoft.com/en-us/library/ms173823.aspx */ + "@@DATEFIRST|@@DBTS|@@LANGID|@@LANGUAGE|@@LOCK_TIMEOUT|@@MAX_CONNECTIONS|@@MAX_PRECISION|@@NESTLEVEL|@@OPTIONS|@@REMSERVER|@@SERVERNAME|@@SERVICENAME|@@SPID|@@TEXTSIZE|@@VERSION|" + + /* https://msdn.microsoft.com/en-us/library/hh231076.aspx */ + "CAST|CONVERT|PARSE|TRY_CAST|TRY_CONVERT|TRY_PARSE" + + /* https://msdn.microsoft.com/en-us/library/ms186285.aspx */ + "@@CURSOR_ROWS|@@FETCH_STATUS|CURSOR_STATUS|" + + /* https://msdn.microsoft.com/en-us/library/ms186724.aspx */ + "@@DATEFIRST|@@LANGUAGE|CURRENT_TIMESTAMP|DATEADD|DATEDIFF|DATEFROMPARTS|DATENAME|DATEPART|DATETIME2FROMPARTS|DATETIMEFROMPARTS|DATETIMEOFFSETFROMPARTS|DAY|EOMONTH|GETDATE|GETUTCDATE|ISDATE|MONTH|SET DATEFIRST|SET DATEFORMAT|SET LANGUAGE|SMALLDATETIMEFROMPARTS|SP_HELPLANGUAGE|SWITCHOFFSET|SYSDATETIME|SYSDATETIMEOFFSET|SYSUTCDATETIME|TIMEFROMPARTS|TODATETIMEOFFSET|YEAR|" + + /* https://msdn.microsoft.com/en-us/library/hh213226.aspx */ + "CHOOSE|IIF|" + + /* https://msdn.microsoft.com/en-us/library/ms177516.aspx */ + "ABS|ACOS|ASIN|ATAN|ATN2|CEILING|COS|COT|DEGREES|EXP|FLOOR|LOG|LOG10|PI|POWER|RADIANS|RAND|ROUND|SIGN|SIN|SQRT|SQUARE|TAN|" + + /* https://msdn.microsoft.com/en-us/library/ms187812.aspx */ + "@@PROCID|APPLOCK_MODE|APPLOCK_TEST|APP_NAME|ASSEMBLYPROPERTY|COLUMNPROPERTY|COL_LENGTH|COL_NAME|DATABASEPROPERTYEX|DATABASE_PRINCIPAL_ID|DB_ID|DB_NAME|FILEGROUPPROPERTY|FILEGROUP_ID|FILEGROUP_NAME|FILEPROPERTY|FILE_ID|FILE_IDEX|FILE_NAME|FULLTEXTCATALOGPROPERTY|FULLTEXTSERVICEPROPERTY|INDEXKEY_PROPERTY|INDEXPROPERTY|INDEX_COL|OBJECTPROPERTY|OBJECTPROPERTYEX|OBJECT_DEFINITION|OBJECT_ID|OBJECT_NAME|OBJECT_SCHEMA_NAME|ORIGINAL_DB_NAME|PARSENAME|SCHEMA_ID|SCHEMA_NAME|SCOPE_IDENTITY|SERVERPROPERTY|STATS_DATE|TYPEPROPERTY|TYPE_ID|TYPE_NAME|" + + /* https://msdn.microsoft.com/en-us/library/ms186236.aspx */ + "CERTENCODED|CERTPRIVATEKEY|CURRENT_USER|DATABASE_PRINCIPAL_ID|HAS_PERMS_BY_NAME|IS_MEMBER|IS_ROLEMEMBER|IS_SRVROLEMEMBER|ORIGINAL_LOGIN|PERMISSIONS|PWDCOMPARE|PWDENCRYPT|SCHEMA_ID|SCHEMA_NAME|SESSION_USER|SUSER_ID|SUSER_NAME|SUSER_SID|SUSER_SNAME|SYS.FN_BUILTIN_PERMISSIONS|SYS.FN_GET_AUDIT_FILE|SYS.FN_MY_PERMISSIONS|SYSTEM_USER|USER_ID|USER_NAME|" + + /* https://msdn.microsoft.com/en-us/library/ms181984.aspx */ + "ASCII|CHAR|CHARINDEX|CONCAT|DIFFERENCE|FORMAT|LEN|LOWER|LTRIM|NCHAR|PATINDEX|QUOTENAME|REPLACE|REPLICATE|REVERSE|RTRIM|SOUNDEX|SPACE|STR|STUFF|SUBSTRING|UNICODE|UPPER|" + + /* https://msdn.microsoft.com/en-us/library/ms187786.aspx */ + "$PARTITION|@@ERROR|@@IDENTITY|@@PACK_RECEIVED|@@ROWCOUNT|@@TRANCOUNT|BINARY_CHECKSUM|CHECKSUM|CONNECTIONPROPERTY|CONTEXT_INFO|CURRENT_REQUEST_ID|ERROR_LINE|ERROR_MESSAGE|ERROR_NUMBER|ERROR_PROCEDURE|ERROR_SEVERITY|ERROR_STATE|FORMATMESSAGE|GETANSINULL|GET_FILESTREAM_TRANSACTION_CONTEXT|HOST_ID|HOST_NAME|ISNULL|ISNUMERIC|MIN_ACTIVE_ROWVERSION|NEWID|NEWSEQUENTIALID|ROWCOUNT_BIG|XACT_STATE|" + + /* 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|" + + /* 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"; + // https://msdn.microsoft.com/en-us/library/ms173815.aspx + keywords += "|LOOP|HASH|MERGE|REMOTE"; + // https://msdn.microsoft.com/en-us/library/ms175976.aspx + keywords += "|TRY|CATCH|THROW"; + // highlighted words in SSMS that I'm not even sure where they come from + keywords += "|TYPE"; + + + //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, + "support.function": builtinFunctions, + "support.storedprocedure": builtInStoredProcedures, + "keyword": keywords, + }, "identifier", true); + + + //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]); + } + + + this.$rules = { + start: [{ + token: "string.start", + regex: "'", + next: [{ + token: "constant.language.escape", + regex: /''/ + }, { + token: "string.end", + next: "start", + regex: "'" + }, { + defaultToken: "string" + }] + }, + DocCommentHighlightRules.getStartRule("doc-start"), { + token: "comment", + regex: "--.*$" + }, { + token: "comment", + start: "/\\*", + end: "\\*/" + }, { + 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 built in functions + }, { + token: "constant.class", + regex: "@@?[a-zA-Z_$][a-zA-Z0-9_$]*\\b" + }, { + //https://msdn.microsoft.com/en-us/library/ms174986.aspx + token: "keyword.operator", + regex: "\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|=|\\*" + }, { + token: "paren.lparen", + regex: "[\\(]" + }, { + token: "paren.rparen", + regex: "[\\)]" + }, { + token: "punctuation", + regex: ",|;" + }, { + token: "text", + regex: "\\s+" + }], + comment: [ + DocCommentHighlightRules.getTagRule(), { + token: "comment", + regex: "\\*\\/", + next: "no_regex" + }, { + defaultToken: "comment", + 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 + for (var i = 0; i < setStatements.length; i++) { + this.$rules.start.unshift({ + token: "set.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); + +exports.SqlHighlightRules = SqlServerHighlightRules; +}); 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/vbscript_highlight_rules.js b/lib/ace/mode/vbscript_highlight_rules.js index c706002d..ef829922 100644 --- a/lib/ace/mode/vbscript_highlight_rules.js +++ b/lib/ace/mode/vbscript_highlight_rules.js @@ -3,7 +3,7 @@ * * Copyright (c) 2012, 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 @@ -14,7 +14,7 @@ * * 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 @@ -85,29 +85,35 @@ var VBScriptHighlightRules = function() { { token: "punctuation.definition.comment.asp", regex: "'|REM", - next: "comment" + next: "comment", + caseInsensitive: true }, { token: [ "keyword.control.asp" ], - regex: "\\b(?:If|Then|Else|ElseIf|Else If|End If|While|Wend|For|To|Each|Case|Select|End Select|Return|Continue|Do|Until|Loop|Next|With|Exit Do|Exit For|Exit Function|Exit Property|Exit Sub|IIf)\\b" + regex: "\\b(?:If|Then|Else|ElseIf|Else If|End If|While|Wend|For|To|Each|Case|Select|End Select|Return|Continue|Do|Until|Loop|Next|With|Exit Do|Exit For|Exit Function|Exit Property|Exit Sub|IIf)\\b", + caseInsensitive: true }, { token: "keyword.operator.asp", - regex: "\\b(?:Mod|And|Not|Or|Xor|as)\\b" + regex: "\\b(?:Mod|And|Not|Or|Xor|as)\\b", + caseInsensitive: true }, { token: "storage.type.asp", - regex: "Dim|Call|Class|Const|Dim|Redim|Function|Sub|Private Sub|Public Sub|End sub|End Function|Set|Let|Get|New|Randomize|Option Explicit|On Error Resume Next|On Error GoTo" + regex: "Dim|Call|Class|Const|Dim|Redim|Function|Sub|Private Sub|Public Sub|End sub|End Function|Set|Let|Get|New|Randomize|Option Explicit|On Error Resume Next|On Error GoTo", + caseInsensitive: true }, { token: "storage.modifier.asp", - regex: "\\b(?:Private|Public|Default)\\b" + regex: "\\b(?:Private|Public|Default)\\b", + caseInsensitive: true }, { token: "constant.language.asp", - regex: "\\b(?:Empty|False|Nothing|Null|True)\\b" + regex: "\\b(?:Empty|False|Nothing|Null|True)\\b", + caseInsensitive: true }, { token: "punctuation.definition.string.begin.asp", @@ -122,23 +128,28 @@ var VBScriptHighlightRules = function() { }, { token: "support.class.asp", - regex: "\\b(?:Application|ObjectContext|Request|Response|Server|Session)\\b" + regex: "\\b(?:Application|ObjectContext|Request|Response|Server|Session)\\b", + caseInsensitive: true }, { token: "support.class.collection.asp", - regex: "\\b(?:Contents|StaticObjects|ClientCertificate|Cookies|Form|QueryString|ServerVariables)\\b" + regex: "\\b(?:Contents|StaticObjects|ClientCertificate|Cookies|Form|QueryString|ServerVariables)\\b", + caseInsensitive: true }, { token: "support.constant.asp", - regex: "\\b(?:TotalBytes|Buffer|CacheControl|Charset|ContentType|Expires|ExpiresAbsolute|IsClientConnected|PICS|Status|ScriptTimeout|CodePage|LCID|SessionID|Timeout)\\b" + regex: "\\b(?:TotalBytes|Buffer|CacheControl|Charset|ContentType|Expires|ExpiresAbsolute|IsClientConnected|PICS|Status|ScriptTimeout|CodePage|LCID|SessionID|Timeout)\\b", + caseInsensitive: true }, { token: "support.function.asp", - regex: "\\b(?:Lock|Unlock|SetAbort|SetComplete|BinaryRead|AddHeader|AppendToLog|BinaryWrite|Clear|End|Flush|Redirect|Write|CreateObject|HTMLEncode|MapPath|URLEncode|Abandon|Convert|Regex)\\b" + regex: "\\b(?:Lock|Unlock|SetAbort|SetComplete|BinaryRead|AddHeader|AppendToLog|BinaryWrite|Clear|End|Flush|Redirect|Write|CreateObject|HTMLEncode|MapPath|URLEncode|Abandon|Convert|Regex)\\b", + caseInsensitive: true }, { token: "support.function.event.asp", - regex: "\\b(?:Application_OnEnd|Application_OnStart|OnTransactionAbort|OnTransactionCommit|Session_OnEnd|Session_OnStart)\\b" + regex: "\\b(?:Application_OnEnd|Application_OnStart|OnTransactionAbort|OnTransactionCommit|Session_OnEnd|Session_OnStart)\\b", + caseInsensitive: true }, // { // token: [ @@ -148,7 +159,8 @@ var VBScriptHighlightRules = function() { // }, { token: "support.function.vb.asp", - regex: "\\b(?:Array|Add|Asc|Atn|CBool|CByte|CCur|CDate|CDbl|Chr|CInt|CLng|Conversions|Cos|CreateObject|CSng|CStr|Date|DateAdd|DateDiff|DatePart|DateSerial|DateValue|Day|Derived|Math|Escape|Eval|Exists|Exp|Filter|FormatCurrency|FormatDateTime|FormatNumber|FormatPercent|GetLocale|GetObject|GetRef|Hex|Hour|InputBox|InStr|InStrRev|Int|Fix|IsArray|IsDate|IsEmpty|IsNull|IsNumeric|IsObject|Item|Items|Join|Keys|LBound|LCase|Left|Len|LoadPicture|Log|LTrim|RTrim|Trim|Maths|Mid|Minute|Month|MonthName|MsgBox|Now|Oct|Remove|RemoveAll|Replace|RGB|Right|Rnd|Round|ScriptEngine|ScriptEngineBuildVersion|ScriptEngineMajorVersion|ScriptEngineMinorVersion|Second|SetLocale|Sgn|Sin|Space|Split|Sqr|StrComp|String|StrReverse|Tan|Time|Timer|TimeSerial|TimeValue|TypeName|UBound|UCase|Unescape|VarType|Weekday|WeekdayName|Year)\\b" + regex: "\\b(?:Array|Add|Asc|Atn|CBool|CByte|CCur|CDate|CDbl|Chr|CInt|CLng|Conversions|Cos|CreateObject|CSng|CStr|Date|DateAdd|DateDiff|DatePart|DateSerial|DateValue|Day|Derived|Math|Escape|Eval|Exists|Exp|Filter|FormatCurrency|FormatDateTime|FormatNumber|FormatPercent|GetLocale|GetObject|GetRef|Hex|Hour|InputBox|InStr|InStrRev|Int|Fix|IsArray|IsDate|IsEmpty|IsNull|IsNumeric|IsObject|Item|Items|Join|Keys|LBound|LCase|Left|Len|LoadPicture|Log|LTrim|RTrim|Trim|Maths|Mid|Minute|Month|MonthName|MsgBox|Now|Oct|Remove|RemoveAll|Replace|RGB|Right|Rnd|Round|ScriptEngine|ScriptEngineBuildVersion|ScriptEngineMajorVersion|ScriptEngineMinorVersion|Second|SetLocale|Sgn|Sin|Space|Split|Sqr|StrComp|String|StrReverse|Tan|Time|Timer|TimeSerial|TimeValue|TypeName|UBound|UCase|Unescape|VarType|Weekday|WeekdayName|Year)\\b", + caseInsensitive: true }, { token: [ @@ -158,7 +170,8 @@ var VBScriptHighlightRules = function() { }, { token: "support.type.vb.asp", - regex: "\\b(?:vbtrue|vbfalse|vbcr|vbcrlf|vbformfeed|vblf|vbnewline|vbnullchar|vbnullstring|int32|vbtab|vbverticaltab|vbbinarycompare|vbtextcomparevbsunday|vbmonday|vbtuesday|vbwednesday|vbthursday|vbfriday|vbsaturday|vbusesystemdayofweek|vbfirstjan1|vbfirstfourdays|vbfirstfullweek|vbgeneraldate|vblongdate|vbshortdate|vblongtime|vbshorttime|vbobjecterror|vbEmpty|vbNull|vbInteger|vbLong|vbSingle|vbDouble|vbCurrency|vbDate|vbString|vbObject|vbError|vbBoolean|vbVariant|vbDataObject|vbDecimal|vbByte|vbArray)\\b" + regex: "\\b(?:vbtrue|vbfalse|vbcr|vbcrlf|vbformfeed|vblf|vbnewline|vbnullchar|vbnullstring|int32|vbtab|vbverticaltab|vbbinarycompare|vbtextcomparevbsunday|vbmonday|vbtuesday|vbwednesday|vbthursday|vbfriday|vbsaturday|vbusesystemdayofweek|vbfirstjan1|vbfirstfourdays|vbfirstfullweek|vbgeneraldate|vblongdate|vbshortdate|vblongtime|vbshorttime|vbobjecterror|vbEmpty|vbNull|vbInteger|vbLong|vbSingle|vbDouble|vbCurrency|vbDate|vbString|vbObject|vbError|vbBoolean|vbVariant|vbDataObject|vbDecimal|vbByte|vbArray)\\b", + caseInsensitive: true }, { token: [ 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/mode/xml_highlight_rules.js b/lib/ace/mode/xml_highlight_rules.js index 91da0e70..8e39cae5 100644 --- a/lib/ace/mode/xml_highlight_rules.js +++ b/lib/ace/mode/xml_highlight_rules.js @@ -35,8 +35,10 @@ var oop = require("../lib/oop"); var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; var XmlHighlightRules = function(normalize) { - - var tagRegex = "[a-zA-Z][-_a-zA-Z0-9]*"; + // http://www.w3.org/TR/REC-xml/#NT-NameChar + // NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] + // NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] + var tagRegex = "[_:a-zA-Z\xc0-\uffff][-_:.a-zA-Z0-9\xc0-\uffff]*"; this.$rules = { start : [ diff --git a/lib/ace/mouse/multi_select_handler.js b/lib/ace/mouse/multi_select_handler.js index 9b52e237..649043fa 100644 --- a/lib/ace/mouse/multi_select_handler.js +++ b/lib/ace/mouse/multi_select_handler.js @@ -83,15 +83,15 @@ function onMouseDown(e) { var selectionMode; if (editor.$mouseHandler.$enableJumpToDef) { if (ctrl && alt || accel && alt) - selectionMode = "add"; - else if (alt) + selectionMode = shift ? "block" : "add"; + else if (alt && editor.$blockSelectEnabled) selectionMode = "block"; } else { if (accel && !alt) { selectionMode = "add"; if (!isMultiSelect && shift) return; - } else if (alt) { + } else if (alt && editor.$blockSelectEnabled) { selectionMode = "block"; } } @@ -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 0635afc2..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; @@ -924,7 +924,8 @@ function addAltCursorListeners(editor){ var el = editor.textInput.getElement(); var altCursor = false; event.addListener(el, "keydown", function(e) { - if (e.keyCode == 18 && !(e.ctrlKey || e.shiftKey || e.metaKey)) { + var altDown = e.keyCode == 18 && !(e.ctrlKey || e.shiftKey || e.metaKey); + if (editor.$blockSelectEnabled && altDown) { if (!altCursor) { editor.renderer.setMouseCursor("crosshair"); altCursor = true; @@ -962,6 +963,12 @@ require("./config").defineOptions(Editor.prototype, "editor", { } }, value: true + }, + enableBlockSelect: { + set: function(val) { + this.$blockSelectEnabled = val; + }, + value: true } }); 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/r.snippets b/lib/ace/snippets/r.snippets index cfe482bc..948542f5 100644 --- a/lib/ace/snippets/r.snippets +++ b/lib/ace/snippets/r.snippets @@ -47,7 +47,7 @@ snippet apply snippet lapply lapply(${1:list}, ${2:function}) snippet sapply - lapply(${1:list}, ${2:function}) + sapply(${1:list}, ${2:function}) snippet vapply vapply(${1:list}, ${2:function}, ${3:type}) snippet mapply diff --git a/lib/ace/snippets/sqlserver.js b/lib/ace/snippets/sqlserver.js new file mode 100644 index 00000000..f84694dc --- /dev/null +++ b/lib/ace/snippets/sqlserver.js @@ -0,0 +1,7 @@ +define(function(require, exports, module) { +"use strict"; + +exports.snippetText = require("../requirejs/text!./sqlserver.snippets"); +exports.scope = "sqlserver"; + +}); diff --git a/lib/ace/snippets/sqlserver.snippets b/lib/ace/snippets/sqlserver.snippets new file mode 100644 index 00000000..403bd6bc --- /dev/null +++ b/lib/ace/snippets/sqlserver.snippets @@ -0,0 +1,70 @@ +# ISNULL +snippet isnull + ISNULL(${1:check_expression}, ${2:replacement_value}) +# FORMAT +snippet format + FORMAT(${1:value}, ${2:format}) +# CAST +snippet cast + CAST(${1:expression} AS ${2:data_type}) +# CONVERT +snippet convert + CONVERT(${1:data_type}, ${2:expression}) +# DATEPART +snippet datepart + DATEPART(${1:datepart}, ${2:date}) +# DATEDIFF +snippet datediff + DATEDIFF(${1:datepart}, ${2:startdate}, ${3:enddate}) +# DATEADD +snippet dateadd + DATEADD(${1:datepart}, ${2:number}, ${3:date}) +# 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 + -- ============================================= + -- Author: ${1:Author} + -- Create date: ${2:Date} + -- Description: ${3:Description} + -- ============================================= + CREATE PROCEDURE ${4:Procedure_Name} + ${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 +snippet createfn + -- ============================================= + -- Author: ${1:Author} + -- Create date: ${2:Date} + -- Description: ${3:Description} + -- ============================================= + CREATE FUNCTION ${4:Scalar_Function_Name} + -- Add the parameters for the function here + RETURNS ${5:Function_Data_Type} + AS + 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 8d23bcbe..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 { @@ -48,7 +47,7 @@ } .ace-clouds-midnight .ace_invisible { - color: #BFBFBF + color: #666 } .ace-clouds-midnight .ace_keyword, diff --git a/lib/ace/theme/cobalt.css b/lib/ace/theme/cobalt.css index 16568fc3..b883ba97 100644 --- a/lib/ace/theme/cobalt.css +++ b/lib/ace/theme/cobalt.css @@ -5,7 +5,7 @@ .ace-cobalt .ace_print-margin { width: 1px; - background: #011e3a + background: #555555 } .ace-cobalt { @@ -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 { @@ -131,4 +130,4 @@ .ace-cobalt .ace_indent-guide { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHCLSvkPAAP3AgSDTRd4AAAAAElFTkSuQmCC) right repeat-y -} \ No newline at end of file +} 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/github.css b/lib/ace/theme/github.css index f496f6ee..213d3fd3 100644 --- a/lib/ace/theme/github.css +++ b/lib/ace/theme/github.css @@ -68,9 +68,12 @@ color: black; } -.ace-github .ace_marker-layer .ace_active-line { +.ace-github.ace_focus .ace_marker-layer .ace_active-line { background: rgb(255, 255, 204); } +.ace-github .ace_marker-layer .ace_active-line { + background: rgb(245, 245, 245); +} .ace-github .ace_marker-layer .ace_selection { background: rgb(181, 213, 255); @@ -106,7 +109,10 @@ .ace-github .ace_marker-layer .ace_selected-word { background: rgb(250, 250, 255); border: 1px solid rgb(200, 200, 250); +} +.ace-github .ace_invisible { + color: #BFBFBF } .ace-github .ace_print-margin { 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/iplastic.css b/lib/ace/theme/iplastic.css new file mode 100644 index 00000000..b07dc8f9 --- /dev/null +++ b/lib/ace/theme/iplastic.css @@ -0,0 +1,140 @@ +.ace-iplastic .ace_gutter { + background: #dddddd; + color: #666666 +} + +.ace-iplastic .ace_print-margin { + width: 1px; + background: #bbbbbb +} + +.ace-iplastic { + background-color: #eeeeee; + color: #333333 +} + +.ace-iplastic .ace_cursor { + color: #333 +} + +.ace-iplastic .ace_marker-layer .ace_selection { + background: #BAD6FD; +} + +.ace-iplastic.ace_multiselect .ace_selection.ace_start { + border-radius: 4px +} + +.ace-iplastic .ace_marker-layer .ace_step { + background: #444444 +} + +.ace-iplastic .ace_marker-layer .ace_bracket { + margin: -1px 0 0 -1px; + border: 1px solid #49483E; + background: #FFF799 +} + +.ace-iplastic .ace_marker-layer .ace_active-line { + background: #e5e5e5 +} + +.ace-iplastic .ace_gutter-active-line { + background-color: #eeeeee +} + +.ace-iplastic .ace_marker-layer .ace_selected-word { + border: 1px solid #555555; + border-radius:4px +} + +.ace-iplastic .ace_invisible { + color: #999999 +} + +.ace-iplastic .ace_entity.ace_name.ace_tag, +.ace-iplastic .ace_keyword, +.ace-iplastic .ace_meta.ace_tag, +.ace-iplastic .ace_storage { + color: #0000FF +} + +.ace-iplastic .ace_punctuation, +.ace-iplastic .ace_punctuation.ace_tag { + color: #000 +} + +.ace-iplastic .ace_constant { + color: #333333; + font-weight: 700 +} + +.ace-iplastic .ace_constant.ace_character, +.ace-iplastic .ace_constant.ace_language, +.ace-iplastic .ace_constant.ace_numeric, +.ace-iplastic .ace_constant.ace_other { + color: #0066FF; + font-weight: 700 +} + +.ace-iplastic .ace_constant.ace_numeric{ + font-weight: 100 +} + +.ace-iplastic .ace_invalid { + color: #F8F8F0; + background-color: #F92672 +} + +.ace-iplastic .ace_invalid.ace_deprecated { + color: #F8F8F0; + background-color: #AE81FF +} + +.ace-iplastic .ace_support.ace_constant, +.ace-iplastic .ace_support.ace_function { + color: #333333; + font-weight: 700 +} + +.ace-iplastic .ace_fold { + background-color: #464646; + border-color: #F8F8F2 +} + +.ace-iplastic .ace_storage.ace_type, +.ace-iplastic .ace_support.ace_class, +.ace-iplastic .ace_support.ace_type { + color: #3333fc; + font-weight: 700 +} + +.ace-iplastic .ace_entity.ace_name.ace_function, +.ace-iplastic .ace_entity.ace_other, +.ace-iplastic .ace_entity.ace_other.ace_attribute-name, +.ace-iplastic .ace_variable { + color: #3366cc; + font-style: italic +} + +.ace-iplastic .ace_variable.ace_parameter { + font-style: italic; + color: #2469E0 +} + +.ace-iplastic .ace_string { + color: #a55f03 +} + +.ace-iplastic .ace_comment { + color: #777777; + font-style: italic +} + +.ace-iplastic .ace_fold-widget { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==); +} + +.ace-iplastic .ace_indent-guide { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAABlJREFUeNpi+P//PwMzMzPzfwAAAAD//wMAGRsECSML/RIAAAAASUVORK5CYII=) right repeat-y +} \ No newline at end of file diff --git a/lib/ace/theme/iplastic.js b/lib/ace/theme/iplastic.js new file mode 100644 index 00000000..ff784196 --- /dev/null +++ b/lib/ace/theme/iplastic.js @@ -0,0 +1,40 @@ + +/* ***** 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 = false; +exports.cssClass = "ace-iplastic"; +exports.cssText = require("../requirejs/text!./iplastic.css"); + +var dom = require("../lib/dom"); +dom.importCssString(exports.cssText, exports.cssClass); +}); diff --git a/lib/ace/theme/katzenmilch.css b/lib/ace/theme/katzenmilch.css index e2526b93..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 { @@ -55,6 +54,10 @@ border: 1px solid rgba(100, 5, 208, 0.27) } +.ace-katzenmilch .ace_invisible { + color: #BFBFBF +} + .ace-katzenmilch .ace_fold { background-color: rgba(2, 95, 73, 0.97); border-color: rgba(15, 0, 9, 1.0) 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/kuroir.css b/lib/ace/theme/kuroir.css index 9c0ba6a8..5e987abc 100644 --- a/lib/ace/theme/kuroir.css +++ b/lib/ace/theme/kuroir.css @@ -49,6 +49,10 @@ border: 1px solid rgba(245, 170, 0, 0.57); } +.ace-kuroir .ace_invisible { + color: #BFBFBF +} + .ace-kuroir .ace_fold { border-color: #363636; } 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/sqlserver.css b/lib/ace/theme/sqlserver.css new file mode 100644 index 00000000..ff6d1e1c --- /dev/null +++ b/lib/ace/theme/sqlserver.css @@ -0,0 +1,171 @@ +.ace_line { + font-family: Consolas; +} + +.ace-sqlserver .ace_gutter { + background: #ebebeb; + color: #333; + overflow: hidden; +} + +.ace-sqlserver .ace_print-margin { + width: 1px; + background: #e8e8e8; +} + +.ace-sqlserver { + background-color: #FFFFFF; + color: black; +} + +.ace-sqlserver .ace_identifier { + color: black; +} + +.ace-sqlserver .ace_keyword { + color: #0000FF; +} + +.ace-sqlserver .ace_numeric { + color: black; +} + +.ace-sqlserver .ace_storage { + color: #11B7BE; +} + +.ace-sqlserver .ace_keyword.ace_operator, +.ace-sqlserver .ace_lparen, +.ace-sqlserver .ace_rparen, +.ace-sqlserver .ace_punctuation { + color: #808080; +} + +.ace-sqlserver .ace_set.ace_statement { + color: #0000FF; + text-decoration: underline; +} + +.ace-sqlserver .ace_cursor { + color: black; +} + +.ace-sqlserver .ace_invisible { + color: rgb(191, 191, 191); +} + +.ace-sqlserver .ace_constant.ace_buildin { + color: rgb(88, 72, 246); +} + +.ace-sqlserver .ace_constant.ace_language { + color: #979797; +} + +.ace-sqlserver .ace_constant.ace_library { + color: rgb(6, 150, 14); +} + +.ace-sqlserver .ace_invalid { + background-color: rgb(153, 0, 0); + color: white; +} + +.ace-sqlserver .ace_support.ace_function { + color: #FF00FF; +} + +.ace-sqlserver .ace_support.ace_constant { + color: rgb(6, 150, 14); +} + +.ace-sqlserver .ace_class { + color: #008080; +} + +.ace-sqlserver .ace_support.ace_other { + color: #6D79DE; +} + +.ace-sqlserver .ace_variable.ace_parameter { + font-style: italic; + color: #FD971F; +} + +.ace-sqlserver .ace_comment { + color: #008000; +} + +.ace-sqlserver .ace_constant.ace_numeric { + color: black; +} + +.ace-sqlserver .ace_variable { + color: rgb(49, 132, 149); +} + +.ace-sqlserver .ace_xml-pe { + color: rgb(104, 104, 91); +} + +.ace-sqlserver .ace_support.ace_storedprocedure { + color: #800000; +} + +.ace-sqlserver .ace_heading { + color: rgb(12, 7, 255); +} + +.ace-sqlserver .ace_list { + color: rgb(185, 6, 144); +} + +.ace-sqlserver .ace_marker-layer .ace_selection { + background: rgb(181, 213, 255); +} + +.ace-sqlserver .ace_marker-layer .ace_step { + background: rgb(252, 255, 0); +} + +.ace-sqlserver .ace_marker-layer .ace_stack { + background: rgb(164, 229, 101); +} + +.ace-sqlserver .ace_marker-layer .ace_bracket { + margin: -1px 0 0 -1px; + border: 1px solid rgb(192, 192, 192); +} + +.ace-sqlserver .ace_marker-layer .ace_active-line { + background: rgba(0, 0, 0, 0.07); +} + +.ace-sqlserver .ace_gutter-active-line { + background-color: #dcdcdc; +} + +.ace-sqlserver .ace_marker-layer .ace_selected-word { + background: rgb(250, 250, 255); + border: 1px solid rgb(200, 200, 250); +} + +.ace-sqlserver .ace_meta.ace_tag { + color: #0000FF; +} + +.ace-sqlserver .ace_string.ace_regex { + color: #FF0000; +} + +.ace-sqlserver .ace_string { + color: #FF0000; +} + +.ace-sqlserver .ace_entity.ace_other.ace_attribute-name { + color: #994409; +} + +.ace-sqlserver .ace_indent-guide { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y; +} diff --git a/lib/ace/theme/sqlserver.js b/lib/ace/theme/sqlserver.js new file mode 100644 index 00000000..ab657483 --- /dev/null +++ b/lib/ace/theme/sqlserver.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 = false; +exports.cssClass = "ace-sqlserver"; +exports.cssText = require("../requirejs/text!./sqlserver.css"); + +var dom = require("../lib/dom"); +dom.importCssString(exports.cssText, exports.cssClass); +}); 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 484ebb5e..45226a1d 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -46,7 +46,7 @@ var FontMetrics = require("./layer/font_metrics").FontMetrics; var EventEmitter = require("./lib/event_emitter").EventEmitter; var editorCss = require("./requirejs/text!./css/editor.css"); -dom.importCssString(editorCss, "ace_editor"); +dom.importCssString(editorCss, "ace_editor.css"); /** * The class that is responsible for drawing everything you see on the screen! @@ -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/package.json b/package.json index a4a9f50b..41c40fce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ace", "description": "Ajax.org Code Editor is a full featured source code highlighting editor that powers the Cloud9 IDE", - "version": "1.1.8", + "version": "1.1.9", "homepage": "http://github.com/ajaxorg/ace", "engines": {"node": ">= 0.6.0"}, "author": "Fabian Jakobs ", 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 e72194d7..3d57b6e0 100644 --- a/tool/lib.js +++ b/tool/lib.js @@ -1,6 +1,7 @@ var plist = require("plist"); var util = require("util"); var url = require("url"); +var cson = require("cson"); var https = require("https"); var http = require("http"); @@ -12,17 +13,120 @@ exports.parsePlist = function(xmlOrJSON, callback) { json = result[0]; }); } else { - xmlOrJSON = xmlOrJSON.replace(/^\s*\/\/.*/gm, ""); - json = JSON.parse(xmlOrJSON) + try { + 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); + } } callback && callback(json); 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/package.json b/tool/package.json index 2d33713a..974673c6 100644 --- a/tool/package.json +++ b/tool/package.json @@ -1,9 +1,10 @@ { - "name": "ace-tools", - "version": "0.1.0", - "dependencies": { - "plist": "", - "css-parse": "1.0.3", - "css-stringify": "1.0.3" - } + "name": "ace-tools", + "version": "0.1.0", + "dependencies": { + "cson": "^3.0.1", + "css-parse": "1.0.3", + "css-stringify": "1.0.3", + "plist": "" + } } diff --git a/tool/release.sh b/tool/release.sh index 6dc830e1..52376914 100644 --- a/tool/release.sh +++ b/tool/release.sh @@ -9,11 +9,16 @@ pause() { done } -read -p "enter version number for the build " VERSION_NUM + cd `dirname $0`/.. SOURCE=`pwd` +CUR_VERSION=`node -e 'console.log(require("./package.json").version)'` +git --no-pager log --first-parent --oneline v$CUR_VERSION..master +echo "current version is $CUR_VERSION" +read -p "enter version number for the build " VERSION_NUM + node -e " var fs = require('fs'); var version = '$VERSION_NUM'; 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) {