Remove livepreview

This commit is contained in:
Dawa Ometto
2017-04-12 22:20:59 +02:00
parent f9b8b4e8d3
commit 5aa5bccda3
953 changed files with 0 additions and 272317 deletions
-6
View File
@@ -105,12 +105,6 @@ MSG
opts.on("--no-edit", "Disable the feature of editing pages.") do
wiki_options[:allow_editing] = false
end
opts.on("--live-preview", "Enable the live preview feature in page editor.") do
wiki_options[:live_preview] = true
end
opts.on("--no-live-preview", "Disable the live preview feature in page editor.") do
wiki_options[:live_preview] = false
end
opts.on("--allow-uploads [MODE]", [:dir, :page], "Enable file uploads.",
"If set to 'dir', Gollum will store all uploads in the '<git-repo>/uploads/' directory.",
"If set to 'page', Gollum will store each upload at the currently edited page.") do |mode|
-14
View File
@@ -160,18 +160,10 @@ module Precious
wiki = wikip.wiki
@allow_uploads = wiki.allow_uploads
if page = wikip.page
if wiki.live_preview && page.format.to_s.include?('markdown') && supported_useragent?(request.user_agent)
live_preview_url = '/livepreview/?page=' + encodeURIComponent(@name)
if @path
live_preview_url << '&path=' + encodeURIComponent(@path)
end
redirect to(live_preview_url)
else
@page = page
@page.version = wiki.repo.log(wiki.ref, @page.path).first
@content = page.text_data
mustache :edit
end
else
redirect to("/create/#{encodeURIComponent(@name)}")
end
@@ -394,12 +386,6 @@ module Precious
mustache :page
end
get '/livepreview/' do
wiki = wiki_new
@mathjax = wiki.mathjax
mustache :livepreview, { :layout => false }
end
get '/history/*' do
@page = wiki_page(params[:splat].first).page
@page_num = [params[:page].to_i, 1].max
@@ -1,132 +0,0 @@
body {
overflow: hidden;
}
#editor .ace_sb {
overflow-y: auto !important;
}
#darkness {
visibility: hidden;
position: absolute;
left: 0px;
top: 0px;
background-color: black;
opacity: 0.8;
z-index: 1001; /* must be > 1000 to overlay ace gutter */
}
#commenttoolpanel {
visibility: hidden;
z-index: 1002; /* > 1001 to not be hidden by darkness */
}
#comment, #editor {
margin: 0;
padding: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
/* Set font size of both ace editors. */
font-size: 16px;
}
/*
Must set ace-line to preserve whitespace in empty lines.
Ace editor sets the height inline. The static highlight ext does not.
<div class="ace_line" style="height:15px"></div>
*/
#previewframe #contentframe .ace-github .ace_editor.ace_scroller.ace_text-layer .ace_line {
height: 15px;
}
/* Set comment to have a higher z-index
so editor doesn't display in the background. */
#comment {
visibility: hidden;
z-index: 1003; /* > 1002 to not be hidden by toolpanel */
}
#contentframe {
margin: 0 auto;
overflow: visible;
width: 90%;
}
#previewframe {
margin: 0;
padding: 0;
position: absolute;
overflow: auto;
top: 0;
bottom: 0;
left: 10px;
right: 0;
}
.editor_bg {
position: fixed;
top: 0;
margin: 0;
padding: 0;
background: black;
width: 50%;
height: 100%;
z-index: -2;
}
.toolpanel_bg {
position: fixed;
background: #666;
top: 0;
height: 30px;
width: 100%;
padding: 5px 0;
margin: 0;
z-index: -1;
}
/* -- Start from notepag.es -- */
.toolpanel {
position: fixed;
background: #666;
top: 0;
height: 30px;
width: 50%;
vertical-align: middle;
padding: 5px 0;
margin: 0;
text-align: center;
}
.toolpanel.edit a.edit {
opacity: 0.4;
/* Make it appear as a link even though save
doesn't have a href attribute. */
cursor: pointer;
display: inline-block;
}
.toolpanel a {
color: white;
text-decoration: none;
margin: 0 5px;
display: none;
padding: 4px;
font-family: sans-serif;
}
.toolpanel a img {
vertical-align: middle;
margin-left: 5px;
margin: 0;
padding: 0;
}
a img {
border: none;
}
/* -- End from notepag.es -- */
Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

@@ -1,129 +0,0 @@
/* ***** 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 ***** */
/**
* The main class required to set up an Ace instance in the browser.
*
* @class Ace
**/
define(function(require, exports, module) {
"use strict";
require("./lib/fixoldbrowsers");
var dom = require("./lib/dom");
var event = require("./lib/event");
var Editor = require("./editor").Editor;
var EditSession = require("./edit_session").EditSession;
var UndoManager = require("./undomanager").UndoManager;
var Renderer = require("./virtual_renderer").VirtualRenderer;
// The following require()s are for inclusion in the built ace file
require("./worker/worker_client");
require("./keyboard/hash_handler");
require("./placeholder");
require("./multi_select");
require("./mode/folding/fold_mode");
require("./theme/textmate");
require("./ext/error_marker");
exports.config = require("./config");
/**
* Provides access to require in packed noconflict mode
* @param {String} moduleName
* @returns {Object}
*
**/
exports.require = require;
/**
* Embeds the Ace editor into the DOM, at the element provided by `el`.
* @param {String | DOMElement} el Either the id of an element, or the element itself
*
**/
exports.edit = function(el) {
if (typeof(el) == "string") {
var _id = el;
el = document.getElementById(_id);
if (!el)
throw new Error("ace.edit can't find div #" + _id);
}
if (el && el.env && el.env.editor instanceof Editor)
return el.env.editor;
var value = "";
if (el && /input|textarea/i.test(el.tagName)) {
var oldNode = el;
value = oldNode.value;
el = dom.createElement("pre");
oldNode.parentNode.replaceChild(el, oldNode);
} else {
value = dom.getInnerText(el);
el.innerHTML = '';
}
var doc = exports.createEditSession(value);
var editor = new Editor(new Renderer(el));
editor.setSession(doc);
var env = {
document: doc,
editor: editor,
onResize: editor.resize.bind(editor, null)
};
if (oldNode) env.textarea = oldNode;
event.addListener(window, "resize", env.onResize);
editor.on("destroy", function() {
event.removeListener(window, "resize", env.onResize);
env.editor.container.env = null; // prevent memory leak on old ie
});
editor.container.env = editor.env = env;
return editor;
};
/**
* Creates a new [[EditSession]], and returns the associated [[Document]].
* @param {Document | String} text {:textParam}
* @param {TextMode} mode {:modeParam}
*
**/
exports.createEditSession = function(text, mode) {
var doc = new EditSession(text, mode);
doc.setUndoManager(new UndoManager());
return doc;
}
exports.EditSession = EditSession;
exports.UndoManager = UndoManager;
});
@@ -1,245 +0,0 @@
/* ***** 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 EventEmitter = require("./lib/event_emitter").EventEmitter;
/**
*
* Defines a floating pointer in the document. Whenever text is inserted or deleted before the cursor, the position of the anchor is updated.
*
* @class Anchor
**/
/**
* Creates a new `Anchor` and associates it with a document.
*
* @param {Document} doc The document to associate with the anchor
* @param {Number} row The starting row position
* @param {Number} column The starting column position
*
* @constructor
**/
var Anchor = exports.Anchor = function(doc, row, column) {
this.$onChange = this.onChange.bind(this);
this.attach(doc);
if (typeof column == "undefined")
this.setPosition(row.row, row.column);
else
this.setPosition(row, column);
};
(function() {
oop.implement(this, EventEmitter);
/**
* Returns an object identifying the `row` and `column` position of the current anchor.
* @returns {Object}
**/
this.getPosition = function() {
return this.$clipPositionToDocument(this.row, this.column);
};
/**
*
* Returns the current document.
* @returns {Document}
**/
this.getDocument = function() {
return this.document;
};
/**
* experimental: allows anchor to stick to the next on the left
*/
this.$insertRight = false;
/**
* Fires whenever the anchor position changes.
*
* Both of these objects have a `row` and `column` property corresponding to the position.
*
* Events that can trigger this function include [[Anchor.setPosition `setPosition()`]].
*
* @event change
* @param {Object} e An object containing information about the anchor position. It has two properties:
* - `old`: An object describing the old Anchor position
* - `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)
return;
if (range.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);
};
/**
* Sets the anchor position to the specified row and column. If `noClip` is `true`, the position is not clipped.
* @param {Number} row The row index to move the anchor to
* @param {Number} column The column index to move the anchor to
* @param {Boolean} noClip Identifies if you want the position to be clipped
*
**/
this.setPosition = function(row, column, noClip) {
var pos;
if (noClip) {
pos = {
row: row,
column: column
};
} else {
pos = this.$clipPositionToDocument(row, column);
}
if (this.row == pos.row && this.column == pos.column)
return;
var old = {
row: this.row,
column: this.column
};
this.row = pos.row;
this.column = pos.column;
this._signal("change", {
old: old,
value: pos
});
};
/**
* When called, the `"change"` event listener is removed.
*
**/
this.detach = function() {
this.document.removeEventListener("change", this.$onChange);
};
this.attach = function(doc) {
this.document = doc || this.document;
this.document.on("change", this.$onChange);
};
/**
* Clips the anchor position to the specified row and column.
* @param {Number} row The row index to clip the anchor to
* @param {Number} column The column index to clip the anchor to
*
**/
this.$clipPositionToDocument = function(row, column) {
var pos = {};
if (row >= this.document.getLength()) {
pos.row = Math.max(0, this.document.getLength() - 1);
pos.column = this.document.getLine(pos.row).length;
}
else if (row < 0) {
pos.row = 0;
pos.column = 0;
}
else {
pos.row = row;
pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column));
}
if (column < 0)
pos.column = 0;
return pos;
};
}).call(Anchor.prototype);
});
@@ -1,223 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var Document = require("./document").Document;
var Anchor = require("./anchor").Anchor;
var Range = require("./range").Range;
var assert = require("./test/assertions");
module.exports = {
"test create anchor" : function() {
var doc = new Document("juhu");
var anchor = new Anchor(doc, 0, 0);
assert.position(anchor.getPosition(), 0, 0);
assert.equal(anchor.getDocument(), doc);
},
"test insert text in same row before cursor should move anchor column": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.insert({row: 1, column: 1}, "123");
assert.position(anchor.getPosition(), 1, 7);
},
"test insert text at anchor should not move anchor when insertRight is true": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
anchor.$insertRight = true;
doc.insert({row: 1, column: 4}, "123");
assert.position(anchor.getPosition(), 1, 4);
},
"test insert lines before cursor should move anchor row": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.insertLines(1, ["123", "456"]);
assert.position(anchor.getPosition(), 3, 4);
},
"test insert lines at anchor position should move anchor down": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 0);
doc.insertLines(1, ["line"]);
assert.position(anchor.getPosition(), 2, 0);
},
"test insert lines at anchor position should not move anchor down when insertRight is true and column is 0": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 0);
anchor.$insertRight = true;
doc.insertLines(1, ["line"]);
assert.position(anchor.getPosition(), 1, 0);
},
"test insert lines at anchor row should move anchor down when column > 0": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 2);
anchor.$insertRight = true;
doc.insertLines(1, ["line"]);
assert.position(anchor.getPosition(), 2, 2);
},
"test insert new line before cursor should move anchor column": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.insertNewLine({row: 0, column: 0});
assert.position(anchor.getPosition(), 2, 4);
},
"test insert new line in anchor line before anchor should move anchor column and row": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.insertNewLine({row: 1, column: 2});
assert.position(anchor.getPosition(), 2, 2);
},
"test delete text in anchor line before anchor should move anchor column": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.remove(new Range(1, 1, 1, 3));
assert.position(anchor.getPosition(), 1, 2);
},
"test remove range which contains the anchor should move the anchor to the start of the range": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 0, 3);
doc.remove(new Range(0, 1, 1, 3));
assert.position(anchor.getPosition(), 0, 1);
},
"test delete character before the anchor should have no effect": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.remove(new Range(1, 4, 1, 5));
assert.position(anchor.getPosition(), 1, 4);
},
"test delete lines in anchor line before anchor should move anchor row": function() {
var doc = new Document("juhu\n1\n2\nkinners");
var anchor = new Anchor(doc, 3, 4);
doc.removeLines(1, 2);
assert.position(anchor.getPosition(), 1, 4);
},
"test remove new line before the cursor": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.removeNewLine(0);
assert.position(anchor.getPosition(), 0, 8);
},
"test delete range which contains the anchor should move anchor to the end of the range": function() {
var doc = new Document("juhu\nkinners");
var anchor = new Anchor(doc, 1, 4);
doc.remove(new Range(0, 2, 1, 2));
assert.position(anchor.getPosition(), 0, 4);
},
"test delete line which contains the anchor should move anchor to the end of the range": function() {
var doc = new Document("juhu\nkinners\n123");
var anchor = new Anchor(doc, 1, 5);
doc.removeLines(1, 1);
assert.position(anchor.getPosition(), 1, 0);
},
"test remove after the anchor should have no effect": function() {
var doc = new Document("juhu\nkinners\n123");
var anchor = new Anchor(doc, 1, 2);
doc.remove(new Range(1, 4, 2, 2));
assert.position(anchor.getPosition(), 1, 2);
},
"test anchor changes triggered by document changes should emit change event": function(next) {
var doc = new Document("juhu\nkinners\n123");
var anchor = new Anchor(doc, 1, 5);
anchor.on("change", function(e) {
assert.position(anchor.getPosition(), 0, 0);
next();
});
doc.remove(new Range(0, 0, 2, 1));
},
"test only fire change event if position changes": function() {
var doc = new Document("juhu\nkinners\n123");
var anchor = new Anchor(doc, 1, 5);
anchor.on("change", function(e) {
assert.fail();
});
doc.remove(new Range(2, 0, 2, 1));
},
"test insert/remove lines at the end of the document": function() {
var doc = new Document("juhu\nkinners\n123");
var anchor = new Anchor(doc, 2, 4);
doc.removeLines(0, 3);
assert.position(anchor.getPosition(), 0, 0);
doc.insertLines(0, ["a", "b", "c"]);
assert.position(anchor.getPosition(), 3, 0);
assert.equal(doc.getValue(), "a\nb\nc\n");
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,497 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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 HashHandler = require("./keyboard/hash_handler").HashHandler;
var AcePopup = require("./autocomplete/popup").AcePopup;
var util = require("./autocomplete/util");
var event = require("./lib/event");
var lang = require("./lib/lang");
var dom = require("./lib/dom");
var snippetManager = require("./snippets").snippetManager;
var Autocomplete = function() {
this.autoInsert = false;
this.autoSelect = true;
this.exactMatch = false;
this.gatherCompletionsId = 0;
this.keyboardHandler = new HashHandler();
this.keyboardHandler.bindKeys(this.commands);
this.blurListener = this.blurListener.bind(this);
this.changeListener = this.changeListener.bind(this);
this.mousedownListener = this.mousedownListener.bind(this);
this.mousewheelListener = this.mousewheelListener.bind(this);
this.changeTimer = lang.delayedCall(function() {
this.updateCompletions(true);
}.bind(this));
this.tooltipTimer = lang.delayedCall(this.updateDocTooltip.bind(this), 50);
};
(function() {
this.$init = function() {
this.popup = new AcePopup(document.body || document.documentElement);
this.popup.on("click", function(e) {
this.insertMatch();
e.stop();
}.bind(this));
this.popup.focus = this.editor.focus.bind(this.editor);
this.popup.on("show", this.tooltipTimer.bind(null, null));
this.popup.on("select", this.tooltipTimer.bind(null, null));
this.popup.on("changeHoverMarker", this.tooltipTimer.bind(null, null));
return this.popup;
};
this.getPopup = function() {
return this.popup || this.$init();
};
this.openPopup = function(editor, prefix, keepPopupPosition) {
if (!this.popup)
this.$init();
this.popup.setData(this.completions.filtered);
editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
var renderer = editor.renderer;
this.popup.setRow(this.autoSelect ? 0 : -1);
if (!keepPopupPosition) {
this.popup.setTheme(editor.getTheme());
this.popup.setFontSize(editor.getFontSize());
var lineHeight = renderer.layerConfig.lineHeight;
var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
pos.left -= this.popup.getTextLeftOffset();
var rect = editor.container.getBoundingClientRect();
pos.top += rect.top - renderer.layerConfig.offset;
pos.left += rect.left - editor.renderer.scrollLeft;
pos.left += renderer.$gutterLayer.gutterWidth;
this.popup.show(pos, lineHeight);
} else if (keepPopupPosition && !prefix) {
this.detach();
}
};
this.detach = function() {
this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
this.editor.off("changeSelection", this.changeListener);
this.editor.off("blur", this.blurListener);
this.editor.off("mousedown", this.mousedownListener);
this.editor.off("mousewheel", this.mousewheelListener);
this.changeTimer.cancel();
this.hideDocTooltip();
this.gatherCompletionsId += 1;
if (this.popup && this.popup.isOpen)
this.popup.hide();
if (this.base)
this.base.detach();
this.activated = false;
this.completions = this.base = null;
};
this.changeListener = function(e) {
var cursor = this.editor.selection.lead;
if (cursor.row != this.base.row || cursor.column < this.base.column) {
this.detach();
}
if (this.activated)
this.changeTimer.schedule();
else
this.detach();
};
this.blurListener = function(e) {
// 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 && el.parentNode != this.popup.container
&& el != this.tooltipNode && e.relatedTarget != this.tooltipNode
&& e.relatedTarget != text
) {
this.detach();
}
};
this.mousedownListener = function(e) {
this.detach();
};
this.mousewheelListener = function(e) {
this.detach();
};
this.goTo = function(where) {
var row = this.popup.getRow();
var max = this.popup.session.getLength() - 1;
switch(where) {
case "up": row = row <= 0 ? max : row - 1; break;
case "down": row = row >= max ? -1 : row + 1; break;
case "start": row = 0; break;
case "end": row = max; break;
}
this.popup.setRow(row);
};
this.insertMatch = function(data) {
if (!data)
data = this.popup.getData(this.popup.getRow());
if (!data)
return false;
if (data.completer && data.completer.insertMatch) {
data.completer.insertMatch(this.editor, data);
} else {
if (this.completions.filterText) {
var ranges = this.editor.selection.getAllRanges();
for (var i = 0, range; range = ranges[i]; i++) {
range.start.column -= this.completions.filterText.length;
this.editor.session.remove(range);
}
}
if (data.snippet)
snippetManager.insertSnippet(this.editor, data.snippet);
else
this.editor.execCommand("insertstring", data.value || data);
}
this.detach();
};
this.commands = {
"Up": function(editor) { editor.completer.goTo("up"); },
"Down": function(editor) { editor.completer.goTo("down"); },
"Ctrl-Up|Ctrl-Home": function(editor) { editor.completer.goTo("start"); },
"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) {
var result = editor.completer.insertMatch();
if (!result && !editor.tabstopManager)
editor.completer.goTo("down");
else
return result;
},
"PageUp": function(editor) { editor.completer.popup.gotoPageUp(); },
"PageDown": function(editor) { editor.completer.popup.gotoPageDown(); }
};
this.gatherCompletions = function(editor, callback) {
var session = editor.getSession();
var pos = editor.getCursorPosition();
var line = session.getLine(pos.row);
var prefix = util.retrievePrecedingIdentifier(line, pos.column);
this.base = session.doc.createAnchor(pos.row, pos.column - prefix.length);
this.base.$insertRight = true;
var matches = [];
var total = editor.completers.length;
editor.completers.forEach(function(completer, i) {
completer.getCompletions(editor, session, pos, prefix, function(err, results) {
if (!err)
matches = matches.concat(results);
// Fetch prefix again, because they may have changed by now
var pos = editor.getCursorPosition();
var line = session.getLine(pos.row);
callback(null, {
prefix: util.retrievePrecedingIdentifier(line, pos.column, results[0] && results[0].identifierRegex),
matches: matches,
finished: (--total === 0)
});
});
});
return true;
};
this.showPopup = function(editor) {
if (this.editor)
this.detach();
this.activated = true;
this.editor = editor;
if (editor.completer != this) {
if (editor.completer)
editor.completer.detach();
editor.completer = this;
}
editor.on("changeSelection", this.changeListener);
editor.on("blur", this.blurListener);
editor.on("mousedown", this.mousedownListener);
editor.on("mousewheel", this.mousewheelListener);
this.updateCompletions();
};
this.updateCompletions = function(keepPopupPosition) {
if (keepPopupPosition && this.base && this.completions) {
var pos = this.editor.getCursorPosition();
var prefix = this.editor.session.getTextRange({start: this.base, end: pos});
if (prefix == this.completions.filterText)
return;
this.completions.setFilter(prefix);
if (!this.completions.filtered.length)
return this.detach();
if (this.completions.filtered.length == 1
&& this.completions.filtered[0].value == prefix
&& !this.completions.filtered[0].snippet)
return this.detach();
this.openPopup(this.editor, prefix, keepPopupPosition);
return;
}
// Save current gatherCompletions session, session is close when a match is insert
var _id = this.gatherCompletionsId;
this.gatherCompletions(this.editor, function(err, results) {
// Only detach if result gathering is finished
var detachIfFinished = function() {
if (!results.finished) return;
return this.detach();
}.bind(this);
var prefix = results.prefix;
var matches = results && results.matches;
if (!matches || !matches.length)
return detachIfFinished();
// Wrong prefix or wrong session -> ignore
if (prefix.indexOf(results.prefix) !== 0 || _id != this.gatherCompletionsId)
return;
this.completions = new FilteredList(matches);
if (this.exactMatch)
this.completions.exactMatch = true;
this.completions.setFilter(prefix);
var filtered = this.completions.filtered;
// No results
if (!filtered.length)
return detachIfFinished();
// One result equals to the prefix
if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet)
return detachIfFinished();
// Autoinsert if one result
if (this.autoInsert && filtered.length == 1 && results.finished)
return this.insertMatch(filtered[0]);
this.openPopup(this.editor, prefix, keepPopupPosition);
}.bind(this));
};
this.cancelContextMenu = function() {
this.editor.$mouseHandler.cancelContextMenu();
};
this.updateDocTooltip = function() {
var popup = this.popup;
var all = popup.data;
var selected = all && (all[popup.getHoveredRow()] || all[popup.getRow()]);
var doc = null;
if (!selected || !this.editor || !this.popup.isOpen)
return this.hideDocTooltip();
this.editor.completers.some(function(completer) {
if (completer.getDocTooltip)
doc = completer.getDocTooltip(selected);
return doc;
});
if (!doc)
doc = selected;
if (typeof doc == "string")
doc = {docText: doc}
if (!doc || !(doc.docHTML || doc.docText))
return this.hideDocTooltip();
this.showDocTooltip(doc);
};
this.showDocTooltip = function(item) {
if (!this.tooltipNode) {
this.tooltipNode = dom.createElement("div");
this.tooltipNode.className = "ace_tooltip ace_doc-tooltip";
this.tooltipNode.style.margin = 0;
this.tooltipNode.style.pointerEvents = "auto";
this.tooltipNode.tabIndex = -1;
this.tooltipNode.onblur = this.blurListener.bind(this);
}
var tooltipNode = this.tooltipNode;
if (item.docHTML) {
tooltipNode.innerHTML = item.docHTML;
} else if (item.docText) {
tooltipNode.textContent = item.docText;
}
if (!tooltipNode.parentNode)
document.body.appendChild(tooltipNode);
var popup = this.popup;
var rect = popup.container.getBoundingClientRect();
tooltipNode.style.top = popup.container.style.top;
tooltipNode.style.bottom = popup.container.style.bottom;
if (window.innerWidth - rect.right < 320) {
tooltipNode.style.right = window.innerWidth - rect.left + "px";
tooltipNode.style.left = "";
} else {
tooltipNode.style.left = (rect.right + 1) + "px";
tooltipNode.style.right = "";
}
tooltipNode.style.display = "block";
};
this.hideDocTooltip = function() {
this.tooltipTimer.cancel();
if (!this.tooltipNode) return;
var el = this.tooltipNode;
if (!this.editor.isFocused() && document.activeElement == el)
this.editor.focus();
this.tooltipNode = null;
if (el.parentNode)
el.parentNode.removeChild(el);
};
}).call(Autocomplete.prototype);
Autocomplete.startCommand = {
name: "startAutocomplete",
exec: function(editor) {
if (!editor.completer)
editor.completer = new Autocomplete();
editor.completer.autoInsert = false;
editor.completer.autoSelect = true;
editor.completer.showPopup(editor);
// prevent ctrl-space opening context menu on firefox on mac
editor.completer.cancelContextMenu();
},
bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
};
var FilteredList = function(array, filterText, mutateData) {
this.all = array;
this.filtered = array;
this.filterText = filterText || "";
this.exactMatch = false;
};
(function(){
this.setFilter = function(str) {
if (str.length > this.filterText && str.lastIndexOf(this.filterText, 0) === 0)
var matches = this.filtered;
else
var matches = this.all;
this.filterText = str;
matches = this.filterCompletions(matches, this.filterText);
matches = matches.sort(function(a, b) {
return b.exactMatch - a.exactMatch || b.score - a.score;
});
// make unique
var prev = null;
matches = matches.filter(function(item){
var caption = item.snippet || item.caption || item.value;
if (caption === prev) return false;
prev = caption;
return true;
});
this.filtered = matches;
};
this.filterCompletions = function(items, needle) {
var results = [];
var upper = needle.toUpperCase();
var lower = needle.toLowerCase();
loop: for (var i = 0, item; item = items[i]; i++) {
var caption = item.value || item.caption || item.snippet;
if (!caption) continue;
var lastIndex = -1;
var matchMask = 0;
var penalty = 0;
var index, distance;
if (this.exactMatch) {
if (needle !== caption.substr(0, needle.length))
continue loop;
}else{
// caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf
for (var j = 0; j < needle.length; j++) {
// TODO add penalty on case mismatch
var i1 = caption.indexOf(lower[j], lastIndex + 1);
var i2 = caption.indexOf(upper[j], lastIndex + 1);
index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2;
if (index < 0)
continue loop;
distance = index - lastIndex - 1;
if (distance > 0) {
// first char mismatch should be more sensitive
if (lastIndex === -1)
penalty += 10;
penalty += distance;
}
matchMask = matchMask | (1 << index);
lastIndex = index;
}
}
item.matchMask = matchMask;
item.exactMatch = penalty ? 0 : 1;
item.score = (item.score || 0) - penalty;
results.push(item);
}
return results;
};
}).call(FilteredList.prototype);
exports.Autocomplete = Autocomplete;
exports.FilteredList = FilteredList;
});
@@ -1,341 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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 EditSession = require("../edit_session").EditSession;
var Renderer = require("../virtual_renderer").VirtualRenderer;
var Editor = require("../editor").Editor;
var Range = require("../range").Range;
var event = require("../lib/event");
var lang = require("../lib/lang");
var dom = require("../lib/dom");
var $singleLineEditor = function(el) {
var renderer = new Renderer(el);
renderer.$maxLines = 4;
var editor = new Editor(renderer);
editor.setHighlightActiveLine(false);
editor.setShowPrintMargin(false);
editor.renderer.setShowGutter(false);
editor.renderer.setHighlightGutterLine(false);
editor.$mouseHandler.$focusWaitTimout = 0;
editor.$highlightTagPending = true;
return editor;
};
var AcePopup = function(parentNode) {
var el = dom.createElement("div");
var popup = new $singleLineEditor(el);
if (parentNode)
parentNode.appendChild(el);
el.style.display = "none";
popup.renderer.content.style.cursor = "default";
popup.renderer.setStyle("ace_autocomplete");
popup.setOption("displayIndentGuides", false);
popup.setOption("dragDelay", 150);
var noop = function(){};
popup.focus = noop;
popup.$isFocused = true;
popup.renderer.$cursorLayer.restartTimer = noop;
popup.renderer.$cursorLayer.element.style.opacity = 0;
popup.renderer.$maxLines = 8;
popup.renderer.$keepTextAreaAtCursor = false;
popup.setHighlightActiveLine(false);
// set default highlight color
popup.session.highlight("");
popup.session.$searchHighlight.clazz = "ace_highlight-marker";
popup.on("mousedown", function(e) {
var pos = e.getDocumentPosition();
popup.selection.moveToPosition(pos);
selectionMarker.start.row = selectionMarker.end.row = pos.row;
e.stop();
});
var lastMouseEvent;
var hoverMarker = new Range(-1,0,-1,Infinity);
var selectionMarker = new Range(-1,0,-1,Infinity);
selectionMarker.id = popup.session.addMarker(selectionMarker, "ace_active-line", "fullLine");
popup.setSelectOnHover = function(val) {
if (!val) {
hoverMarker.id = popup.session.addMarker(hoverMarker, "ace_line-hover", "fullLine");
} else if (hoverMarker.id) {
popup.session.removeMarker(hoverMarker.id);
hoverMarker.id = null;
}
};
popup.setSelectOnHover(false);
popup.on("mousemove", function(e) {
if (!lastMouseEvent) {
lastMouseEvent = e;
return;
}
if (lastMouseEvent.x == e.x && lastMouseEvent.y == e.y) {
return;
}
lastMouseEvent = e;
lastMouseEvent.scrollTop = popup.renderer.scrollTop;
var row = lastMouseEvent.getDocumentPosition().row;
if (hoverMarker.start.row != row) {
if (!hoverMarker.id)
popup.setRow(row);
setHoverMarker(row);
}
});
popup.renderer.on("beforeRender", function() {
if (lastMouseEvent && hoverMarker.start.row != -1) {
lastMouseEvent.$pos = null;
var row = lastMouseEvent.getDocumentPosition().row;
if (!hoverMarker.id)
popup.setRow(row);
setHoverMarker(row, true);
}
});
popup.renderer.on("afterRender", function() {
var row = popup.getRow();
var t = popup.renderer.$textLayer;
var selected = t.element.childNodes[row - t.config.firstRow];
if (selected == t.selectedNode)
return;
if (t.selectedNode)
dom.removeCssClass(t.selectedNode, "ace_selected");
t.selectedNode = selected;
if (selected)
dom.addCssClass(selected, "ace_selected");
});
var hideHoverMarker = function() { setHoverMarker(-1) };
var setHoverMarker = function(row, suppressRedraw) {
if (row !== hoverMarker.start.row) {
hoverMarker.start.row = hoverMarker.end.row = row;
if (!suppressRedraw)
popup.session._emit("changeBackMarker");
popup._emit("changeHoverMarker");
}
};
popup.getHoveredRow = function() {
return hoverMarker.start.row;
};
event.addListener(popup.container, "mouseout", hideHoverMarker);
popup.on("hide", hideHoverMarker);
popup.on("changeSelection", hideHoverMarker);
popup.session.doc.getLength = function() {
return popup.data.length;
};
popup.session.doc.getLine = function(i) {
var data = popup.data[i];
if (typeof data == "string")
return data;
return (data && data.value) || "";
};
var bgTokenizer = popup.session.bgTokenizer;
bgTokenizer.$tokenizeRow = function(row) {
var data = popup.data[row];
var tokens = [];
if (!data)
return tokens;
if (typeof data == "string")
data = {value: data};
if (!data.caption)
data.caption = data.value || data.name;
var last = -1;
var flag, c;
for (var i = 0; i < data.caption.length; i++) {
c = data.caption[i];
flag = data.matchMask & (1 << i) ? 1 : 0;
if (last !== flag) {
tokens.push({type: data.className || "" + ( flag ? "completion-highlight" : ""), value: c});
last = flag;
} else {
tokens[tokens.length - 1].value += c;
}
}
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});
}
return tokens;
};
bgTokenizer.$updateOnChange = noop;
bgTokenizer.start = noop;
popup.session.$computeWidth = function() {
return this.screenWidth = 0;
};
popup.$blockScrolling = Infinity;
// public
popup.isOpen = false;
popup.isTopdown = false;
popup.data = [];
popup.setData = function(list) {
popup.data = list || [];
popup.setValue(lang.stringRepeat("\n", list.length), -1);
popup.setRow(0);
};
popup.getData = function(row) {
return popup.data[row];
};
popup.getRow = function() {
return selectionMarker.start.row;
};
popup.setRow = function(line) {
line = Math.max(-1, Math.min(this.data.length, line));
if (selectionMarker.start.row != line) {
popup.selection.clearSelection();
selectionMarker.start.row = selectionMarker.end.row = line || 0;
popup.session._emit("changeBackMarker");
popup.moveCursorTo(line || 0, 0);
if (popup.isOpen)
popup._signal("select");
}
};
popup.on("changeSelection", function() {
if (popup.isOpen)
popup.setRow(popup.selection.lead.row);
popup.renderer.scrollCursorIntoView();
});
popup.hide = function() {
this.container.style.display = "none";
this._signal("hide");
popup.isOpen = false;
};
popup.show = function(pos, lineHeight, topdownOnly) {
var el = this.container;
var screenHeight = window.innerHeight;
var screenWidth = window.innerWidth;
var renderer = this.renderer;
// var maxLines = Math.min(renderer.$maxLines, this.session.getLength());
var maxH = renderer.$maxLines * lineHeight * 1.4;
var top = pos.top + this.$borderSize;
if (top + maxH > screenHeight - lineHeight && !topdownOnly) {
el.style.top = "";
el.style.bottom = screenHeight - top + "px";
popup.isTopdown = false;
} else {
top += lineHeight;
el.style.top = top + "px";
el.style.bottom = "";
popup.isTopdown = true;
}
el.style.display = "";
this.renderer.$textLayer.checkForSizeChanges();
var left = pos.left;
if (left + el.offsetWidth > screenWidth)
left = screenWidth - el.offsetWidth;
el.style.left = left + "px";
this._signal("show");
lastMouseEvent = null;
popup.isOpen = true;
};
popup.getTextLeftOffset = function() {
return this.$borderSize + this.renderer.$padding + this.$imageSize;
};
popup.$imageSize = 0;
popup.$borderSize = 1;
return popup;
};
dom.importCssString("\
.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\
background-color: #CAD6FA;\
z-index: 1;\
}\
.ace_editor.ace_autocomplete .ace_line-hover {\
border: 1px solid #abbffe;\
margin-top: -1px;\
background: rgba(233,233,253,0.4);\
}\
.ace_editor.ace_autocomplete .ace_line-hover {\
position: absolute;\
z-index: 2;\
}\
.ace_editor.ace_autocomplete .ace_scroller {\
background: none;\
border: none;\
box-shadow: none;\
}\
.ace_rightAlignedText {\
color: gray;\
display: inline-block;\
position: absolute;\
right: 4px;\
text-align: right;\
z-index: -1;\
}\
.ace_editor.ace_autocomplete .ace_completion-highlight{\
color: #000;\
text-shadow: 0 0 0.01em;\
}\
.ace_editor.ace_autocomplete {\
width: 280px;\
z-index: 200000;\
background: #fbfbfb;\
color: #444;\
border: 1px lightgray solid;\
position: fixed;\
box-shadow: 2px 3px 5px rgba(0,0,0,.2);\
line-height: 1.4;\
}");
exports.AcePopup = AcePopup;
});
@@ -1,78 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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) {
var Range = require("../range").Range;
var splitRegex = /[^a-zA-Z_0-9\$\-\u00C0-\u1FFF\u2C00-\uD7FF\w]+/;
function getWordIndex(doc, pos) {
var textBefore = doc.getTextRange(Range.fromPoints({row: 0, column:0}, pos));
return textBefore.split(splitRegex).length - 1;
}
/**
* Does a distance analysis of the word `prefix` at position `pos` in `doc`.
* @return Map
*/
function wordDistance(doc, pos) {
var prefixPos = getWordIndex(doc, pos);
var words = doc.getValue().split(splitRegex);
var wordScores = Object.create(null);
var currentWord = words[prefixPos];
words.forEach(function(word, idx) {
if (!word || word === currentWord) return;
var distance = Math.abs(prefixPos - idx);
var score = words.length - distance;
if (wordScores[word]) {
wordScores[word] = Math.max(score, wordScores[word]);
} else {
wordScores[word] = score;
}
});
return wordScores;
}
exports.getCompletions = function(editor, session, pos, prefix, callback) {
var wordScore = wordDistance(session, pos, prefix);
var wordList = Object.keys(wordScore);
callback(null, wordList.map(function(word) {
return {
caption: word,
value: word,
score: wordScore[word],
meta: "local"
};
}));
};
});
@@ -1,74 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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";
exports.parForEach = function(array, fn, callback) {
var completed = 0;
var arLength = array.length;
if (arLength === 0)
callback();
for (var i = 0; i < arLength; i++) {
fn(array[i], function(result, err) {
completed++;
if (completed === arLength)
callback(result, err);
});
}
};
var ID_REGEX = /[a-zA-Z_0-9\$\-\u00A2-\uFFFF]/;
exports.retrievePrecedingIdentifier = function(text, pos, regex) {
regex = regex || ID_REGEX;
var buf = [];
for (var i = pos-1; i >= 0; i--) {
if (regex.test(text[i]))
buf.push(text[i]);
else
break;
}
return buf.reverse().join("");
};
exports.retrieveFollowingIdentifier = function(text, pos, regex) {
regex = regex || ID_REGEX;
var buf = [];
for (var i = pos; i < text.length; i++) {
if (regex.test(text[i]))
buf.push(text[i]);
else
break;
}
return buf;
};
});
@@ -1,250 +0,0 @@
/* ***** 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 EventEmitter = require("./lib/event_emitter").EventEmitter;
/**
* Tokenizes the current [[Document `Document`]] in the background, and caches the tokenized rows for future use.
*
* If a certain row is changed, everything below that row is re-tokenized.
*
* @class BackgroundTokenizer
**/
/**
* Creates a new `BackgroundTokenizer` object.
* @param {Tokenizer} tokenizer The tokenizer to use
* @param {Editor} editor The editor to associate with
*
* @constructor
**/
var BackgroundTokenizer = function(tokenizer, editor) {
this.running = false;
this.lines = [];
this.states = [];
this.currentLine = 0;
this.tokenizer = tokenizer;
var self = this;
this.$worker = function() {
if (!self.running) { return; }
var workerStart = new Date();
var currentLine = self.currentLine;
var endLine = -1;
var doc = self.doc;
while (self.lines[currentLine])
currentLine++;
var startLine = currentLine;
var len = doc.getLength();
var processedLines = 0;
self.running = false;
while (currentLine < len) {
self.$tokenizeRow(currentLine);
endLine = currentLine;
do {
currentLine++;
} while (self.lines[currentLine]);
// only check every 5 lines
processedLines ++;
if ((processedLines % 5 === 0) && (new Date() - workerStart) > 20) {
self.running = setTimeout(self.$worker, 20);
break;
}
}
self.currentLine = currentLine;
if (startLine <= endLine)
self.fireUpdateEvent(startLine, endLine);
};
};
(function(){
oop.implement(this, EventEmitter);
/**
* Sets a new tokenizer for this object.
*
* @param {Tokenizer} tokenizer The new tokenizer to use
*
**/
this.setTokenizer = function(tokenizer) {
this.tokenizer = tokenizer;
this.lines = [];
this.states = [];
this.start(0);
};
/**
* Sets a new document to associate with this object.
* @param {Document} doc The new document to associate with
**/
this.setDocument = function(doc) {
this.doc = doc;
this.lines = [];
this.states = [];
this.stop();
};
/**
* Fires whenever the background tokeniziers between a range of rows are going to be updated.
*
* @event update
* @param {Object} e An object containing two properties, `first` and `last`, which indicate the rows of the region being updated.
*
**/
/**
* Emits the `'update'` event. `firstRow` and `lastRow` are used to define the boundaries of the region to be updated.
* @param {Number} firstRow The starting row region
* @param {Number} lastRow The final row region
*
**/
this.fireUpdateEvent = function(firstRow, lastRow) {
var data = {
first: firstRow,
last: lastRow
};
this._signal("update", {data: data});
};
/**
* Starts tokenizing at the row indicated.
*
* @param {Number} startRow The row to start at
*
**/
this.start = function(startRow) {
this.currentLine = Math.min(startRow || 0, this.currentLine, this.doc.getLength());
// remove all cached items below this line
this.lines.splice(this.currentLine, this.lines.length);
this.states.splice(this.currentLine, this.states.length);
this.stop();
// pretty long delay to prevent the tokenizer from interfering with the user
this.running = setTimeout(this.$worker, 700);
};
this.scheduleStart = function() {
if (!this.running)
this.running = setTimeout(this.$worker, 700);
}
this.$updateOnChange = function(delta) {
var range = delta.range;
var startRow = range.start.row;
var len = range.end.row - startRow;
if (len === 0) {
this.lines[startRow] = null;
} else if (delta.action == "removeText" || delta.action == "removeLines") {
this.lines.splice(startRow, len + 1, null);
this.states.splice(startRow, len + 1, null);
} else {
var args = Array(len + 1);
args.unshift(startRow, 1);
this.lines.splice.apply(this.lines, args);
this.states.splice.apply(this.states, args);
}
this.currentLine = Math.min(startRow, this.currentLine, this.doc.getLength());
this.stop();
};
/**
* Stops tokenizing.
*
**/
this.stop = function() {
if (this.running)
clearTimeout(this.running);
this.running = false;
};
/**
* Gives list of tokens of the row. (tokens are cached)
*
* @param {Number} row The row to get tokens at
*
*
*
**/
this.getTokens = function(row) {
return this.lines[row] || this.$tokenizeRow(row);
};
/**
* [Returns the state of tokenization at the end of a row.]{: #BackgroundTokenizer.getState}
*
* @param {Number} row The row to get state at
**/
this.getState = function(row) {
if (this.currentLine == row)
this.$tokenizeRow(row);
return this.states[row] || "start";
};
this.$tokenizeRow = function(row) {
var line = this.doc.getLine(row);
var state = this.states[row - 1];
var data = this.tokenizer.getLineTokens(line, state, row);
if (this.states[row] + "" !== data.state + "") {
this.states[row] = data.state;
this.lines[row + 1] = null;
if (this.currentLine > row + 1)
this.currentLine = row + 1;
} else if (this.currentLine == row) {
this.currentLine = row + 1;
}
return this.lines[row] = data.tokens;
};
}).call(BackgroundTokenizer.prototype);
exports.BackgroundTokenizer = BackgroundTokenizer;
});
@@ -1,85 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var EditSession = require("./edit_session").EditSession;
var JavaScriptMode = require("./mode/javascript").Mode;
var Range = require("./range").Range;
var assert = require("./test/assertions");
function forceTokenize(session){
for (var i = 0, l = session.getLength(); i < l; i++)
session.getTokens(i)
}
function testStates(session, states) {
for (var i = 0, l = session.getLength(); i < l; i++)
assert.equal(session.bgTokenizer.states[i], states[i])
assert.ok(l == states.length)
}
module.exports = {
"test background tokenizer update on session change" : function() {
var doc = new EditSession([
"/*",
"*/",
"var juhu"
]);
doc.setMode("./mode/javascript")
forceTokenize(doc)
testStates(doc, ["comment_regex_allowed", "start", "no_regex"])
doc.remove(new Range(0,2,1,2))
testStates(doc, [null, "no_regex"])
forceTokenize(doc)
testStates(doc, ["comment_regex_allowed", "comment_regex_allowed"])
doc.insert({row:0, column:2}, "\n*/")
testStates(doc, [undefined, undefined, "comment_regex_allowed"])
forceTokenize(doc)
testStates(doc, ["comment_regex_allowed", "start", "no_regex"])
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,118 +0,0 @@
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var MultiHashHandler = require("../keyboard/hash_handler").MultiHashHandler;
var EventEmitter = require("../lib/event_emitter").EventEmitter;
/**
* @class CommandManager
*
**/
/**
* new CommandManager(platform, commands)
* @param {String} platform Identifier for the platform; must be either `"mac"` or `"win"`
* @param {Array} commands A list of commands
*
**/
var CommandManager = function(platform, commands) {
MultiHashHandler.call(this, commands, platform);
this.byName = this.commands;
this.setDefaultHandler("exec", function(e) {
return e.command.exec(e.editor, e.args || {});
});
};
oop.inherits(CommandManager, MultiHashHandler);
(function() {
oop.implement(this, EventEmitter);
this.exec = function(command, editor, args) {
if (Array.isArray(command)) {
for (var i = command.length; i--; ) {
if (this.exec(command[i], editor, args)) return true;
}
return false;
}
if (typeof command === "string")
command = this.commands[command];
if (!command)
return false;
if (editor && editor.$readOnly && !command.readOnly)
return false;
var e = {editor: editor, command: command, args: args};
e.returnValue = this._emit("exec", e);
this._signal("afterExec", e);
return e.returnValue === false ? false : true;
};
this.toggleRecording = function(editor) {
if (this.$inReplay)
return;
editor && editor._emit("changeStatus");
if (this.recording) {
this.macro.pop();
this.removeEventListener("exec", this.$addCommandToMacro);
if (!this.macro.length)
this.macro = this.oldMacro;
return this.recording = false;
}
if (!this.$addCommandToMacro) {
this.$addCommandToMacro = function(e) {
this.macro.push([e.command, e.args]);
}.bind(this);
}
this.oldMacro = this.macro;
this.macro = [];
this.on("exec", this.$addCommandToMacro);
return this.recording = true;
};
this.replay = function(editor) {
if (this.$inReplay || !this.macro)
return;
if (this.recording)
return this.toggleRecording(editor);
try {
this.$inReplay = true;
this.macro.forEach(function(x) {
if (typeof x == "string")
this.exec(x, editor);
else
this.exec(x[0], editor, x[1]);
}, this);
} finally {
this.$inReplay = false;
}
};
this.trimMacro = function(m) {
return m.map(function(x){
if (typeof x[0] != "string")
x[0] = x[0].name;
if (!x[1])
x = x[0];
return x;
});
};
}).call(CommandManager.prototype);
exports.CommandManager = CommandManager;
});
@@ -1,199 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var CommandManager = require("./command_manager").CommandManager;
var keys = require("../lib/keys");
var assert = require("../test/assertions");
module.exports = {
setUp: function() {
this.command = {
name: "gotoline",
bindKey: {
mac: "Command-L",
win: "Ctrl-L"
},
called: false,
exec: function(editor) { this.called = true; }
};
this.cm = new CommandManager("mac", [this.command]);
},
"test: register command": function() {
this.cm.exec("gotoline");
assert.ok(this.command.called);
},
"test: mac hotkeys": function() {
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "l");
assert.equal(command, this.command);
var command = this.cm.findKeyCommand(keys.KEY_MODS.ctrl, "l");
assert.equal(command, undefined);
},
"test: win hotkeys": function() {
var cm = new CommandManager("win", [this.command]);
var command = cm.findKeyCommand(keys.KEY_MODS.command, "l");
assert.equal(command, undefined);
var command = cm.findKeyCommand(keys.KEY_MODS.ctrl, "l");
assert.equal(command, this.command);
},
"test: remove command by object": function() {
this.cm.removeCommand(this.command);
this.cm.exec("gotoline");
assert.ok(!this.command.called);
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "l");
assert.equal(command, null);
},
"test: remove command by name": function() {
this.cm.removeCommand("gotoline");
this.cm.exec("gotoline");
assert.ok(!this.command.called);
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "l");
assert.equal(command, null);
},
"test: adding a new command with the same name as an existing one should remove the old one first": function() {
var command = {
name: "gotoline",
bindKey: {
mac: "Command-L",
win: "Ctrl-L"
},
called: false,
exec: function(editor) { this.called = true; }
};
this.cm.addCommand(command);
this.cm.exec("gotoline");
assert.ok(command.called);
assert.ok(!this.command.called);
assert.equal(this.cm.findKeyCommand(keys.KEY_MODS.command, "l"), command);
},
"test: adding commands and recording a macro": function() {
var called = "";
this.cm.addCommands({
togglerecording: function(editor) {
editor.cm.toggleRecording(editor);
},
replay: function(editor) {
editor.cm.replay();
},
cm1: function(editor, arg) {
called += "1" + (arg || "");
},
cm2: function(editor) {
called += "2";
}
});
var statusUpdateEmitted = false;
this._emit = function() {statusUpdateEmitted = true};
this.cm.exec("togglerecording", this);
assert.ok(this.cm.recording);
assert.ok(statusUpdateEmitted);
this.cm.exec("cm1", this, "-");
this.cm.exec("cm2");
this.cm.exec("replay", this);
assert.ok(!this.cm.recording);
assert.equal(called, "1-2");
called = "";
this.cm.exec("replay", this);
assert.equal(called, "1-2");
},
"test: bindkeys": function() {
this.cm.bindKeys({
"Ctrl-L|Command-C": "cm1",
"Ctrl-R": "cm2"
});
var command = this.cm.findKeyCommand(keys.KEY_MODS.command, "c");
assert.equal(command, "cm1");
var command = this.cm.findKeyCommand(keys.KEY_MODS.ctrl, "r");
assert.equal(command, "cm2");
this.cm.bindKeys({
"Ctrl-R": null
});
var command = this.cm.findKeyCommand(keys.KEY_MODS.ctrl, "r");
assert.equal(command, null);
},
"test: binding keys without modifiers": function() {
this.cm.bindKeys({
"R": "cm1",
"Shift-r": "cm2",
"Return": "cm4",
"Enter": "cm3"
});
var command = this.cm.findKeyCommand(-1, "r");
assert.equal(command, "cm1");
var command = this.cm.findKeyCommand(-1, "R");
assert.equal(command, "cm2");
var command = this.cm.findKeyCommand(0, "return");
assert.equal(command + "", ["cm4", "cm3"] + "");
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec();
}
@@ -1,721 +0,0 @@
/* ***** 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 lang = require("../lib/lang");
var config = require("../config");
var Range = require("../range").Range;
function bindKey(win, mac) {
return {win: win, mac: mac};
}
/*
multiSelectAction: "forEach"|"forEachLine"|function|undefined,
scrollIntoView: true|"cursor"|"center"|"selectionPart"
*/
exports.commands = [{
name: "showSettingsMenu",
bindKey: bindKey("Ctrl-,", "Command-,"),
exec: function(editor) {
config.loadModule("ace/ext/settings_menu", function(module) {
module.init(editor);
editor.showSettingsMenu();
});
},
readOnly: true
}, {
name: "goToNextError",
bindKey: bindKey("Alt-E", "Ctrl-E"),
exec: function(editor) {
config.loadModule("ace/ext/error_marker", function(module) {
module.showErrorMarker(editor, 1);
});
},
scrollIntoView: "animate",
readOnly: true
}, {
name: "goToPreviousError",
bindKey: bindKey("Alt-Shift-E", "Ctrl-Shift-E"),
exec: function(editor) {
config.loadModule("ace/ext/error_marker", function(module) {
module.showErrorMarker(editor, -1);
});
},
scrollIntoView: "animate",
readOnly: true
}, {
name: "selectall",
bindKey: bindKey("Ctrl-A", "Command-A"),
exec: function(editor) { editor.selectAll(); },
readOnly: true
}, {
name: "centerselection",
bindKey: bindKey(null, "Ctrl-L"),
exec: function(editor) { editor.centerSelection(); },
readOnly: true
}, {
name: "gotoline",
bindKey: bindKey("Ctrl-L", "Command-L"),
exec: function(editor) {
var line = parseInt(prompt("Enter line number:"), 10);
if (!isNaN(line)) {
editor.gotoLine(line);
}
},
readOnly: true
}, {
name: "fold",
bindKey: bindKey("Alt-L|Ctrl-F1", "Command-Alt-L|Command-F1"),
exec: function(editor) { editor.session.toggleFold(false); },
scrollIntoView: "center",
readOnly: true
}, {
name: "unfold",
bindKey: bindKey("Alt-Shift-L|Ctrl-Shift-F1", "Command-Alt-Shift-L|Command-Shift-F1"),
exec: function(editor) { editor.session.toggleFold(true); },
scrollIntoView: "center",
readOnly: true
}, {
name: "toggleFoldWidget",
bindKey: bindKey("F2", "F2"),
exec: function(editor) { editor.session.toggleFoldWidget(); },
scrollIntoView: "center",
readOnly: true
}, {
name: "toggleParentFoldWidget",
bindKey: bindKey("Alt-F2", "Alt-F2"),
exec: function(editor) { editor.session.toggleFoldWidget(true); },
scrollIntoView: "center",
readOnly: true
}, {
name: "foldall",
bindKey: bindKey("Ctrl-Alt-0", "Ctrl-Command-Option-0"),
exec: function(editor) { editor.session.foldAll(); },
scrollIntoView: "center",
readOnly: true
}, {
name: "foldOther",
bindKey: bindKey("Alt-0", "Command-Option-0"),
exec: function(editor) {
editor.session.foldAll();
editor.session.unfold(editor.selection.getAllRanges());
},
scrollIntoView: "center",
readOnly: true
}, {
name: "unfoldall",
bindKey: bindKey("Alt-Shift-0", "Command-Option-Shift-0"),
exec: function(editor) { editor.session.unfold(); },
scrollIntoView: "center",
readOnly: true
}, {
name: "findnext",
bindKey: bindKey("Ctrl-K", "Command-G"),
exec: function(editor) { editor.findNext(); },
multiSelectAction: "forEach",
scrollIntoView: "center",
readOnly: true
}, {
name: "findprevious",
bindKey: bindKey("Ctrl-Shift-K", "Command-Shift-G"),
exec: function(editor) { editor.findPrevious(); },
multiSelectAction: "forEach",
scrollIntoView: "center",
readOnly: true
}, {
name: "selectOrFindNext",
bindKey: bindKey("Alt-K", "Ctrl-G"),
exec: function(editor) {
if (editor.selection.isEmpty())
editor.selection.selectWord();
else
editor.findNext();
},
readOnly: true
}, {
name: "selectOrFindPrevious",
bindKey: bindKey("Alt-Shift-K", "Ctrl-Shift-G"),
exec: function(editor) {
if (editor.selection.isEmpty())
editor.selection.selectWord();
else
editor.findPrevious();
},
readOnly: true
}, {
name: "find",
bindKey: bindKey("Ctrl-F", "Command-F"),
exec: function(editor) {
config.loadModule("ace/ext/searchbox", function(e) {e.Search(editor)});
},
readOnly: true
}, {
name: "overwrite",
bindKey: "Insert",
exec: function(editor) { editor.toggleOverwrite(); },
readOnly: true
}, {
name: "selecttostart",
bindKey: bindKey("Ctrl-Shift-Home", "Command-Shift-Up"),
exec: function(editor) { editor.getSelection().selectFileStart(); },
multiSelectAction: "forEach",
readOnly: true,
scrollIntoView: "animate",
aceCommandGroup: "fileJump"
}, {
name: "gotostart",
bindKey: bindKey("Ctrl-Home", "Command-Home|Command-Up"),
exec: function(editor) { editor.navigateFileStart(); },
multiSelectAction: "forEach",
readOnly: true,
scrollIntoView: "animate",
aceCommandGroup: "fileJump"
}, {
name: "selectup",
bindKey: bindKey("Shift-Up", "Shift-Up"),
exec: function(editor) { editor.getSelection().selectUp(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "golineup",
bindKey: bindKey("Up", "Up|Ctrl-P"),
exec: function(editor, args) { editor.navigateUp(args.times); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selecttoend",
bindKey: bindKey("Ctrl-Shift-End", "Command-Shift-Down"),
exec: function(editor) { editor.getSelection().selectFileEnd(); },
multiSelectAction: "forEach",
readOnly: true,
scrollIntoView: "animate",
aceCommandGroup: "fileJump"
}, {
name: "gotoend",
bindKey: bindKey("Ctrl-End", "Command-End|Command-Down"),
exec: function(editor) { editor.navigateFileEnd(); },
multiSelectAction: "forEach",
readOnly: true,
scrollIntoView: "animate",
aceCommandGroup: "fileJump"
}, {
name: "selectdown",
bindKey: bindKey("Shift-Down", "Shift-Down"),
exec: function(editor) { editor.getSelection().selectDown(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "golinedown",
bindKey: bindKey("Down", "Down|Ctrl-N"),
exec: function(editor, args) { editor.navigateDown(args.times); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selectwordleft",
bindKey: bindKey("Ctrl-Shift-Left", "Option-Shift-Left"),
exec: function(editor) { editor.getSelection().selectWordLeft(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "gotowordleft",
bindKey: bindKey("Ctrl-Left", "Option-Left"),
exec: function(editor) { editor.navigateWordLeft(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selecttolinestart",
bindKey: bindKey("Alt-Shift-Left", "Command-Shift-Left"),
exec: function(editor) { editor.getSelection().selectLineStart(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "gotolinestart",
bindKey: bindKey("Alt-Left|Home", "Command-Left|Home|Ctrl-A"),
exec: function(editor) { editor.navigateLineStart(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selectleft",
bindKey: bindKey("Shift-Left", "Shift-Left"),
exec: function(editor) { editor.getSelection().selectLeft(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "gotoleft",
bindKey: bindKey("Left", "Left|Ctrl-B"),
exec: function(editor, args) { editor.navigateLeft(args.times); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selectwordright",
bindKey: bindKey("Ctrl-Shift-Right", "Option-Shift-Right"),
exec: function(editor) { editor.getSelection().selectWordRight(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "gotowordright",
bindKey: bindKey("Ctrl-Right", "Option-Right"),
exec: function(editor) { editor.navigateWordRight(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selecttolineend",
bindKey: bindKey("Alt-Shift-Right", "Command-Shift-Right"),
exec: function(editor) { editor.getSelection().selectLineEnd(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "gotolineend",
bindKey: bindKey("Alt-Right|End", "Command-Right|End|Ctrl-E"),
exec: function(editor) { editor.navigateLineEnd(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selectright",
bindKey: bindKey("Shift-Right", "Shift-Right"),
exec: function(editor) { editor.getSelection().selectRight(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "gotoright",
bindKey: bindKey("Right", "Right|Ctrl-F"),
exec: function(editor, args) { editor.navigateRight(args.times); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selectpagedown",
bindKey: "Shift-PageDown",
exec: function(editor) { editor.selectPageDown(); },
readOnly: true
}, {
name: "pagedown",
bindKey: bindKey(null, "Option-PageDown"),
exec: function(editor) { editor.scrollPageDown(); },
readOnly: true
}, {
name: "gotopagedown",
bindKey: bindKey("PageDown", "PageDown|Ctrl-V"),
exec: function(editor) { editor.gotoPageDown(); },
readOnly: true
}, {
name: "selectpageup",
bindKey: "Shift-PageUp",
exec: function(editor) { editor.selectPageUp(); },
readOnly: true
}, {
name: "pageup",
bindKey: bindKey(null, "Option-PageUp"),
exec: function(editor) { editor.scrollPageUp(); },
readOnly: true
}, {
name: "gotopageup",
bindKey: "PageUp",
exec: function(editor) { editor.gotoPageUp(); },
readOnly: true
}, {
name: "scrollup",
bindKey: bindKey("Ctrl-Up", null),
exec: function(e) { e.renderer.scrollBy(0, -2 * e.renderer.layerConfig.lineHeight); },
readOnly: true
}, {
name: "scrolldown",
bindKey: bindKey("Ctrl-Down", null),
exec: function(e) { e.renderer.scrollBy(0, 2 * e.renderer.layerConfig.lineHeight); },
readOnly: true
}, {
name: "selectlinestart",
bindKey: "Shift-Home",
exec: function(editor) { editor.getSelection().selectLineStart(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "selectlineend",
bindKey: "Shift-End",
exec: function(editor) { editor.getSelection().selectLineEnd(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "togglerecording",
bindKey: bindKey("Ctrl-Alt-E", "Command-Option-E"),
exec: function(editor) { editor.commands.toggleRecording(editor); },
readOnly: true
}, {
name: "replaymacro",
bindKey: bindKey("Ctrl-Shift-E", "Command-Shift-E"),
exec: function(editor) { editor.commands.replay(editor); },
readOnly: true
}, {
name: "jumptomatching",
bindKey: bindKey("Ctrl-P", "Ctrl-P"),
exec: function(editor) { editor.jumpToMatching(); },
multiSelectAction: "forEach",
scrollIntoView: "animate",
readOnly: true
}, {
name: "selecttomatching",
bindKey: bindKey("Ctrl-Shift-P", "Ctrl-Shift-P"),
exec: function(editor) { editor.jumpToMatching(true); },
multiSelectAction: "forEach",
scrollIntoView: "animate",
readOnly: true
}, {
name: "expandToMatching",
bindKey: bindKey("Ctrl-Shift-M", "Ctrl-Shift-M"),
exec: function(editor) { editor.jumpToMatching(true, true); },
multiSelectAction: "forEach",
scrollIntoView: "animate",
readOnly: true
}, {
name: "passKeysToBrowser",
bindKey: bindKey("null", "null"),
exec: function() {},
passEvent: true,
readOnly: true
},
// commands disabled in readOnly mode
{
name: "cut",
exec: function(editor) {
var range = editor.getSelectionRange();
editor._emit("cut", range);
if (!editor.selection.isEmpty()) {
editor.session.remove(range);
editor.clearSelection();
}
},
scrollIntoView: "cursor",
multiSelectAction: "forEach"
}, {
name: "removeline",
bindKey: bindKey("Ctrl-D", "Command-D"),
exec: function(editor) { editor.removeLines(); },
scrollIntoView: "cursor",
multiSelectAction: "forEachLine"
}, {
name: "duplicateSelection",
bindKey: bindKey("Ctrl-Shift-D", "Command-Shift-D"),
exec: function(editor) { editor.duplicateSelection(); },
scrollIntoView: "cursor",
multiSelectAction: "forEach"
}, {
name: "sortlines",
bindKey: bindKey("Ctrl-Alt-S", "Command-Alt-S"),
exec: function(editor) { editor.sortLines(); },
scrollIntoView: "selection",
multiSelectAction: "forEachLine"
}, {
name: "togglecomment",
bindKey: bindKey("Ctrl-/", "Command-/"),
exec: function(editor) { editor.toggleCommentLines(); },
multiSelectAction: "forEachLine",
scrollIntoView: "selectionPart"
}, {
name: "toggleBlockComment",
bindKey: bindKey("Ctrl-Shift-/", "Command-Shift-/"),
exec: function(editor) { editor.toggleBlockComment(); },
multiSelectAction: "forEach",
scrollIntoView: "selectionPart"
}, {
name: "modifyNumberUp",
bindKey: bindKey("Ctrl-Shift-Up", "Alt-Shift-Up"),
exec: function(editor) { editor.modifyNumber(1); },
scrollIntoView: "cursor",
multiSelectAction: "forEach"
}, {
name: "modifyNumberDown",
bindKey: bindKey("Ctrl-Shift-Down", "Alt-Shift-Down"),
exec: function(editor) { editor.modifyNumber(-1); },
scrollIntoView: "cursor",
multiSelectAction: "forEach"
}, {
name: "replace",
bindKey: bindKey("Ctrl-H", "Command-Option-F"),
exec: function(editor) {
config.loadModule("ace/ext/searchbox", function(e) {e.Search(editor, true)});
}
}, {
name: "undo",
bindKey: bindKey("Ctrl-Z", "Command-Z"),
exec: function(editor) { editor.undo(); }
}, {
name: "redo",
bindKey: bindKey("Ctrl-Shift-Z|Ctrl-Y", "Command-Shift-Z|Command-Y"),
exec: function(editor) { editor.redo(); }
}, {
name: "copylinesup",
bindKey: bindKey("Alt-Shift-Up", "Command-Option-Up"),
exec: function(editor) { editor.copyLinesUp(); },
scrollIntoView: "cursor"
}, {
name: "movelinesup",
bindKey: bindKey("Alt-Up", "Option-Up"),
exec: function(editor) { editor.moveLinesUp(); },
scrollIntoView: "cursor"
}, {
name: "copylinesdown",
bindKey: bindKey("Alt-Shift-Down", "Command-Option-Down"),
exec: function(editor) { editor.copyLinesDown(); },
scrollIntoView: "cursor"
}, {
name: "movelinesdown",
bindKey: bindKey("Alt-Down", "Option-Down"),
exec: function(editor) { editor.moveLinesDown(); },
scrollIntoView: "cursor"
}, {
name: "del",
bindKey: bindKey("Delete", "Delete|Ctrl-D|Shift-Delete"),
exec: function(editor) { editor.remove("right"); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "backspace",
bindKey: bindKey(
"Shift-Backspace|Backspace",
"Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"
),
exec: function(editor) { editor.remove("left"); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "cut_or_delete",
bindKey: bindKey("Shift-Delete", null),
exec: function(editor) {
if (editor.selection.isEmpty()) {
editor.remove("left");
} else {
return false;
}
},
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "removetolinestart",
bindKey: bindKey("Alt-Backspace", "Command-Backspace"),
exec: function(editor) { editor.removeToLineStart(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "removetolineend",
bindKey: bindKey("Alt-Delete", "Ctrl-K"),
exec: function(editor) { editor.removeToLineEnd(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "removewordleft",
bindKey: bindKey("Ctrl-Backspace", "Alt-Backspace|Ctrl-Alt-Backspace"),
exec: function(editor) { editor.removeWordLeft(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "removewordright",
bindKey: bindKey("Ctrl-Delete", "Alt-Delete"),
exec: function(editor) { editor.removeWordRight(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "outdent",
bindKey: bindKey("Shift-Tab", "Shift-Tab"),
exec: function(editor) { editor.blockOutdent(); },
multiSelectAction: "forEach",
scrollIntoView: "selectionPart"
}, {
name: "indent",
bindKey: bindKey("Tab", "Tab"),
exec: function(editor) { editor.indent(); },
multiSelectAction: "forEach",
scrollIntoView: "selectionPart"
}, {
name: "blockoutdent",
bindKey: bindKey("Ctrl-[", "Ctrl-["),
exec: function(editor) { editor.blockOutdent(); },
multiSelectAction: "forEachLine",
scrollIntoView: "selectionPart"
}, {
name: "blockindent",
bindKey: bindKey("Ctrl-]", "Ctrl-]"),
exec: function(editor) { editor.blockIndent(); },
multiSelectAction: "forEachLine",
scrollIntoView: "selectionPart"
}, {
name: "insertstring",
exec: function(editor, str) { editor.insert(str); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "inserttext",
exec: function(editor, args) {
editor.insert(lang.stringRepeat(args.text || "", args.times || 1));
},
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "splitline",
bindKey: bindKey(null, "Ctrl-O"),
exec: function(editor) { editor.splitLine(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "transposeletters",
bindKey: bindKey("Ctrl-T", "Ctrl-T"),
exec: function(editor) { editor.transposeLetters(); },
multiSelectAction: function(editor) {editor.transposeSelections(1); },
scrollIntoView: "cursor"
}, {
name: "touppercase",
bindKey: bindKey("Ctrl-U", "Ctrl-U"),
exec: function(editor) { editor.toUpperCase(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "tolowercase",
bindKey: bindKey("Ctrl-Shift-U", "Ctrl-Shift-U"),
exec: function(editor) { editor.toLowerCase(); },
multiSelectAction: "forEach",
scrollIntoView: "cursor"
}, {
name: "expandtoline",
bindKey: bindKey("Ctrl-Shift-L", "Command-Shift-L"),
exec: function(editor) {
var range = editor.selection.getRange();
range.start.column = range.end.column = 0;
range.end.row++;
editor.selection.setRange(range, false);
},
multiSelectAction: "forEach",
scrollIntoView: "cursor",
readOnly: true
}, {
name: "joinlines",
bindKey: bindKey(null, null),
exec: function(editor) {
var isBackwards = editor.selection.isBackwards();
var selectionStart = isBackwards ? editor.selection.getSelectionLead() : editor.selection.getSelectionAnchor();
var selectionEnd = isBackwards ? editor.selection.getSelectionAnchor() : editor.selection.getSelectionLead();
var firstLineEndCol = editor.session.doc.getLine(selectionStart.row).length;
var selectedText = editor.session.doc.getTextRange(editor.selection.getRange());
var selectedCount = selectedText.replace(/\n\s*/, " ").length;
var insertLine = editor.session.doc.getLine(selectionStart.row);
for (var i = selectionStart.row + 1; i <= selectionEnd.row + 1; i++) {
var curLine = lang.stringTrimLeft(lang.stringTrimRight(editor.session.doc.getLine(i)));
if (curLine.length !== 0) {
curLine = " " + curLine;
}
insertLine += curLine;
}
if (selectionEnd.row + 1 < (editor.session.doc.getLength() - 1)) {
// Don't insert a newline at the end of the document
insertLine += editor.session.doc.getNewLineCharacter();
}
editor.clearSelection();
editor.session.doc.replace(new Range(selectionStart.row, 0, selectionEnd.row + 2, 0), insertLine);
if (selectedCount > 0) {
// Select the text that was previously selected
editor.selection.moveCursorTo(selectionStart.row, selectionStart.column);
editor.selection.selectTo(selectionStart.row, selectionStart.column + selectedCount);
} else {
// If the joined line had something in it, start the cursor at that something
firstLineEndCol = editor.session.doc.getLine(selectionStart.row).length > firstLineEndCol ? (firstLineEndCol + 1) : firstLineEndCol;
editor.selection.moveCursorTo(selectionStart.row, firstLineEndCol);
}
},
multiSelectAction: "forEach",
readOnly: true
}, {
name: "invertSelection",
bindKey: bindKey(null, null),
exec: function(editor) {
var endRow = editor.session.doc.getLength() - 1;
var endCol = editor.session.doc.getLine(endRow).length;
var ranges = editor.selection.rangeList.ranges;
var newRanges = [];
// If multiple selections don't exist, rangeList will return 0 so replace with single range
if (ranges.length < 1) {
ranges = [editor.selection.getRange()];
}
for (var i = 0; i < ranges.length; i++) {
if (i == (ranges.length - 1)) {
// The last selection must connect to the end of the document, unless it already does
if (!(ranges[i].end.row === endRow && ranges[i].end.column === endCol)) {
newRanges.push(new Range(ranges[i].end.row, ranges[i].end.column, endRow, endCol));
}
}
if (i === 0) {
// The first selection must connect to the start of the document, unless it already does
if (!(ranges[i].start.row === 0 && ranges[i].start.column === 0)) {
newRanges.push(new Range(0, 0, ranges[i].start.row, ranges[i].start.column));
}
} else {
newRanges.push(new Range(ranges[i-1].end.row, ranges[i-1].end.column, ranges[i].start.row, ranges[i].start.column));
}
}
editor.exitMultiSelectMode();
editor.clearSelection();
for(var i = 0; i < newRanges.length; i++) {
editor.selection.addRange(newRanges[i], false);
}
},
readOnly: true,
scrollIntoView: "none"
}];
});
@@ -1,232 +0,0 @@
/* ***** 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) {
var config = require("../config");
var oop = require("../lib/oop");
var HashHandler = require("../keyboard/hash_handler").HashHandler;
var occurStartCommand = require("./occur_commands").occurStartCommand;
// These commands can be installed in a normal key handler to start iSearch:
exports.iSearchStartCommands = [{
name: "iSearch",
bindKey: {win: "Ctrl-F", mac: "Command-F"},
exec: function(editor, options) {
config.loadModule(["core", "ace/incremental_search"], function(e) {
var iSearch = e.iSearch = e.iSearch || new e.IncrementalSearch();
iSearch.activate(editor, options.backwards);
if (options.jumpToFirstMatch) iSearch.next(options);
});
},
readOnly: true
}, {
name: "iSearchBackwards",
exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {backwards: true}); },
readOnly: true
}, {
name: "iSearchAndGo",
bindKey: {win: "Ctrl-K", mac: "Command-G"},
exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {jumpToFirstMatch: true, useCurrentOrPrevSearch: true}); },
readOnly: true
}, {
name: "iSearchBackwardsAndGo",
bindKey: {win: "Ctrl-Shift-K", mac: "Command-Shift-G"},
exec: function(editor) { editor.execCommand('iSearch', {jumpToFirstMatch: true, backwards: true, useCurrentOrPrevSearch: true}); },
readOnly: true
}];
// These commands are only available when incremental search mode is active:
exports.iSearchCommands = [{
name: "restartSearch",
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"},
exec: function(iSearch, options) {
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
}, {
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
}, {
name: 'cancelSearch',
bindKey: 'esc|Ctrl-G',
exec: function(iSearch) { iSearch.deactivate(true); },
readOnly: true,
isIncrementalSearchCommand: true
}, {
name: 'occurisearch',
bindKey: 'Ctrl-O',
exec: function(iSearch) {
var options = oop.mixin({}, iSearch.$options);
iSearch.deactivate();
occurStartCommand.exec(iSearch.$editor, options);
},
readOnly: true,
isIncrementalSearchCommand: true
}, {
name: "yankNextWord",
bindKey: "Ctrl-w",
exec: function(iSearch) {
var ed = iSearch.$editor,
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",
exec: function(iSearch) {
var ed = iSearch.$editor,
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
}, {
name: 'selectAllMatches',
bindKey: 'Ctrl-space',
exec: function(iSearch) {
var ed = iSearch.$editor,
hl = ed.session.$isearchHighlight,
ranges = hl && hl.cache ? hl.cache
.reduce(function(ranges, ea) {
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
}];
function IncrementalSearchKeyboardHandler(iSearch) {
this.$iSearch = iSearch;
}
oop.inherits(IncrementalSearchKeyboardHandler, HashHandler);
;(function() {
this.attach = function(editor) {
var iSearch = this.$iSearch;
HashHandler.call(this, exports.iSearchCommands, editor.commands.platform);
this.$commandExecHandler = editor.commands.addEventListener('exec', function(e) {
if (!e.command.isIncrementalSearchCommand) return undefined;
e.stopPropagation();
e.preventDefault();
return e.command.exec(iSearch, e.args || {});
});
}
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) {
if (((hashId === 1/*ctrl*/ || hashId === 8/*command*/) && key === 'v')
|| (hashId === 1/*ctrl*/ && key === 'y')) return null;
var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode);
if (cmd.command) { return cmd; }
if (hashId == -1) {
var extendCmd = this.commands.extendSearchTerm;
if (extendCmd) { return {command: extendCmd, args: key}; }
}
return {command: "null", passEvent: hashId == 0 || hashId == 4};
}
}).call(IncrementalSearchKeyboardHandler.prototype);
exports.IncrementalSearchKeyboardHandler = IncrementalSearchKeyboardHandler;
});
@@ -1,113 +0,0 @@
/* ***** 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) {
// commands to enter multiselect mode
exports.defaultCommands = [{
name: "addCursorAbove",
exec: function(editor) { editor.selectMoreLines(-1); },
bindKey: {win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "addCursorBelow",
exec: function(editor) { editor.selectMoreLines(1); },
bindKey: {win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "addCursorAboveSkipCurrent",
exec: function(editor) { editor.selectMoreLines(-1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "addCursorBelowSkipCurrent",
exec: function(editor) { editor.selectMoreLines(1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "selectMoreBefore",
exec: function(editor) { editor.selectMore(-1); },
bindKey: {win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "selectMoreAfter",
exec: function(editor) { editor.selectMore(1); },
bindKey: {win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "selectNextBefore",
exec: function(editor) { editor.selectMore(-1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "selectNextAfter",
exec: function(editor) { editor.selectMore(1, true); },
bindKey: {win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right"},
scrollIntoView: "cursor",
readonly: true
}, {
name: "splitIntoLines",
exec: function(editor) { editor.multiSelect.splitIntoLines(); },
bindKey: {win: "Ctrl-Alt-L", mac: "Ctrl-Alt-L"},
readonly: true
}, {
name: "alignCursors",
exec: function(editor) { editor.alignCursors(); },
bindKey: {win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A"},
scrollIntoView: "cursor"
}, {
name: "findAll",
exec: function(editor) { editor.findAll(); },
bindKey: {win: "Ctrl-Alt-K", mac: "Ctrl-Alt-G"},
scrollIntoView: "cursor",
readonly: true
}];
// commands active only in multiselect mode
exports.multiSelectCommands = [{
name: "singleSelection",
bindKey: "esc",
exec: function(editor) { editor.exitMultiSelectMode(); },
scrollIntoView: "cursor",
readonly: true,
isAvailable: function(editor) {return editor && editor.inMultiSelectMode}
}];
var HashHandler = require("../keyboard/hash_handler").HashHandler;
exports.keyboardHandler = new HashHandler(exports.multiSelectCommands);
});
@@ -1,110 +0,0 @@
/* ***** 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) {
var config = require("../config"),
Occur = require("../occur").Occur;
// These commands can be installed in a normal command handler to start occur:
var occurStartCommand = {
name: "occur",
exec: function(editor, options) {
var alreadyInOccur = !!editor.session.$occur;
var occurSessionActive = new Occur().enter(editor, options);
if (occurSessionActive && !alreadyInOccur)
OccurKeyboardHandler.installIn(editor);
},
readOnly: true
};
var occurCommands = [{
name: "occurexit",
bindKey: 'esc|Ctrl-G',
exec: function(editor) {
var occur = editor.session.$occur;
if (!occur) return;
occur.exit(editor, {});
if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor);
},
readOnly: true
}, {
name: "occuraccept",
bindKey: 'enter',
exec: function(editor) {
var occur = editor.session.$occur;
if (!occur) return;
occur.exit(editor, {translatePosition: true});
if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor);
},
readOnly: true
}];
var HashHandler = require("../keyboard/hash_handler").HashHandler;
var oop = require("../lib/oop");
function OccurKeyboardHandler() {}
oop.inherits(OccurKeyboardHandler, HashHandler);
;(function() {
this.isOccurHandler = true;
this.attach = function(editor) {
HashHandler.call(this, occurCommands, editor.commands.platform);
this.$editor = editor;
}
var handleKeyboard$super = this.handleKeyboard;
this.handleKeyboard = function(data, hashId, key, keyCode) {
var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode);
return (cmd && cmd.command) ? cmd : undefined;
}
}).call(OccurKeyboardHandler.prototype);
OccurKeyboardHandler.installIn = function(editor) {
var handler = new this();
editor.keyBinding.addKeyboardHandler(handler);
editor.commands.addCommands(occurCommands);
}
OccurKeyboardHandler.uninstallFrom = function(editor) {
editor.commands.removeCommands(occurCommands);
var handler = editor.getKeyboardHandler();
if (handler.isOccurHandler)
editor.keyBinding.removeKeyboardHandler(handler);
}
exports.occurStartCommand = occurStartCommand;
});
@@ -1,202 +0,0 @@
/* ***** 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) {
"no use strict";
var lang = require("./lib/lang");
var oop = require("./lib/oop");
var net = require("./lib/net");
var AppConfig = require("./lib/app_config").AppConfig;
module.exports = exports = new AppConfig();
var global = (function() {
return this;
})();
var options = {
packaged: false,
workerPath: null,
modePath: null,
themePath: null,
basePath: "",
suffix: ".js",
$moduleUrls: {}
};
exports.get = function(key) {
if (!options.hasOwnProperty(key))
throw new Error("Unknown config key: " + key);
return options[key];
};
exports.set = function(key, value) {
if (!options.hasOwnProperty(key))
throw new Error("Unknown config key: " + key);
options[key] = value;
};
exports.all = function() {
return lang.copyObject(options);
};
// module loading
exports.moduleUrl = function(name, component) {
if (options.$moduleUrls[name])
return options.$moduleUrls[name];
var parts = name.split("/");
component = component || parts[parts.length - 2] || "";
// todo make this configurable or get rid of '-'
var sep = component == "snippets" ? "/" : "-";
var base = parts[parts.length - 1];
if (component == "worker" && sep == "-") {
var re = new RegExp("^" + component + "[\\-_]|[\\-_]" + component + "$", "g");
base = base.replace(re, "");
}
if ((!base || base == component) && parts.length > 1)
base = parts[parts.length - 2];
var path = options[component + "Path"];
if (path == null) {
path = options.basePath;
} else if (sep == "/") {
component = sep = "";
}
if (path && path.slice(-1) != "/")
path += "/";
return path + component + sep + base + this.get("suffix");
};
exports.setModuleUrl = function(name, subst) {
return options.$moduleUrls[name] = subst;
};
exports.$loading = {};
exports.loadModule = function(moduleName, onLoad) {
var module, moduleType;
if (Array.isArray(moduleName)) {
moduleType = moduleName[0];
moduleName = moduleName[1];
}
try {
module = require(moduleName);
} catch (e) {}
// require(moduleName) can return empty object if called after require([moduleName], callback)
if (module && !exports.$loading[moduleName])
return onLoad && onLoad(module);
if (!exports.$loading[moduleName])
exports.$loading[moduleName] = [];
exports.$loading[moduleName].push(onLoad);
if (exports.$loading[moduleName].length > 1)
return;
var afterLoad = function() {
require([moduleName], function(module) {
exports._emit("load.module", {name: moduleName, module: module});
var listeners = exports.$loading[moduleName];
exports.$loading[moduleName] = null;
listeners.forEach(function(onLoad) {
onLoad && onLoad(module);
});
});
};
if (!exports.get("packaged"))
return afterLoad();
net.loadScript(exports.moduleUrl(moduleName, moduleType), afterLoad);
};
// initialization
function init(packaged) {
options.packaged = packaged || require.packaged || module.packaged || (global.define && define.packaged);
if (!global.document)
return "";
var scriptOptions = {};
var scriptUrl = "";
// Use currentScript.ownerDocument in case this file was loaded from imported document. (HTML Imports)
var currentScript = (document.currentScript || document._currentScript ); // native or polyfill
var currentDocument = currentScript && currentScript.ownerDocument || document;
var scripts = currentDocument.getElementsByTagName("script");
for (var i=0; i<scripts.length; i++) {
var script = scripts[i];
var src = script.src || script.getAttribute("src");
if (!src)
continue;
var attributes = script.attributes;
for (var j=0, l=attributes.length; j < l; j++) {
var attr = attributes[j];
if (attr.name.indexOf("data-ace-") === 0) {
scriptOptions[deHyphenate(attr.name.replace(/^data-ace-/, ""))] = attr.value;
}
}
var m = src.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/);
if (m)
scriptUrl = m[1];
}
if (scriptUrl) {
scriptOptions.base = scriptOptions.base || scriptUrl;
scriptOptions.packaged = true;
}
scriptOptions.basePath = scriptOptions.base;
scriptOptions.workerPath = scriptOptions.workerPath || scriptOptions.base;
scriptOptions.modePath = scriptOptions.modePath || scriptOptions.base;
scriptOptions.themePath = scriptOptions.themePath || scriptOptions.base;
delete scriptOptions.base;
for (var key in scriptOptions)
if (typeof scriptOptions[key] !== "undefined")
exports.set(key, scriptOptions[key]);
};
exports.init = init;
function deHyphenate(str) {
return str.replace(/-(.)/g, function(m, m1) { return m1.toUpperCase(); });
}
});
@@ -1,135 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var config = require("./config");
var assert = require("./test/assertions");
module.exports = {
"test: path resolution" : function() {
config.set("packaged", "true");
var url = config.moduleUrl("kr_theme", "theme");
assert.equal(url, "theme-kr_theme.js");
config.set("basePath", "a/b");
url = config.moduleUrl("m/theme", "theme");
assert.equal(url, "a/b/theme-m.js");
url = config.moduleUrl("m/theme", "ext");
assert.equal(url, "a/b/ext-theme.js");
config.set("workerPath", "c/");
url = config.moduleUrl("foo/1", "worker");
assert.equal(url, "c/worker-1.js");
config.setModuleUrl("foo/1", "a/b1.js");
url = config.moduleUrl("foo/1", "theme");
assert.equal(url, "a/b1.js");
url = config.moduleUrl("snippets/js");
assert.equal(url, "a/b/snippets/js.js");
config.setModuleUrl("snippets/js", "_.js");
url = config.moduleUrl("snippets/js");
assert.equal(url, "_.js");
url = config.moduleUrl("ace/ext/textarea");
assert.equal(url, "a/b/ext-textarea.js");
assert.equal();
},
"test: define options" : function() {
var o = {};
config.defineOptions(o, "test_object", {
opt1: {
set: function(val) {
this.x = val;
},
value: 7,
},
initialValue: {
set: function(val) {
this.x = val;
},
initialValue: 8,
},
opt2: {
get: function(val) {
return this.x;
}
},
forwarded: "model"
});
o.model = {};
config.defineOptions(o.model, "model", {
forwarded: {value: 1}
});
config.resetOptions(o);
config.resetOptions(o.model);
assert.equal(o.getOption("opt1"), 7);
assert.equal(o.getOption("opt2"), 7);
o.setOption("opt1", 8);
assert.equal(o.getOption("opt1"), 8);
assert.equal(o.getOption("opt2"), 8);
assert.equal(o.getOption("forwarded"), 1);
assert.equal(o.getOption("new"), undefined);
o.setOption("new", 0);
assert.equal(o.getOption("new"), undefined);
assert.equal(o.getOption("initialValue"), 8);
o.setOption("initialValue", 7);
assert.equal(o.getOption("opt2"), 7);
config.setDefaultValues("test_object", {
opt1: 1,
forwarded: 2
});
config.resetOptions(o);
assert.equal(o.getOption("opt1"), 1);
assert.equal(o.getOption("forwarded"), 2);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec();
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 B

@@ -1,422 +0,0 @@
.ace_editor {
position: relative;
overflow: hidden;
font: 12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
direction: ltr;
}
.ace_scroller {
position: absolute;
overflow: hidden;
top: 0;
bottom: 0;
background-color: inherit;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: text;
}
.ace_content {
position: absolute;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
min-width: 100%;
}
.ace_dragging .ace_scroller:before{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: '';
background: rgba(250, 250, 250, 0.01);
z-index: 1000;
}
.ace_dragging.ace_dark .ace_scroller:before{
background: rgba(0, 0, 0, 0.01);
}
.ace_selecting, .ace_selecting * {
cursor: text !important;
}
.ace_gutter {
position: absolute;
overflow : hidden;
width: auto;
top: 0;
bottom: 0;
left: 0;
cursor: default;
z-index: 4;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.ace_gutter-active-line {
position: absolute;
left: 0;
right: 0;
}
.ace_scroller.ace_scroll-left {
box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;
}
.ace_gutter-cell {
padding-left: 19px;
padding-right: 6px;
background-repeat: no-repeat;
}
.ace_gutter-cell.ace_error {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==");
background-repeat: no-repeat;
background-position: 2px center;
}
.ace_gutter-cell.ace_warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAmVBMVEX///8AAAD///8AAAAAAABPSzb/5sAAAAB/blH/73z/ulkAAAAAAAD85pkAAAAAAAACAgP/vGz/rkDerGbGrV7/pkQICAf////e0IsAAAD/oED/qTvhrnUAAAD/yHD/njcAAADuv2r/nz//oTj/p064oGf/zHAAAAA9Nir/tFIAAAD/tlTiuWf/tkIAAACynXEAAAAAAAAtIRW7zBpBAAAAM3RSTlMAABR1m7RXO8Ln31Z36zT+neXe5OzooRDfn+TZ4p3h2hTf4t3k3ucyrN1K5+Xaks52Sfs9CXgrAAAAjklEQVR42o3PbQ+CIBQFYEwboPhSYgoYunIqqLn6/z8uYdH8Vmdnu9vz4WwXgN/xTPRD2+sgOcZjsge/whXZgUaYYvT8QnuJaUrjrHUQreGczuEafQCO/SJTufTbroWsPgsllVhq3wJEk2jUSzX3CUEDJC84707djRc5MTAQxoLgupWRwW6UB5fS++NV8AbOZgnsC7BpEAAAAABJRU5ErkJggg==");
background-position: 2px center;
}
.ace_gutter-cell.ace_info {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAJ0Uk5TAAB2k804AAAAPklEQVQY02NgIB68QuO3tiLznjAwpKTgNyDbMegwisCHZUETUZV0ZqOquBpXj2rtnpSJT1AEnnRmL2OgGgAAIKkRQap2htgAAAAASUVORK5CYII=");
background-position: 2px center;
}
.ace_dark .ace_gutter-cell.ace_info {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAChoaGAgIAqKiq+vr6tra1ZWVmUlJSbm5s8PDxubm56enrdgzg3AAAAAXRSTlMAQObYZgAAAClJREFUeNpjYMAPdsMYHegyJZFQBlsUlMFVCWUYKkAZMxZAGdxlDMQBAG+TBP4B6RyJAAAAAElFTkSuQmCC");
}
.ace_scrollbar {
position: absolute;
right: 0;
bottom: 0;
z-index: 6;
}
.ace_scrollbar-inner {
position: absolute;
cursor: text;
left: 0;
top: 0;
}
.ace_scrollbar-v{
overflow-x: hidden;
overflow-y: scroll;
top: 0;
}
.ace_scrollbar-h {
overflow-x: scroll;
overflow-y: hidden;
left: 0;
}
.ace_print-margin {
position: absolute;
height: 100%;
}
.ace_text-input {
position: absolute;
z-index: 0;
width: 0.5em;
height: 1em;
opacity: 0;
background: transparent;
-moz-appearance: none;
appearance: none;
border: none;
resize: none;
outline: none;
overflow: hidden;
font: inherit;
padding: 0 1px;
margin: 0 -1px;
text-indent: -1em;
-ms-user-select: text;
-moz-user-select: text;
-webkit-user-select: text;
user-select: text;
}
.ace_text-input.ace_composition {
background: inherit;
color: inherit;
z-index: 1000;
opacity: 1;
text-indent: 0;
}
.ace_layer {
z-index: 1;
position: absolute;
overflow: hidden;
white-space: pre;
height: 100%;
width: 100%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
/* setting pointer-events: auto; on node under the mouse, which changes
during scroll, will break mouse wheel scrolling in Safari */
pointer-events: none;
}
.ace_gutter-layer {
position: relative;
width: auto;
text-align: right;
pointer-events: auto;
}
.ace_text-layer {
font: inherit !important;
}
.ace_cjk {
display: inline-block;
text-align: center;
}
.ace_cursor-layer {
z-index: 4;
}
.ace_cursor {
z-index: 4;
position: absolute;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border-left: 2px solid
}
.ace_slim-cursors .ace_cursor {
border-left-width: 1px;
}
.ace_overwrite-cursors .ace_cursor {
border-left-width: 0;
border-bottom: 1px solid;
}
.ace_hidden-cursors .ace_cursor {
opacity: 0.2;
}
.ace_smooth-blinking .ace_cursor {
-webkit-transition: opacity 0.18s;
transition: opacity 0.18s;
}
.ace_editor.ace_multiselect .ace_cursor {
border-left-width: 1px;
}
.ace_marker-layer .ace_step, .ace_marker-layer .ace_stack {
position: absolute;
z-index: 3;
}
.ace_marker-layer .ace_selection {
position: absolute;
z-index: 5;
}
.ace_marker-layer .ace_bracket {
position: absolute;
z-index: 6;
}
.ace_marker-layer .ace_active-line {
position: absolute;
z-index: 2;
}
.ace_marker-layer .ace_selected-word {
position: absolute;
z-index: 4;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.ace_line .ace_fold {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: inline-block;
height: 11px;
margin-top: -2px;
vertical-align: middle;
background-image:
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi+P//fxgTAwPDBxDxD078RSX+YeEyDFMCIMAAI3INmXiwf2YAAAAASUVORK5CYII=");
background-repeat: no-repeat, repeat-x;
background-position: center center, top left;
color: transparent;
border: 1px solid black;
border-radius: 2px;
cursor: pointer;
pointer-events: auto;
}
.ace_dark .ace_fold {
}
.ace_fold:hover{
background-image:
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi+P//fz4TAwPDZxDxD5X4i5fLMEwJgAADAEPVDbjNw87ZAAAAAElFTkSuQmCC");
}
.ace_tooltip {
background-color: #FFF;
background-image: -webkit-linear-gradient(top, transparent, rgba(0, 0, 0, 0.1));
background-image: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));
border: 1px solid gray;
border-radius: 1px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
color: black;
max-width: 100%;
padding: 3px 4px;
position: fixed;
z-index: 999999;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
cursor: default;
white-space: pre;
word-wrap: break-word;
line-height: normal;
font-style: normal;
font-weight: normal;
letter-spacing: normal;
pointer-events: none;
}
.ace_folding-enabled > .ace_gutter-cell {
padding-right: 13px;
}
.ace_fold-widget {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0 -12px 0 1px;
display: none;
width: 11px;
vertical-align: top;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==");
background-repeat: no-repeat;
background-position: center;
border-radius: 3px;
border: 1px solid transparent;
cursor: pointer;
}
.ace_folding-enabled .ace_fold-widget {
display: inline-block;
}
.ace_fold-widget.ace_end {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42m3HwQkAMAhD0YzsRchFKI7sAikeWkrxwScEB0nh5e7KTPWimZki4tYfVbX+MNl4pyZXejUO1QAAAABJRU5ErkJggg==");
}
.ace_fold-widget.ace_closed {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAGCAYAAAAG5SQMAAAAOUlEQVR42jXKwQkAMAgDwKwqKD4EwQ26sSOkVWjgIIHAzPiCgaqiqnJHZnKICBERHN194O5b9vbLuAVRL+l0YWnZAAAAAElFTkSuQmCCXA==");
}
.ace_fold-widget:hover {
border: 1px solid rgba(0, 0, 0, 0.3);
background-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);
}
.ace_fold-widget:active {
border: 1px solid rgba(0, 0, 0, 0.4);
background-color: rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);
}
/**
* Dark version for fold widgets
*/
.ace_dark .ace_fold-widget {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC");
}
.ace_dark .ace_fold-widget.ace_end {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==");
}
.ace_dark .ace_fold-widget.ace_closed {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==");
}
.ace_dark .ace_fold-widget:hover {
box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);
background-color: rgba(255, 255, 255, 0.1);
}
.ace_dark .ace_fold-widget:active {
box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);
}
.ace_fold-widget.ace_invalid {
background-color: #FFB4B4;
border-color: #DE5555;
}
.ace_fade-fold-widgets .ace_fold-widget {
-webkit-transition: opacity 0.4s ease 0.05s;
transition: opacity 0.4s ease 0.05s;
opacity: 0;
}
.ace_fade-fold-widgets:hover .ace_fold-widget {
-webkit-transition: opacity 0.05s ease 0.05s;
transition: opacity 0.05s ease 0.05s;
opacity:1;
}
.ace_underline {
text-decoration: underline;
}
.ace_bold {
font-weight: bold;
}
.ace_nobold .ace_bold {
font-weight: normal;
}
.ace_italic {
font-style: italic;
}
.ace_error-marker {
background-color: rgba(255, 0, 0,0.2);
position: absolute;
z-index: 9;
}
.ace_highlight-marker {
background-color: rgba(255, 255, 0,0.2);
position: absolute;
z-index: 8;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

@@ -1,645 +0,0 @@
/* ***** 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 EventEmitter = require("./lib/event_emitter").EventEmitter;
var Range = require("./range").Range;
var Anchor = require("./anchor").Anchor;
/**
* Contains the text of the document. Document can be attached to several [[EditSession `EditSession`]]s.
*
* At its core, `Document`s are just an array of strings, with each row in the document matching up to the array index.
*
* @class Document
**/
/**
*
* Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty.
* @param {String | Array} text The starting text
* @constructor
**/
var Document = function(text) {
this.$lines = [];
// There has to be one line at least in the document. If you pass an empty
// string to the insert function, nothing will happen. Workaround.
if (text.length === 0) {
this.$lines = [""];
} else if (Array.isArray(text)) {
this._insertLines(0, text);
} else {
this.insert({row: 0, column:0}, text);
}
};
(function() {
oop.implement(this, EventEmitter);
/**
* Replaces all the lines in the current `Document` with the value of `text`.
*
* @param {String} text The text to use
**/
this.setValue = function(text) {
var len = this.getLength();
this.remove(new Range(0, 0, len, this.getLine(len-1).length));
this.insert({row: 0, column:0}, text);
};
/**
* Returns all the lines in the document as a single string, joined by the new line character.
**/
this.getValue = function() {
return this.getAllLines().join(this.getNewLineCharacter());
};
/**
* Creates a new `Anchor` to define a floating point in the document.
* @param {Number} row The row number to use
* @param {Number} column The column number to use
*
**/
this.createAnchor = function(row, column) {
return new Anchor(this, row, column);
};
/**
* Splits a string of text on any newline (`\n`) or carriage-return ('\r') characters.
*
* @method $split
* @param {String} text The text to work with
* @returns {String} A String array, with each index containing a piece of the original `text` string.
*
**/
// check for IE split bug
if ("aaa".split(/a/).length === 0)
this.$split = function(text) {
return text.replace(/\r\n|\r/g, "\n").split("\n");
};
else
this.$split = function(text) {
return text.split(/\r\n|\r|\n/);
};
this.$detectNewLine = function(text) {
var match = text.match(/^.*?(\r\n|\r|\n)/m);
this.$autoNewLine = match ? match[1] : "\n";
this._signal("changeNewLineMode");
};
/**
* Returns the newline character that's being used, depending on the value of `newLineMode`.
* @returns {String} If `newLineMode == windows`, `\r\n` is returned.
* If `newLineMode == unix`, `\n` is returned.
* If `newLineMode == auto`, the value of `autoNewLine` is returned.
*
**/
this.getNewLineCharacter = function() {
switch (this.$newLineMode) {
case "windows":
return "\r\n";
case "unix":
return "\n";
default:
return this.$autoNewLine || "\n";
}
};
this.$autoNewLine = "";
this.$newLineMode = "auto";
/**
* [Sets the new line mode.]{: #Document.setNewLineMode.desc}
* @param {String} newLineMode [The newline mode to use; can be either `windows`, `unix`, or `auto`]{: #Document.setNewLineMode.param}
*
**/
this.setNewLineMode = function(newLineMode) {
if (this.$newLineMode === newLineMode)
return;
this.$newLineMode = newLineMode;
this._signal("changeNewLineMode");
};
/**
* [Returns the type of newlines being used; either `windows`, `unix`, or `auto`]{: #Document.getNewLineMode}
* @returns {String}
**/
this.getNewLineMode = function() {
return this.$newLineMode;
};
/**
* Returns `true` if `text` is a newline character (either `\r\n`, `\r`, or `\n`).
* @param {String} text The text to check
*
**/
this.isNewLine = function(text) {
return (text == "\r\n" || text == "\r" || text == "\n");
};
/**
* Returns a verbatim copy of the given line as it is in the document
* @param {Number} row The row index to retrieve
*
**/
this.getLine = function(row) {
return this.$lines[row] || "";
};
/**
* Returns an array of strings of the rows between `firstRow` and `lastRow`. This function is inclusive of `lastRow`.
* @param {Number} firstRow The first row index to retrieve
* @param {Number} lastRow The final row index to retrieve
*
**/
this.getLines = function(firstRow, lastRow) {
return this.$lines.slice(firstRow, lastRow + 1);
};
/**
* Returns all lines in the document as string array.
**/
this.getAllLines = function() {
return this.getLines(0, this.getLength());
};
/**
* Returns the number of rows in the document.
**/
this.getLength = function() {
return this.$lines.length;
};
/**
* [Given a range within the document, this function returns all the text within that range as a single string.]{: #Document.getTextRange.desc}
* @param {Range} range The range to work with
*
* @returns {String}
**/
this.getTextRange = function(range) {
if (range.start.row == range.end.row) {
return this.getLine(range.start.row)
.substring(range.start.column, range.end.column);
}
var lines = this.getLines(range.start.row, range.end.row);
lines[0] = (lines[0] || "").substring(range.start.column);
var l = lines.length - 1;
if (range.end.row - range.start.row == l)
lines[l] = lines[l].substring(0, range.end.column);
return lines.join(this.getNewLineCharacter());
};
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 if (position.row < 0)
position.row = 0;
return position;
};
/**
* Inserts a block of `text` at the indicated `position`.
* @param {Object} position The position to start inserting at; it's an object that looks like `{ row: row, column: column}`
* @param {String} text A chunk of text to insert
* @returns {Object} The position ({row, column}) of the last line of `text`. If the length of `text` is 0, this function simply returns `position`.
*
**/
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
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 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.
*
**/
/**
* Inserts the elements in `lines` into the document, 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:
* ```
* {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.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;
}
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,
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:<br/>
* ```
* {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;
};
/**
* Removes the `range` from the document.
* @param {Range} range A specified Range to remove
* @returns {Object} Returns the new `start` property of the range, which contains `startRow` and `startColumn`. If `range` is empty, this function returns the unmodified value of `range.start`.
*
**/
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;
};
/**
* 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.<br/>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;
};
/**
* 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;
};
/**
* 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 });
};
/**
* Replaces a range in the document with the new `text`.
* @param {Range} range A specified Range to replace
* @param {String} text The new text to use as a replacement
* @returns {Object} Returns an object containing the final row and column, like this:
* {row: endRow, column: 0}
* If the text and range are empty, this function returns an object containing the current `range.start` value.
* If the text is the exact same as what currently exists, this function returns an object containing the current `range.end` value.
*
**/
this.replace = function(range, text) {
if (!(range instanceof Range))
range = Range.fromPoints(range.start, range.end);
if (text.length == 0 && range.isEmpty())
return range.start;
// Shortcut: If the text we want to insert is the same as it is already
// in the document, we don't have to replace anything.
if (text == this.getTextRange(range))
return range.end;
this.remove(range);
if (text) {
var 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'`.
**/
this.applyDeltas = function(deltas) {
for (var i=0; i<deltas.length; i++) {
var delta = deltas[i];
var range = Range.fromPoints(delta.range.start, delta.range.end);
if (delta.action == "insertLines")
this.insertLines(range.start.row, delta.lines);
else if (delta.action == "insertText")
this.insert(range.start, delta.text);
else if (delta.action == "removeLines")
this._removeLines(range.start.row, range.end.row - 1);
else if (delta.action == "removeText")
this.remove(range);
}
};
/**
* Reverts any changes previously applied. These can be either `'insertText'`, `'insertLines'`, `'removeText'`, and `'removeLines'`.
**/
this.revertDeltas = function(deltas) {
for (var i=deltas.length-1; 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);
}
};
/**
* Converts an index position in a document to a `{row, column}` object.
*
* Index refers to the "absolute position" of a character in the document. For example:
*
* ```javascript
* var x = 0; // 10 characters, plus one for newline
* var y = -1;
* ```
*
* Here, `y` is an index 15: 11 characters for the first row, and 5 characters until `y` in the second.
*
* @param {Number} index An index to convert
* @param {Number} startRow=0 The row from which to start the conversion
* @returns {Object} A `{row, column}` object of the `index` position
*/
this.indexToPosition = function(index, startRow) {
var lines = this.$lines || this.getAllLines();
var newlineLength = this.getNewLineCharacter().length;
for (var i = startRow || 0, l = lines.length; i < l; i++) {
index -= lines[i].length + newlineLength;
if (index < 0)
return {row: i, column: index + lines[i].length + newlineLength};
}
return {row: l-1, column: lines[l-1].length};
};
/**
* Converts the `{row, column}` position in a document to the character's index.
*
* Index refers to the "absolute position" of a character in the document. For example:
*
* ```javascript
* var x = 0; // 10 characters, plus one for newline
* var y = -1;
* ```
*
* Here, `y` is an index 15: 11 characters for the first row, and 5 characters until `y` in the second.
*
* @param {Object} pos The `{row, column}` to convert
* @param {Number} startRow=0 The row from which to start the conversion
* @returns {Number} The index position in the document
*/
this.positionToIndex = function(pos, startRow) {
var lines = this.$lines || this.getAllLines();
var newlineLength = this.getNewLineCharacter().length;
var index = 0;
var row = Math.min(pos.row, lines.length);
for (var i = startRow || 0; i < row; ++i)
index += lines[i].length + newlineLength;
return index + pos.column;
};
}).call(Document.prototype);
exports.Document = Document;
});
@@ -1,306 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
require("./test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var Document = require("./document").Document;
var Range = require("./range").Range;
var assert = require("./test/assertions");
module.exports = {
"test: insert text in line" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insert({row: 0, column: 1}, "juhu");
assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n"));
},
"test: insert new line" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insertNewLine({row: 0, column: 1});
assert.equal(doc.getValue(), ["1", "2", "34"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["1", "2", "34"].join("\n"));
},
"test: insert lines at the beginning" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insertLines(0, ["aa", "bb"]);
assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n"));
},
"test: insert lines at the end" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insertLines(2, ["aa", "bb"]);
assert.equal(doc.getValue(), ["12", "34", "aa", "bb"].join("\n"));
},
"test: insert lines in the middle" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insertLines(1, ["aa", "bb"]);
assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n"));
},
"test: insert multi line string at the start" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insert({row: 0, column: 0}, "aa\nbb\ncc");
assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n"));
},
"test: insert multi line string at the end" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insert({row: 2, column: 0}, "aa\nbb\ncc");
assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n"));
},
"test: insert multi line string in the middle" : function() {
var doc = new Document(["12", "34"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.insert({row: 0, column: 1}, "aa\nbb\ncc");
assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n"));
},
"test: delete in line" : function() {
var doc = new Document(["1234", "5678"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.remove(new Range(0, 1, 0, 3));
assert.equal(doc.getValue(), ["14", "5678"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["1234", "5678"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["14", "5678"].join("\n"));
},
"test: delete new line" : function() {
var doc = new Document(["1234", "5678"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.remove(new Range(0, 4, 1, 0));
assert.equal(doc.getValue(), ["12345678"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["1234", "5678"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["12345678"].join("\n"));
},
"test: delete multi line range line" : function() {
var doc = new Document(["1234", "5678", "abcd"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.remove(new Range(0, 2, 2, 2));
assert.equal(doc.getValue(), ["12cd"].join("\n"));
var d = deltas.concat();
doc.revertDeltas(d);
assert.equal(doc.getValue(), ["1234", "5678", "abcd"].join("\n"));
doc.applyDeltas(d);
assert.equal(doc.getValue(), ["12cd"].join("\n"));
},
"test: delete full lines" : function() {
var doc = new Document(["1234", "5678", "abcd"]);
var deltas = [];
doc.on("change", function(e) { deltas.push(e.data); });
doc.remove(new Range(1, 0, 3, 0));
assert.equal(doc.getValue(), ["1234", ""].join("\n"));
},
"test: remove lines should return the removed lines" : function() {
var doc = new Document(["1234", "5678", "abcd"]);
var removed = doc.removeLines(1, 2);
assert.equal(removed.join("\n"), ["5678", "abcd"].join("\n"));
},
"test: should handle unix style new lines" : function() {
var doc = new Document(["1", "2", "3"]);
assert.equal(doc.getValue(), ["1", "2", "3"].join("\n"));
},
"test: should handle windows style new lines" : function() {
var doc = new Document(["1", "2", "3"].join("\r\n"));
doc.setNewLineMode("unix");
assert.equal(doc.getValue(), ["1", "2", "3"].join("\n"));
},
"test: set new line mode to 'windows' should use '\\r\\n' as new lines": function() {
var doc = new Document(["1", "2", "3"].join("\n"));
doc.setNewLineMode("windows");
assert.equal(doc.getValue(), ["1", "2", "3"].join("\r\n"));
},
"test: set new line mode to 'unix' should use '\\n' as new lines": function() {
var doc = new Document(["1", "2", "3"].join("\r\n"));
doc.setNewLineMode("unix");
assert.equal(doc.getValue(), ["1", "2", "3"].join("\n"));
},
"test: set new line mode to 'auto' should detect the incoming nl type": function() {
var doc = new Document(["1", "2", "3"].join("\n"));
doc.setNewLineMode("auto");
assert.equal(doc.getValue(), ["1", "2", "3"].join("\n"));
var doc = new Document(["1", "2", "3"].join("\r\n"));
doc.setNewLineMode("auto");
assert.equal(doc.getValue(), ["1", "2", "3"].join("\r\n"));
doc.replace(new Range(0, 0, 2, 1), ["4", "5", "6"].join("\n"));
assert.equal(["4", "5", "6"].join("\n"), doc.getValue());
},
"test: set value": function() {
var doc = new Document("1");
assert.equal("1", doc.getValue());
doc.setValue(doc.getValue());
assert.equal("1", doc.getValue());
var doc = new Document("1\n2");
assert.equal("1\n2", doc.getValue());
doc.setValue(doc.getValue());
assert.equal("1\n2", doc.getValue());
},
"test: empty document has to contain one line": function() {
var doc = new Document("");
assert.equal(doc.$lines.length, 1);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
File diff suppressed because it is too large Load Diff
@@ -1,221 +0,0 @@
/* ***** 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 TokenIterator = require("../token_iterator").TokenIterator;
var Range = require("../range").Range;
function BracketMatch() {
this.findMatchingBracket = function(position, chr) {
if (position.column == 0) return null;
var charBeforeCursor = chr || this.getLine(position.row).charAt(position.column-1);
if (charBeforeCursor == "") return null;
var match = charBeforeCursor.match(/([\(\[\{])|([\)\]\}])/);
if (!match)
return null;
if (match[1])
return this.$findClosingBracket(match[1], position);
else
return this.$findOpeningBracket(match[2], position);
};
this.getBracketRange = function(pos) {
var line = this.getLine(pos.row);
var before = true, range;
var chr = line.charAt(pos.column-1);
var match = chr && chr.match(/([\(\[\{])|([\)\]\}])/);
if (!match) {
chr = line.charAt(pos.column);
pos = {row: pos.row, column: pos.column + 1};
match = chr && chr.match(/([\(\[\{])|([\)\]\}])/);
before = false;
}
if (!match)
return null;
if (match[1]) {
var bracketPos = this.$findClosingBracket(match[1], pos);
if (!bracketPos)
return null;
range = Range.fromPoints(pos, bracketPos);
if (!before) {
range.end.column++;
range.start.column--;
}
range.cursor = range.end;
} else {
var bracketPos = this.$findOpeningBracket(match[2], pos);
if (!bracketPos)
return null;
range = Range.fromPoints(bracketPos, pos);
if (!before) {
range.start.column++;
range.end.column--;
}
range.cursor = range.start;
}
return range;
};
this.$brackets = {
")": "(",
"(": ")",
"]": "[",
"[": "]",
"{": "}",
"}": "{"
};
this.$findOpeningBracket = function(bracket, position, typeRe) {
var openBracket = this.$brackets[bracket];
var depth = 1;
var iterator = new TokenIterator(this, position.row, position.column);
var token = iterator.getCurrentToken();
if (!token)
token = iterator.stepForward();
if (!token)
return;
if (!typeRe){
typeRe = new RegExp(
"(\\.?" +
token.type.replace(".", "\\.").replace("rparen", ".paren")
.replace(/\b(?:end|start|begin)\b/, "")
+ ")+"
);
}
// Start searching in token, just before the character at position.column
var valueIndex = position.column - iterator.getCurrentTokenColumn() - 2;
var value = token.value;
while (true) {
while (valueIndex >= 0) {
var chr = value.charAt(valueIndex);
if (chr == openBracket) {
depth -= 1;
if (depth == 0) {
return {row: iterator.getCurrentTokenRow(),
column: valueIndex + iterator.getCurrentTokenColumn()};
}
}
else if (chr == bracket) {
depth += 1;
}
valueIndex -= 1;
}
// Scan backward through the document, looking for the next token
// whose type matches typeRe
do {
token = iterator.stepBackward();
} while (token && !typeRe.test(token.type));
if (token == null)
break;
value = token.value;
valueIndex = value.length - 1;
}
return null;
};
this.$findClosingBracket = function(bracket, position, typeRe) {
var closingBracket = this.$brackets[bracket];
var depth = 1;
var iterator = new TokenIterator(this, position.row, position.column);
var token = iterator.getCurrentToken();
if (!token)
token = iterator.stepForward();
if (!token)
return;
if (!typeRe){
typeRe = new RegExp(
"(\\.?" +
token.type.replace(".", "\\.").replace("lparen", ".paren")
.replace(/\b(?:end|start|begin)\b/, "")
+ ")+"
);
}
// Start searching in token, after the character at position.column
var valueIndex = position.column - iterator.getCurrentTokenColumn();
while (true) {
var value = token.value;
var valueLength = value.length;
while (valueIndex < valueLength) {
var chr = value.charAt(valueIndex);
if (chr == closingBracket) {
depth -= 1;
if (depth == 0) {
return {row: iterator.getCurrentTokenRow(),
column: valueIndex + iterator.getCurrentTokenColumn()};
}
}
else if (chr == bracket) {
depth += 1;
}
valueIndex += 1;
}
// Scan forward through the document, looking for the next token
// whose type matches typeRe
do {
token = iterator.stepForward();
} while (token && !typeRe.test(token.type));
if (token == null)
break;
valueIndex = 0;
}
return null;
};
}
exports.BracketMatch = BracketMatch;
});
@@ -1,140 +0,0 @@
/* ***** 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 Range = require("../range").Range;
var RangeList = require("../range_list").RangeList;
var oop = require("../lib/oop")
/*
* Simple fold-data struct.
**/
var Fold = exports.Fold = function(range, placeholder) {
this.foldLine = null;
this.placeholder = placeholder;
this.range = range;
this.start = range.start;
this.end = range.end;
this.sameRow = range.start.row == range.end.row;
this.subFolds = this.ranges = [];
};
oop.inherits(Fold, RangeList);
(function() {
this.toString = function() {
return '"' + this.placeholder + '" ' + this.range.toString();
};
this.setFoldLine = function(foldLine) {
this.foldLine = foldLine;
this.subFolds.forEach(function(fold) {
fold.setFoldLine(foldLine);
});
};
this.clone = function() {
var range = this.range.clone();
var fold = new Fold(range, this.placeholder);
this.subFolds.forEach(function(subFold) {
fold.subFolds.push(subFold.clone());
});
fold.collapseChildren = this.collapseChildren;
return fold;
};
this.addSubFold = function(fold) {
if (this.range.isEqual(fold))
return;
if (!this.range.containsRange(fold))
throw new Error("A fold can't intersect already existing fold" + fold.range + this.range);
// transform fold to local coordinates
consumeRange(fold, this.start);
var row = fold.start.row, column = fold.start.column;
for (var i = 0, cmp = -1; i < this.subFolds.length; i++) {
cmp = this.subFolds[i].range.compare(row, column);
if (cmp != 1)
break;
}
var afterStart = this.subFolds[i];
if (cmp == 0)
return afterStart.addSubFold(fold);
// cmp == -1
var row = fold.range.end.row, column = fold.range.end.column;
for (var j = i, cmp = -1; j < this.subFolds.length; j++) {
cmp = this.subFolds[j].range.compare(row, column);
if (cmp != 1)
break;
}
var afterEnd = this.subFolds[j];
if (cmp == 0)
throw new Error("A fold can't intersect already existing fold" + fold.range + this.range);
var consumedFolds = this.subFolds.splice(i, j - i, fold);
fold.setFoldLine(this.foldLine);
return fold;
};
this.restoreRange = function(range) {
return restoreRange(range, this.start);
};
}).call(Fold.prototype);
function consumePoint(point, anchor) {
point.row -= anchor.row;
if (point.row == 0)
point.column -= anchor.column;
}
function consumeRange(range, anchor) {
consumePoint(range.start, anchor);
consumePoint(range.end, anchor);
}
function restorePoint(point, anchor) {
if (point.row == 0)
point.column += anchor.column;
point.row += anchor.row;
}
function restoreRange(range, anchor) {
restorePoint(range.start, anchor);
restorePoint(range.end, anchor);
}
});
@@ -1,269 +0,0 @@
/* ***** 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 Range = require("../range").Range;
/*
* If an array is passed in, the folds are expected to be sorted already.
*/
function FoldLine(foldData, folds) {
this.foldData = foldData;
if (Array.isArray(folds)) {
this.folds = folds;
} else {
folds = this.folds = [ folds ];
}
var last = folds[folds.length - 1];
this.range = new Range(folds[0].start.row, folds[0].start.column,
last.end.row, last.end.column);
this.start = this.range.start;
this.end = this.range.end;
this.folds.forEach(function(fold) {
fold.setFoldLine(this);
}, this);
}
(function() {
/*
* Note: This doesn't update wrapData!
*/
this.shiftRow = function(shift) {
this.start.row += shift;
this.end.row += shift;
this.folds.forEach(function(fold) {
fold.start.row += shift;
fold.end.row += shift;
});
};
this.addFold = function(fold) {
if (fold.sameRow) {
if (fold.start.row < this.startRow || fold.endRow > this.endRow) {
throw new Error("Can't add a fold to this FoldLine as it has no connection");
}
this.folds.push(fold);
this.folds.sort(function(a, b) {
return -a.range.compareEnd(b.start.row, b.start.column);
});
if (this.range.compareEnd(fold.start.row, fold.start.column) > 0) {
this.end.row = fold.end.row;
this.end.column = fold.end.column;
} else if (this.range.compareStart(fold.end.row, fold.end.column) < 0) {
this.start.row = fold.start.row;
this.start.column = fold.start.column;
}
} else if (fold.start.row == this.end.row) {
this.folds.push(fold);
this.end.row = fold.end.row;
this.end.column = fold.end.column;
} else if (fold.end.row == this.start.row) {
this.folds.unshift(fold);
this.start.row = fold.start.row;
this.start.column = fold.start.column;
} else {
throw new Error("Trying to add fold to FoldRow that doesn't have a matching row");
}
fold.foldLine = this;
};
this.containsRow = function(row) {
return row >= this.start.row && row <= this.end.row;
};
this.walk = function(callback, endRow, endColumn) {
var lastEnd = 0,
folds = this.folds,
fold,
cmp, stop, isNewRow = true;
if (endRow == null) {
endRow = this.end.row;
endColumn = this.end.column;
}
for (var i = 0; i < folds.length; i++) {
fold = folds[i];
cmp = fold.range.compareStart(endRow, endColumn);
// This fold is after the endRow/Column.
if (cmp == -1) {
callback(null, endRow, endColumn, lastEnd, isNewRow);
return;
}
stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow);
stop = !stop && callback(fold.placeholder, fold.start.row, fold.start.column, lastEnd);
// If the user requested to stop the walk or endRow/endColumn is
// inside of this fold (cmp == 0), then end here.
if (stop || cmp === 0) {
return;
}
// Note the new lastEnd might not be on the same line. However,
// it's the callback's job to recognize this.
isNewRow = !fold.sameRow;
lastEnd = fold.end.column;
}
callback(null, endRow, endColumn, lastEnd, isNewRow);
};
this.getNextFoldTo = function(row, column) {
var fold, cmp;
for (var i = 0; i < this.folds.length; i++) {
fold = this.folds[i];
cmp = fold.range.compareEnd(row, column);
if (cmp == -1) {
return {
fold: fold,
kind: "after"
};
} else if (cmp === 0) {
return {
fold: fold,
kind: "inside"
};
}
}
return null;
};
this.addRemoveChars = function(row, column, len) {
var ret = this.getNextFoldTo(row, column),
fold, folds;
if (ret) {
fold = ret.fold;
if (ret.kind == "inside"
&& fold.start.column != column
&& fold.start.row != row)
{
//throwing here breaks whole editor
//TODO: properly handle this
window.console && window.console.log(row, column, fold);
} else if (fold.start.row == row) {
folds = this.folds;
var i = folds.indexOf(fold);
if (i === 0) {
this.start.column += len;
}
for (i; i < folds.length; i++) {
fold = folds[i];
fold.start.column += len;
if (!fold.sameRow) {
return;
}
fold.end.column += len;
}
this.end.column += len;
}
}
};
this.split = function(row, column) {
var pos = this.getNextFoldTo(row, column);
if (!pos || pos.kind == "inside")
return null;
var fold = pos.fold;
var folds = this.folds;
var foldData = this.foldData;
var i = folds.indexOf(fold);
var foldBefore = folds[i - 1];
this.end.row = foldBefore.end.row;
this.end.column = foldBefore.end.column;
// Remove the folds after row/column and create a new FoldLine
// containing these removed folds.
folds = folds.splice(i, folds.length - i);
var newFoldLine = new FoldLine(foldData, folds);
foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine);
return newFoldLine;
};
this.merge = function(foldLineNext) {
var folds = foldLineNext.folds;
for (var i = 0; i < folds.length; i++) {
this.addFold(folds[i]);
}
// Remove the foldLineNext - no longer needed, as
// it's merged now with foldLineNext.
var foldData = this.foldData;
foldData.splice(foldData.indexOf(foldLineNext), 1);
};
this.toString = function() {
var ret = [this.range.toString() + ": [" ];
this.folds.forEach(function(fold) {
ret.push(" " + fold.toString());
});
ret.push("]");
return ret.join("\n");
};
this.idxToPosition = function(idx) {
var lastFoldEndColumn = 0;
for (var i = 0; i < this.folds.length; i++) {
var fold = this.folds[i];
idx -= fold.start.column - lastFoldEndColumn;
if (idx < 0) {
return {
row: fold.start.row,
column: fold.start.column + idx
};
}
idx -= fold.placeholder.length;
if (idx < 0) {
return fold.start;
}
lastFoldEndColumn = fold.end.column;
}
return {
row: this.end.row,
column: this.end.column + idx
};
};
}).call(FoldLine.prototype);
exports.FoldLine = FoldLine;
});
@@ -1,859 +0,0 @@
/* ***** 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 Range = require("../range").Range;
var FoldLine = require("./fold_line").FoldLine;
var Fold = require("./fold").Fold;
var TokenIterator = require("../token_iterator").TokenIterator;
function Folding() {
/*
* Looks up a fold at a given row/column. Possible values for side:
* -1: ignore a fold if fold.start = row/column
* +1: ignore a fold if fold.end = row/column
*/
this.getFoldAt = function(row, column, side) {
var foldLine = this.getFoldLine(row);
if (!foldLine)
return null;
var folds = foldLine.folds;
for (var i = 0; i < folds.length; i++) {
var fold = folds[i];
if (fold.range.contains(row, column)) {
if (side == 1 && fold.range.isEnd(row, column)) {
continue;
} else if (side == -1 && fold.range.isStart(row, column)) {
continue;
}
return fold;
}
}
};
/*
* Returns all folds in the given range. Note, that this will return folds
*
*/
this.getFoldsInRange = function(range) {
var start = range.start;
var end = range.end;
var foldLines = this.$foldData;
var foundFolds = [];
start.column += 1;
end.column -= 1;
for (var i = 0; i < foldLines.length; i++) {
var cmp = foldLines[i].range.compareRange(range);
if (cmp == 2) {
// Range is before foldLine. No intersection. This means,
// there might be other foldLines that intersect.
continue;
}
else if (cmp == -2) {
// Range is after foldLine. There can't be any other foldLines then,
// so let's give up.
break;
}
var folds = foldLines[i].folds;
for (var j = 0; j < folds.length; j++) {
var fold = folds[j];
cmp = fold.range.compareRange(range);
if (cmp == -2) {
break;
} else if (cmp == 2) {
continue;
} else
// WTF-state: Can happen due to -1/+1 to start/end column.
if (cmp == 42) {
break;
}
foundFolds.push(fold);
}
}
start.column -= 1;
end.column += 1;
return foundFolds;
};
this.getFoldsInRangeList = function(ranges) {
if (Array.isArray(ranges)) {
var folds = [];
ranges.forEach(function(range) {
folds = folds.concat(this.getFoldsInRange(range));
}, this);
} else {
var folds = this.getFoldsInRange(ranges);
}
return folds;
}
/*
* Returns all folds in the document
*/
this.getAllFolds = function() {
var folds = [];
var foldLines = this.$foldData;
for (var i = 0; i < foldLines.length; i++)
for (var j = 0; j < foldLines[i].folds.length; j++)
folds.push(foldLines[i].folds[j]);
return folds;
};
/*
* Returns the string between folds at the given position.
* E.g.
* foo<fold>b|ar<fold>wolrd -> "bar"
* foo<fold>bar<fold>wol|rd -> "world"
* foo<fold>bar<fo|ld>wolrd -> <null>
*
* where | means the position of row/column
*
* The trim option determs if the return string should be trimed according
* to the "side" passed with the trim value:
*
* E.g.
* foo<fold>b|ar<fold>wolrd -trim=-1> "b"
* foo<fold>bar<fold>wol|rd -trim=+1> "rld"
* fo|o<fold>bar<fold>wolrd -trim=00> "foo"
*/
this.getFoldStringAt = function(row, column, trim, foldLine) {
foldLine = foldLine || this.getFoldLine(row);
if (!foldLine)
return null;
var lastFold = {
end: { column: 0 }
};
// TODO: Refactor to use getNextFoldTo function.
var str, fold;
for (var i = 0; i < foldLine.folds.length; i++) {
fold = foldLine.folds[i];
var cmp = fold.range.compareEnd(row, column);
if (cmp == -1) {
str = this
.getLine(fold.start.row)
.substring(lastFold.end.column, fold.start.column);
break;
}
else if (cmp === 0) {
return null;
}
lastFold = fold;
}
if (!str)
str = this.getLine(fold.start.row).substring(lastFold.end.column);
if (trim == -1)
return str.substring(0, column - lastFold.end.column);
else if (trim == 1)
return str.substring(column - lastFold.end.column);
else
return str;
};
this.getFoldLine = function(docRow, startFoldLine) {
var foldData = this.$foldData;
var i = 0;
if (startFoldLine)
i = foldData.indexOf(startFoldLine);
if (i == -1)
i = 0;
for (i; i < foldData.length; i++) {
var foldLine = foldData[i];
if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) {
return foldLine;
} else if (foldLine.end.row > docRow) {
return null;
}
}
return null;
};
// returns the fold which starts after or contains docRow
this.getNextFoldLine = function(docRow, startFoldLine) {
var foldData = this.$foldData;
var i = 0;
if (startFoldLine)
i = foldData.indexOf(startFoldLine);
if (i == -1)
i = 0;
for (i; i < foldData.length; i++) {
var foldLine = foldData[i];
if (foldLine.end.row >= docRow) {
return foldLine;
}
}
return null;
};
this.getFoldedRowCount = function(first, last) {
var foldData = this.$foldData, rowCount = last-first+1;
for (var i = 0; i < foldData.length; i++) {
var foldLine = foldData[i],
end = foldLine.end.row,
start = foldLine.start.row;
if (end >= last) {
if(start < last) {
if(start >= first)
rowCount -= last-start;
else
rowCount = 0;//in one fold
}
break;
} else if(end >= first){
if (start >= first) //fold inside range
rowCount -= end-start;
else
rowCount -= end-first+1;
}
}
return rowCount;
};
this.$addFoldLine = function(foldLine) {
this.$foldData.push(foldLine);
this.$foldData.sort(function(a, b) {
return a.start.row - b.start.row;
});
return foldLine;
};
/**
* Adds a new fold.
*
* @returns
* The new created Fold object or an existing fold object in case the
* passed in range fits an existing fold exactly.
*/
this.addFold = function(placeholder, range) {
var foldData = this.$foldData;
var added = false;
var fold;
if (placeholder instanceof Fold)
fold = placeholder;
else {
fold = new Fold(range, placeholder);
fold.collapseChildren = range.collapseChildren;
}
this.$clipRangeToDocument(fold.range);
var startRow = fold.start.row;
var startColumn = fold.start.column;
var endRow = fold.end.row;
var endColumn = fold.end.column;
// --- Some checking ---
if (!(startRow < endRow ||
startRow == endRow && startColumn <= endColumn - 2))
throw new Error("The range has to be at least 2 characters width");
var startFold = this.getFoldAt(startRow, startColumn, 1);
var endFold = this.getFoldAt(endRow, endColumn, -1);
if (startFold && endFold == startFold)
return startFold.addSubFold(fold);
if (startFold && !startFold.range.isStart(startRow, startColumn))
this.removeFold(startFold);
if (endFold && !endFold.range.isEnd(endRow, endColumn))
this.removeFold(endFold);
// Check if there are folds in the range we create the new fold for.
var folds = this.getFoldsInRange(fold.range);
if (folds.length > 0) {
// Remove the folds from fold data.
this.removeFolds(folds);
// Add the removed folds as subfolds on the new fold.
folds.forEach(function(subFold) {
fold.addSubFold(subFold);
});
}
for (var i = 0; i < foldData.length; i++) {
var foldLine = foldData[i];
if (endRow == foldLine.start.row) {
foldLine.addFold(fold);
added = true;
break;
} else if (startRow == foldLine.end.row) {
foldLine.addFold(fold);
added = true;
if (!fold.sameRow) {
// Check if we might have to merge two FoldLines.
var foldLineNext = foldData[i + 1];
if (foldLineNext && foldLineNext.start.row == endRow) {
// We need to merge!
foldLine.merge(foldLineNext);
break;
}
}
break;
} else if (endRow <= foldLine.start.row) {
break;
}
}
if (!added)
foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold));
if (this.$useWrapMode)
this.$updateWrapData(foldLine.start.row, foldLine.start.row);
else
this.$updateRowLengthCache(foldLine.start.row, foldLine.start.row);
// Notify that fold data has changed.
this.$modified = true;
this._emit("changeFold", { data: fold, action: "add" });
return fold;
};
this.addFolds = function(folds) {
folds.forEach(function(fold) {
this.addFold(fold);
}, this);
};
this.removeFold = function(fold) {
var foldLine = fold.foldLine;
var startRow = foldLine.start.row;
var endRow = foldLine.end.row;
var foldLines = this.$foldData;
var folds = foldLine.folds;
// Simple case where there is only one fold in the FoldLine such that
// the entire fold line can get removed directly.
if (folds.length == 1) {
foldLines.splice(foldLines.indexOf(foldLine), 1);
} else
// If the fold is the last fold of the foldLine, just remove it.
if (foldLine.range.isEnd(fold.end.row, fold.end.column)) {
folds.pop();
foldLine.end.row = folds[folds.length - 1].end.row;
foldLine.end.column = folds[folds.length - 1].end.column;
} else
// If the fold is the first fold of the foldLine, just remove it.
if (foldLine.range.isStart(fold.start.row, fold.start.column)) {
folds.shift();
foldLine.start.row = folds[0].start.row;
foldLine.start.column = folds[0].start.column;
} else
// We know there are more then 2 folds and the fold is not at the edge.
// This means, the fold is somewhere in between.
//
// If the fold is in one row, we just can remove it.
if (fold.sameRow) {
folds.splice(folds.indexOf(fold), 1);
} else
// The fold goes over more then one row. This means remvoing this fold
// will cause the fold line to get splitted up. newFoldLine is the second part
{
var newFoldLine = foldLine.split(fold.start.row, fold.start.column);
folds = newFoldLine.folds;
folds.shift();
newFoldLine.start.row = folds[0].start.row;
newFoldLine.start.column = folds[0].start.column;
}
if (!this.$updating) {
if (this.$useWrapMode)
this.$updateWrapData(startRow, endRow);
else
this.$updateRowLengthCache(startRow, endRow);
}
// Notify that fold data has changed.
this.$modified = true;
this._emit("changeFold", { data: fold, action: "remove" });
};
this.removeFolds = function(folds) {
// We need to clone the folds array passed in as it might be the folds
// array of a fold line and as we call this.removeFold(fold), folds
// are removed from folds and changes the current index.
var cloneFolds = [];
for (var i = 0; i < folds.length; i++) {
cloneFolds.push(folds[i]);
}
cloneFolds.forEach(function(fold) {
this.removeFold(fold);
}, this);
this.$modified = true;
};
this.expandFold = function(fold) {
this.removeFold(fold);
fold.subFolds.forEach(function(subFold) {
fold.restoreRange(subFold);
this.addFold(subFold);
}, this);
if (fold.collapseChildren > 0) {
this.foldAll(fold.start.row+1, fold.end.row, fold.collapseChildren-1);
}
fold.subFolds = [];
};
this.expandFolds = function(folds) {
folds.forEach(function(fold) {
this.expandFold(fold);
}, this);
};
this.unfold = function(location, expandInner) {
var range, folds;
if (location == null) {
range = new Range(0, 0, this.getLength(), 0);
expandInner = true;
} else if (typeof location == "number")
range = new Range(location, 0, location, this.getLine(location).length);
else if ("row" in location)
range = Range.fromPoints(location, location);
else
range = location;
folds = this.getFoldsInRangeList(range);
if (expandInner) {
this.removeFolds(folds);
} else {
var subFolds = folds;
// TODO: might be better to remove and add folds in one go instead of using
// expandFolds several times.
while (subFolds.length) {
this.expandFolds(subFolds);
subFolds = this.getFoldsInRangeList(range);
}
}
if (folds.length)
return folds;
};
/*
* Checks if a given documentRow is folded. This is true if there are some
* folded parts such that some parts of the line is still visible.
**/
this.isRowFolded = function(docRow, startFoldRow) {
return !!this.getFoldLine(docRow, startFoldRow);
};
this.getRowFoldEnd = function(docRow, startFoldRow) {
var foldLine = this.getFoldLine(docRow, startFoldRow);
return foldLine ? foldLine.end.row : docRow;
};
this.getRowFoldStart = function(docRow, startFoldRow) {
var foldLine = this.getFoldLine(docRow, startFoldRow);
return foldLine ? foldLine.start.row : docRow;
};
this.getFoldDisplayLine = function(foldLine, endRow, endColumn, startRow, startColumn) {
if (startRow == null)
startRow = foldLine.start.row;
if (startColumn == null)
startColumn = 0;
if (endRow == null)
endRow = foldLine.end.row;
if (endColumn == null)
endColumn = this.getLine(endRow).length;
// Build the textline using the FoldLine walker.
var doc = this.doc;
var textLine = "";
foldLine.walk(function(placeholder, row, column, lastColumn) {
if (row < startRow)
return;
if (row == startRow) {
if (column < startColumn)
return;
lastColumn = Math.max(startColumn, lastColumn);
}
if (placeholder != null) {
textLine += placeholder;
} else {
textLine += doc.getLine(row).substring(lastColumn, column);
}
}, endRow, endColumn);
return textLine;
};
this.getDisplayLine = function(row, endColumn, startRow, startColumn) {
var foldLine = this.getFoldLine(row);
if (!foldLine) {
var line;
line = this.doc.getLine(row);
return line.substring(startColumn || 0, endColumn || line.length);
} else {
return this.getFoldDisplayLine(
foldLine, row, endColumn, startRow, startColumn);
}
};
this.$cloneFoldData = function() {
var fd = [];
fd = this.$foldData.map(function(foldLine) {
var folds = foldLine.folds.map(function(fold) {
return fold.clone();
});
return new FoldLine(fd, folds);
});
return fd;
};
this.toggleFold = function(tryToUnfold) {
var selection = this.selection;
var range = selection.getRange();
var fold;
var bracketPos;
if (range.isEmpty()) {
var cursor = range.start;
fold = this.getFoldAt(cursor.row, cursor.column);
if (fold) {
this.expandFold(fold);
return;
} else if (bracketPos = this.findMatchingBracket(cursor)) {
if (range.comparePoint(bracketPos) == 1) {
range.end = bracketPos;
} else {
range.start = bracketPos;
range.start.column++;
range.end.column--;
}
} else if (bracketPos = this.findMatchingBracket({row: cursor.row, column: cursor.column + 1})) {
if (range.comparePoint(bracketPos) == 1)
range.end = bracketPos;
else
range.start = bracketPos;
range.start.column++;
} else {
range = this.getCommentFoldRange(cursor.row, cursor.column) || range;
}
} else {
var folds = this.getFoldsInRange(range);
if (tryToUnfold && folds.length) {
this.expandFolds(folds);
return;
} else if (folds.length == 1 ) {
fold = folds[0];
}
}
if (!fold)
fold = this.getFoldAt(range.start.row, range.start.column);
if (fold && fold.range.toString() == range.toString()) {
this.expandFold(fold);
return;
}
var placeholder = "...";
if (!range.isMultiLine()) {
placeholder = this.getTextRange(range);
if(placeholder.length < 4)
return;
placeholder = placeholder.trim().substring(0, 2) + "..";
}
this.addFold(placeholder, range);
};
this.getCommentFoldRange = function(row, column, dir) {
var iterator = new TokenIterator(this, row, column);
var token = iterator.getCurrentToken();
if (token && /^comment|string/.test(token.type)) {
var range = new Range();
var re = new RegExp(token.type.replace(/\..*/, "\\."));
if (dir != 1) {
do {
token = iterator.stepBackward();
} while(token && re.test(token.type));
iterator.stepForward();
}
range.start.row = iterator.getCurrentTokenRow();
range.start.column = iterator.getCurrentTokenColumn() + 2;
iterator = new TokenIterator(this, row, column);
if (dir != -1) {
do {
token = iterator.stepForward();
} while(token && re.test(token.type));
token = iterator.stepBackward();
} else
token = iterator.getCurrentToken();
range.end.row = iterator.getCurrentTokenRow();
range.end.column = iterator.getCurrentTokenColumn() + token.value.length - 2;
return range;
}
};
this.foldAll = function(startRow, endRow, depth) {
if (depth == undefined)
depth = 100000; // JSON.stringify doesn't hanle Infinity
var foldWidgets = this.foldWidgets;
if (!foldWidgets)
return; // mode doesn't support folding
endRow = endRow || this.getLength();
startRow = startRow || 0;
for (var row = startRow; row < endRow; row++) {
if (foldWidgets[row] == null)
foldWidgets[row] = this.getFoldWidget(row);
if (foldWidgets[row] != "start")
continue;
var range = this.getFoldWidgetRange(row);
// sometimes range can be incompatible with existing fold
// TODO change addFold to return null istead of throwing
if (range && range.isMultiLine()
&& range.end.row <= endRow
&& range.start.row >= startRow
) {
row = range.end.row;
try {
// addFold can change the range
var fold = this.addFold("...", range);
if (fold)
fold.collapseChildren = depth;
} catch(e) {}
}
}
};
// structured folding
this.$foldStyles = {
"manual": 1,
"markbegin": 1,
"markbeginend": 1
};
this.$foldStyle = "markbegin";
this.setFoldStyle = function(style) {
if (!this.$foldStyles[style])
throw new Error("invalid fold style: " + style + "[" + Object.keys(this.$foldStyles).join(", ") + "]");
if (this.$foldStyle == style)
return;
this.$foldStyle = style;
if (style == "manual")
this.unfold();
// reset folding
var mode = this.$foldMode;
this.$setFolding(null);
this.$setFolding(mode);
};
this.$setFolding = function(foldMode) {
if (this.$foldMode == foldMode)
return;
this.$foldMode = foldMode;
this.off('change', this.$updateFoldWidgets);
this.off('tokenizerUpdate', this.$tokenizerUpdateFoldWidgets);
this._emit("changeAnnotation");
if (!foldMode || this.$foldStyle == "manual") {
this.foldWidgets = null;
return;
}
this.foldWidgets = [];
this.getFoldWidget = foldMode.getFoldWidget.bind(foldMode, this, this.$foldStyle);
this.getFoldWidgetRange = foldMode.getFoldWidgetRange.bind(foldMode, this, this.$foldStyle);
this.$updateFoldWidgets = this.updateFoldWidgets.bind(this);
this.$tokenizerUpdateFoldWidgets = this.tokenizerUpdateFoldWidgets.bind(this);
this.on('change', this.$updateFoldWidgets);
this.on('tokenizerUpdate', this.$tokenizerUpdateFoldWidgets);
};
this.getParentFoldRangeData = function (row, ignoreCurrent) {
var fw = this.foldWidgets;
if (!fw || (ignoreCurrent && fw[row]))
return {};
var i = row - 1, firstRange;
while (i >= 0) {
var c = fw[i];
if (c == null)
c = fw[i] = this.getFoldWidget(i);
if (c == "start") {
var range = this.getFoldWidgetRange(i);
if (!firstRange)
firstRange = range;
if (range && range.end.row >= row)
break;
}
i--;
}
return {
range: i !== -1 && range,
firstRange: firstRange
};
}
this.onFoldWidgetClick = function(row, e) {
e = e.domEvent;
var options = {
children: e.shiftKey,
all: e.ctrlKey || e.metaKey,
siblings: e.altKey
};
var range = this.$toggleFoldWidget(row, options);
if (!range) {
var el = (e.target || e.srcElement)
if (el && /ace_fold-widget/.test(el.className))
el.className += " ace_invalid";
}
};
this.$toggleFoldWidget = function(row, options) {
if (!this.getFoldWidget)
return;
var type = this.getFoldWidget(row);
var line = this.getLine(row);
var dir = type === "end" ? -1 : 1;
var fold = this.getFoldAt(row, dir === -1 ? 0 : line.length, dir);
if (fold) {
if (options.children || options.all)
this.removeFold(fold);
else
this.expandFold(fold);
return;
}
var range = this.getFoldWidgetRange(row, true);
// sometimes singleline folds can be missed by the code above
if (range && !range.isMultiLine()) {
fold = this.getFoldAt(range.start.row, range.start.column, 1);
if (fold && range.isEqual(fold.range)) {
this.removeFold(fold);
return;
}
}
if (options.siblings) {
var data = this.getParentFoldRangeData(row);
if (data.range) {
var startRow = data.range.start.row + 1;
var endRow = data.range.end.row;
}
this.foldAll(startRow, endRow, options.all ? 10000 : 0);
} else if (options.children) {
endRow = range ? range.end.row : this.getLength();
this.foldAll(row + 1, range.end.row, options.all ? 10000 : 0);
} else if (range) {
if (options.all)
range.collapseChildren = 10000;
this.addFold("...", range);
}
return range;
};
this.toggleFoldWidget = function(toggleParent) {
var row = this.selection.getCursor().row;
row = this.getRowFoldStart(row);
var range = this.$toggleFoldWidget(row, {});
if (range)
return;
// handle toggleParent
var data = this.getParentFoldRangeData(row, true);
range = data.range || data.firstRange;
if (range) {
row = range.start.row;
var fold = this.getFoldAt(row, this.getLine(row).length, 1);
if (fold) {
this.removeFold(fold);
} else {
this.addFold("...", range);
}
}
};
this.updateFoldWidgets = function(e) {
var delta = e.data;
var range = delta.range;
var firstRow = range.start.row;
var len = range.end.row - firstRow;
if (len === 0) {
this.foldWidgets[firstRow] = null;
} else if (delta.action == "removeText" || delta.action == "removeLines") {
this.foldWidgets.splice(firstRow, len + 1, null);
} else {
var args = Array(len + 1);
args.unshift(firstRow, 1);
this.foldWidgets.splice.apply(this.foldWidgets, args);
}
};
this.tokenizerUpdateFoldWidgets = function(e) {
var rows = e.data;
if (rows.first != rows.last) {
if (this.foldWidgets.length > rows.first)
this.foldWidgets.splice(rows.first, this.foldWidgets.length);
}
}
}
exports.Folding = Folding;
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,188 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
require("./test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var EditSession = require("./edit_session").EditSession;
var Editor = require("./editor").Editor;
var Text = require("./mode/text").Mode;
var JavaScriptMode = require("./mode/javascript").Mode;
var CssMode = require("./mode/css").Mode;
var HtmlMode = require("./mode/html").Mode;
var MockRenderer = require("./test/mockrenderer").MockRenderer;
var assert = require("./test/assertions");
module.exports = {
setUp : function(next) {
this.session1 = new EditSession(["abc", "def"]);
this.session2 = new EditSession(["ghi", "jkl"]);
this.editor = new Editor(new MockRenderer());
next();
},
"test: change document" : function() {
this.editor.setSession(this.session1);
assert.equal(this.editor.getSession(), this.session1);
this.editor.setSession(this.session2);
assert.equal(this.editor.getSession(), this.session2);
},
"test: only changes to the new document should have effect" : function() {
var called = false;
this.editor.onDocumentChange = function() {
called = true;
};
this.editor.setSession(this.session1);
this.editor.setSession(this.session2);
this.session1.duplicateLines(0, 0);
assert.notOk(called);
this.session2.duplicateLines(0, 0);
assert.ok(called);
},
"test: should use cursor of new document" : function() {
this.session1.getSelection().moveCursorTo(0, 1);
this.session2.getSelection().moveCursorTo(1, 0);
this.editor.setSession(this.session1);
assert.position(this.editor.getCursorPosition(), 0, 1);
this.editor.setSession(this.session2);
assert.position(this.editor.getCursorPosition(), 1, 0);
},
"test: only changing the cursor of the new doc should not have an effect" : function() {
this.editor.onCursorChange = function() {
called = true;
};
this.editor.setSession(this.session1);
this.editor.setSession(this.session2);
assert.position(this.editor.getCursorPosition(), 0, 0);
var called = false;
this.session1.getSelection().moveCursorTo(0, 1);
assert.position(this.editor.getCursorPosition(), 0, 0);
assert.notOk(called);
this.session2.getSelection().moveCursorTo(1, 1);
assert.position(this.editor.getCursorPosition(), 1, 1);
assert.ok(called);
},
"test: should use selection of new document" : function() {
this.session1.getSelection().selectTo(0, 1);
this.session2.getSelection().selectTo(1, 0);
this.editor.setSession(this.session1);
assert.position(this.editor.getSelection().getSelectionLead(), 0, 1);
this.editor.setSession(this.session2);
assert.position(this.editor.getSelection().getSelectionLead(), 1, 0);
},
"test: only changing the selection of the new doc should not have an effect" : function() {
this.editor.onSelectionChange = function() {
called = true;
};
this.editor.setSession(this.session1);
this.editor.setSession(this.session2);
assert.position(this.editor.getSelection().getSelectionLead(), 0, 0);
var called = false;
this.session1.getSelection().selectTo(0, 1);
assert.position(this.editor.getSelection().getSelectionLead(), 0, 0);
assert.notOk(called);
this.session2.getSelection().selectTo(1, 1);
assert.position(this.editor.getSelection().getSelectionLead(), 1, 1);
assert.ok(called);
},
"test: should use mode of new document" : function() {
this.editor.onChangeMode = function() {
called = true;
};
this.editor.setSession(this.session1);
this.editor.setSession(this.session2);
var called = false;
this.session1.setMode(new Text());
assert.notOk(called);
this.session2.setMode(new JavaScriptMode());
assert.ok(called);
},
"test: should use stop worker of old document" : function(next) {
var self = this;
// 1. Open an editor and set the session to CssMode
self.editor.setSession(self.session1);
self.session1.setMode(new CssMode());
// 2. Add a line or two of valid CSS.
self.session1.setValue("DIV { color: red; }");
// 3. Clear the session value.
self.session1.setValue("");
// 4. Set the session to HtmlMode
self.session1.setMode(new HtmlMode());
// 5. Try to type valid HTML
self.session1.insert({row: 0, column: 0}, "<html></html>");
setTimeout(function() {
assert.equal(Object.keys(self.session1.getAnnotations()).length, 0);
next();
}, 600);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,223 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
require("./test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var EditSession = require("./edit_session").EditSession;
var Editor = require("./editor").Editor;
var MockRenderer = require("./test/mockrenderer").MockRenderer;
var assert = require("./test/assertions");
var lipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Mauris at arcu mi, eu lobortis mauris. Quisque ut libero eget " +
"diam congue vehicula. Quisque ut odio ut mi aliquam tincidunt. " +
"Duis lacinia aliquam lorem eget eleifend. Morbi eget felis mi. " +
"Duis quam ligula, consequat vitae convallis volutpat, blandit " +
"nec neque. Nulla facilisi. Etiam suscipit lorem ac justo " +
"sollicitudin tristique. Phasellus ut posuere nunc. Aliquam " +
"scelerisque mollis felis non gravida. Vestibulum lacus sem, " +
"posuere non bibendum id, luctus non dolor. Aenean id metus " +
"lorem, vel dapibus est. Donec gravida feugiat augue nec " +
"accumsan.Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit. Nulla vulputate, velit vitae tincidunt congue, nunc " +
"augue accumsan velit, eu consequat turpis lectus ac orci. " +
"Pellentesque ornare dolor feugiat dui auctor eu varius nulla " +
"fermentum. Sed aliquam odio at velit lacinia vel fermentum " +
"felis sodales. In dignissim magna eget nunc lobortis non " +
"fringilla nibh ullamcorper. Donec facilisis malesuada elit " +
"at egestas. Etiam bibendum, diam vitae tempor aliquet, dui " +
"libero vehicula odio, eget bibendum mauris velit eu lorem.\n" +
"consectetur";
function callHighlighterUpdate(session, firstRow, lastRow) {
var rangeCount = 0;
var mockMarkerLayer = { drawSingleLineMarker: function() {rangeCount++;} }
session.$searchHighlight.update([], mockMarkerLayer, session, {
firstRow: firstRow,
lastRow: lastRow
});
return rangeCount;
}
module.exports = {
setUp: function(next) {
this.session = new EditSession(lipsum);
this.editor = new Editor(new MockRenderer(), this.session);
this.selection = this.session.getSelection();
this.search = this.editor.$search;
next();
},
"test: highlight selected words by default": function() {
assert.equal(this.editor.getHighlightSelectedWord(), true);
},
"test: highlight a word": function() {
this.editor.moveCursorTo(0, 9);
this.selection.selectWord();
var highlighter = this.editor.session.$searchHighlight;
assert.ok(highlighter != null);
var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "ipsum");
assert.equal(highlighter.cache.length, 0);
assert.equal(callHighlighterUpdate(this.session, 0, 0), 2);
},
"test: highlight a word and clear highlight": function() {
this.editor.moveCursorTo(0, 8);
this.selection.selectWord();
var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "ipsum");
assert.equal(callHighlighterUpdate(this.session, 0, 0), 2);
this.session.highlight("");
assert.equal(this.session.$searchHighlight.cache.length, 0);
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: highlight another word": function() {
this.selection.moveCursorTo(0, 14);
this.selection.selectWord();
var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolor");
assert.equal(callHighlighterUpdate(this.session, 0, 0), 4);
},
"test: no selection, no highlight": function() {
this.selection.clearSelection();
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: select a word, no highlight": function() {
this.selection.moveCursorTo(0, 14);
this.selection.selectWord();
this.editor.setHighlightSelectedWord(false);
var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolor");
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: select a word with no matches": function() {
this.editor.setHighlightSelectedWord(true);
var currentOptions = this.search.getOptions();
var newOptions = {
wrap: true,
wholeWord: true,
caseSensitive: true,
needle: "Mauris"
};
this.search.set(newOptions);
var match = this.search.find(this.session);
assert.notEqual(match, null, "found a match for 'Mauris'");
this.search.set(currentOptions);
this.selection.setSelectionRange(match);
assert.equal(this.session.getTextRange(match), "Mauris");
assert.equal(callHighlighterUpdate(this.session, 0, 0), 1);
},
"test: partial word selection 1": function() {
this.selection.moveCursorTo(0, 14);
this.selection.selectWord();
this.selection.selectLeft();
var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolo");
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: partial word selection 2": function() {
this.selection.moveCursorTo(0, 13);
this.selection.selectWord();
this.selection.selectRight();
var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "dolor ");
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: partial word selection 3": function() {
this.selection.moveCursorTo(0, 14);
this.selection.selectWord();
this.selection.selectLeft();
this.selection.shiftSelection(1);
var range = this.selection.getRange();
assert.equal(this.session.getTextRange(range), "olor");
assert.equal(callHighlighterUpdate(this.session, 0, 0), 0);
},
"test: select last word": function() {
this.selection.moveCursorTo(0, 1);
var currentOptions = this.search.getOptions();
var newOptions = {
wrap: true,
wholeWord: true,
caseSensitive: true,
backwards: true,
needle: "consectetur"
};
this.search.set(newOptions);
var match = this.search.find(this.session);
assert.notEqual(match, null, "found a match for 'consectetur'");
assert.position(match.start, 1, 0);
this.search.set(currentOptions);
this.selection.setSelectionRange(match);
assert.equal(this.session.getTextRange(match), "consectetur");
assert.equal(callHighlighterUpdate(this.session, 0, 1), 3);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec();
}
@@ -1,164 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
require("./test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var EditSession = require("./edit_session").EditSession;
var Editor = require("./editor").Editor;
var MockRenderer = require("./test/mockrenderer").MockRenderer;
var assert = require("./test/assertions");
module.exports = {
createEditSession : function(rows, cols) {
var line = new Array(cols + 1).join("a");
var text = new Array(rows).join(line + "\n") + line;
return new EditSession(text);
},
"test: navigate to end of file should scroll the last line into view" : function() {
var doc = this.createEditSession(200, 10);
var editor = new Editor(new MockRenderer(), doc);
editor.navigateFileEnd();
var cursor = editor.getCursorPosition();
assert.ok(editor.getFirstVisibleRow() <= cursor.row);
assert.ok(editor.getLastVisibleRow() >= cursor.row);
},
"test: navigate to start of file should scroll the first row into view" : function() {
var doc = this.createEditSession(200, 10);
var editor = new Editor(new MockRenderer(), doc);
editor.moveCursorTo(editor.getLastVisibleRow() + 20);
editor.navigateFileStart();
assert.equal(editor.getFirstVisibleRow(), 0);
},
"test: goto hidden line should scroll the line into the middle of the viewport" : function() {
var editor = new Editor(new MockRenderer(), this.createEditSession(200, 5));
editor.navigateTo(0, 0);
editor.gotoLine(101);
assert.position(editor.getCursorPosition(), 100, 0);
assert.equal(editor.getFirstVisibleRow(), 89);
editor.navigateTo(100, 0);
editor.gotoLine(11);
assert.position(editor.getCursorPosition(), 10, 0);
assert.equal(editor.getFirstVisibleRow(), 0);
editor.navigateTo(100, 0);
editor.gotoLine(6);
assert.position(editor.getCursorPosition(), 5, 0);
assert.equal(0, editor.getFirstVisibleRow(), 0);
editor.navigateTo(100, 0);
editor.gotoLine(1);
assert.position(editor.getCursorPosition(), 0, 0);
assert.equal(editor.getFirstVisibleRow(), 0);
editor.navigateTo(0, 0);
editor.gotoLine(191);
assert.position(editor.getCursorPosition(), 190, 0);
assert.equal(editor.getFirstVisibleRow(), 179);
editor.navigateTo(0, 0);
editor.gotoLine(196);
assert.position(editor.getCursorPosition(), 195, 0);
assert.equal(editor.getFirstVisibleRow(), 180);
},
"test: goto visible line should only move the cursor and not scroll": function() {
var editor = new Editor(new MockRenderer(), this.createEditSession(200, 5));
editor.navigateTo(0, 0);
editor.gotoLine(12);
assert.position(editor.getCursorPosition(), 11, 0);
assert.equal(editor.getFirstVisibleRow(), 0);
editor.navigateTo(30, 0);
editor.gotoLine(33);
assert.position(editor.getCursorPosition(), 32, 0);
assert.equal(editor.getFirstVisibleRow(), 30);
},
"test: navigate from the end of a long line down to a short line and back should maintain the curser column": function() {
var editor = new Editor(new MockRenderer(), new EditSession(["123456", "1"]));
editor.navigateTo(0, 6);
assert.position(editor.getCursorPosition(), 0, 6);
editor.navigateDown();
assert.position(editor.getCursorPosition(), 1, 1);
editor.navigateUp();
assert.position(editor.getCursorPosition(), 0, 6);
},
"test: reset desired column on navigate left or right": function() {
var editor = new Editor(new MockRenderer(), new EditSession(["123456", "12"]));
editor.navigateTo(0, 6);
assert.position(editor.getCursorPosition(), 0, 6);
editor.navigateDown();
assert.position(editor.getCursorPosition(), 1, 2);
editor.navigateLeft();
assert.position(editor.getCursorPosition(), 1, 1);
editor.navigateUp();
assert.position(editor.getCursorPosition(), 0, 1);
},
"test: typing text should update the desired column": function() {
var editor = new Editor(new MockRenderer(), new EditSession(["1234", "1234567890"]));
editor.navigateTo(0, 3);
editor.insert("juhu");
editor.navigateDown();
assert.position(editor.getCursorPosition(), 1, 7);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,557 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
require("./test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var EditSession = require("./edit_session").EditSession;
var Editor = require("./editor").Editor;
var JavaScriptMode = require("./mode/javascript").Mode;
var UndoManager = require("./undomanager").UndoManager;
var MockRenderer = require("./test/mockrenderer").MockRenderer;
var assert = require("./test/assertions");
var whitespace = require("./ext/whitespace");
module.exports = {
"test: delete line from the middle" : function() {
var session = new EditSession(["a", "b", "c", "d"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 1);
editor.removeLines();
assert.equal(session.toString(), "a\nc\nd");
assert.position(editor.getCursorPosition(), 1, 0);
editor.removeLines();
assert.equal(session.toString(), "a\nd");
assert.position(editor.getCursorPosition(), 1, 0);
editor.removeLines();
assert.equal(session.toString(), "a");
assert.position(editor.getCursorPosition(), 0, 1);
editor.removeLines();
assert.equal(session.toString(), "");
assert.position(editor.getCursorPosition(), 0, 0);
},
"test: delete multiple selected lines" : function() {
var session = new EditSession(["a", "b", "c", "d"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 1);
editor.getSelection().selectDown();
editor.removeLines();
assert.equal(session.toString(), "a\nd");
assert.position(editor.getCursorPosition(), 1, 0);
},
"test: delete first line" : function() {
var session = new EditSession(["a", "b", "c"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.removeLines();
assert.equal(session.toString(), "b\nc");
assert.position(editor.getCursorPosition(), 0, 0);
},
"test: delete last should also delete the new line of the previous line" : function() {
var session = new EditSession(["a", "b", "c", ""].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(3, 0);
editor.removeLines();
assert.equal(session.toString(), "a\nb\nc");
assert.position(editor.getCursorPosition(), 2, 1);
editor.removeLines();
assert.equal(session.toString(), "a\nb");
assert.position(editor.getCursorPosition(), 1, 1);
},
"test: indent block" : function() {
var session = new EditSession(["a12345", "b12345", "c12345"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 3);
editor.getSelection().selectDown();
editor.indent();
assert.equal(["a12345", " b12345", " c12345"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 2, 7);
var range = editor.getSelectionRange();
assert.position(range.start, 1, 7);
assert.position(range.end, 2, 7);
},
"test: indent selected lines" : function() {
var session = new EditSession(["a12345", "b12345", "c12345"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.getSelection().selectDown();
editor.indent();
assert.equal(["a12345", " b12345", "c12345"].join("\n"), session.toString());
},
"test: no auto indent if cursor is before the {" : function() {
var session = new EditSession("{", new JavaScriptMode());
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(0, 0);
editor.onTextInput("\n");
assert.equal(["", "{"].join("\n"), session.toString());
},
"test: outdent block" : function() {
var session = new EditSession([" a12345", " b12345", " c12345"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(0, 5);
editor.getSelection().selectDown();
editor.getSelection().selectDown();
editor.blockOutdent();
assert.equal(session.toString(), [" a12345", "b12345", " c12345"].join("\n"));
assert.position(editor.getCursorPosition(), 2, 1);
var range = editor.getSelectionRange();
assert.position(range.start, 0, 1);
assert.position(range.end, 2, 1);
editor.blockOutdent();
assert.equal(session.toString(), ["a12345", "b12345", "c12345"].join("\n"));
var range = editor.getSelectionRange();
assert.position(range.start, 0, 0);
assert.position(range.end, 2, 0);
},
"test: outent without a selection should update cursor" : function() {
var session = new EditSession(" 12");
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(0, 3);
editor.blockOutdent(" ");
assert.equal(session.toString(), " 12");
assert.position(editor.getCursorPosition(), 0, 0);
},
"test: comment lines should perserve selection" : function() {
var session = new EditSession([" abc", "cde"].join("\n"), new JavaScriptMode());
var editor = new Editor(new MockRenderer(), session);
whitespace.detectIndentation(session);
editor.moveCursorTo(0, 2);
editor.getSelection().selectDown();
editor.toggleCommentLines();
assert.equal(["// abc", "// cde"].join("\n"), session.toString());
var selection = editor.getSelectionRange();
assert.position(selection.start, 0, 5);
assert.position(selection.end, 1, 5);
},
"test: uncomment lines should perserve selection" : function() {
var session = new EditSession(["// abc", "//cde"].join("\n"), new JavaScriptMode());
var editor = new Editor(new MockRenderer(), session);
session.setTabSize(2);
editor.moveCursorTo(0, 1);
editor.getSelection().selectDown();
editor.getSelection().selectRight();
editor.getSelection().selectRight();
editor.toggleCommentLines();
assert.equal([" abc", "cde"].join("\n"), session.toString());
assert.range(editor.getSelectionRange(), 0, 0, 1, 1);
},
"test: toggle comment lines twice should return the original text" : function() {
var session = new EditSession([" abc", "cde", "fg"], new JavaScriptMode());
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(0, 0);
editor.getSelection().selectDown();
editor.getSelection().selectDown();
editor.toggleCommentLines();
editor.toggleCommentLines();
assert.equal([" abc", "cde", "fg"].join("\n"), session.toString());
},
"test: comment lines - if the selection end is at the line start it should stay there": function() {
//select down
var session = new EditSession(["abc", "cde"].join("\n"), new JavaScriptMode());
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(0, 0);
editor.getSelection().selectDown();
editor.toggleCommentLines();
assert.range(editor.getSelectionRange(), 0, 3, 1, 0);
// select up
var session = new EditSession(["abc", "cde"].join("\n"), new JavaScriptMode());
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.getSelection().selectUp();
editor.toggleCommentLines();
assert.range(editor.getSelectionRange(), 0, 3, 1, 0);
},
"test: move lines down should keep selection on moved lines" : function() {
var session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(0, 1);
editor.getSelection().selectDown();
editor.moveLinesDown();
assert.equal(["33", "11", "22", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 2, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
assert.position(editor.getSelection().getSelectionLead(), 2, 1);
editor.moveLinesDown();
assert.equal(["33", "44", "11", "22"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 3, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 2, 1);
assert.position(editor.getSelection().getSelectionLead(), 3, 1);
// moving again should have no effect
editor.moveLinesDown();
assert.equal(["33", "44", "11", "22"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 3, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 2, 1);
assert.position(editor.getSelection().getSelectionLead(), 3, 1);
},
"test: move lines up should keep selection on moved lines" : function() {
var session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(2, 1);
editor.getSelection().selectDown();
editor.moveLinesUp();
assert.equal(session.toString(), ["11", "33", "44", "22"].join("\n"));
assert.position(editor.getCursorPosition(), 2, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
assert.position(editor.getSelection().getSelectionLead(), 2, 1);
editor.moveLinesUp();
assert.equal(session.toString(), ["33", "44", "11", "22"].join("\n"));
assert.position(editor.getCursorPosition(), 1, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 0, 1);
assert.position(editor.getSelection().getSelectionLead(), 1, 1);
},
"test: move line without active selection should not move cursor relative to the moved line" : function() {
var session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 1);
editor.clearSelection();
editor.moveLinesDown();
assert.equal(["11", "33", "22", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 2, 1);
editor.clearSelection();
editor.moveLinesUp();
assert.equal(["11", "22", "33", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 1, 1);
},
"test: copy lines down should keep selection" : function() {
var session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 1);
editor.getSelection().selectDown();
editor.copyLinesDown();
assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 4, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 3, 1);
assert.position(editor.getSelection().getSelectionLead(), 4, 1);
},
"test: copy lines up should keep selection" : function() {
var session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 1);
editor.getSelection().selectDown();
editor.copyLinesUp();
assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 2, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
assert.position(editor.getSelection().getSelectionLead(), 2, 1);
},
"test: input a tab with soft tab should convert it to spaces" : function() {
var session = new EditSession("");
var editor = new Editor(new MockRenderer(), session);
session.setTabSize(2);
session.setUseSoftTabs(true);
editor.onTextInput("\t");
assert.equal(session.toString(), " ");
session.setTabSize(5);
editor.onTextInput("\t");
assert.equal(session.toString(), " ");
},
"test: input tab without soft tabs should keep the tab character" : function() {
var session = new EditSession("");
var editor = new Editor(new MockRenderer(), session);
session.setUseSoftTabs(false);
editor.onTextInput("\t");
assert.equal(session.toString(), "\t");
},
"test: undo/redo for delete line" : function() {
var session = new EditSession(["111", "222", "333"]);
var undoManager = new UndoManager();
session.setUndoManager(undoManager);
var initialText = session.toString();
var editor = new Editor(new MockRenderer(), session);
editor.removeLines();
var step1 = session.toString();
assert.equal(step1, "222\n333");
session.$syncInformUndoManager();
editor.removeLines();
var step2 = session.toString();
assert.equal(step2, "333");
session.$syncInformUndoManager();
editor.removeLines();
var step3 = session.toString();
assert.equal(step3, "");
session.$syncInformUndoManager();
undoManager.undo();
session.$syncInformUndoManager();
assert.equal(session.toString(), step2);
undoManager.undo();
session.$syncInformUndoManager();
assert.equal(session.toString(), step1);
undoManager.undo();
session.$syncInformUndoManager();
assert.equal(session.toString(), initialText);
undoManager.undo();
session.$syncInformUndoManager();
assert.equal(session.toString(), initialText);
},
"test: remove left should remove character left of the cursor" : function() {
var session = new EditSession(["123", "456"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 1);
editor.remove("left");
assert.equal(session.toString(), "123\n56");
},
"test: remove left should remove line break if cursor is at line start" : function() {
var session = new EditSession(["123", "456"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.remove("left");
assert.equal(session.toString(), "123456");
},
"test: remove left should remove tabsize spaces if cursor is on a tab stop and preceeded by spaces" : function() {
var session = new EditSession(["123", " 456"]);
session.setUseSoftTabs(true);
session.setTabSize(4);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 8);
editor.remove("left");
assert.equal(session.toString(), "123\n 456");
},
"test: transpose at line start should be a noop": function() {
var session = new EditSession(["123", "4567", "89"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.transposeLetters();
assert.equal(session.getValue(), ["123", "4567", "89"].join("\n"));
},
"test: transpose in line should swap the charaters before and after the cursor": function() {
var session = new EditSession(["123", "4567", "89"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 2);
editor.transposeLetters();
assert.equal(session.getValue(), ["123", "4657", "89"].join("\n"));
},
"test: transpose at line end should swap the last two characters": function() {
var session = new EditSession(["123", "4567", "89"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 4);
editor.transposeLetters();
assert.equal(session.getValue(), ["123", "4576", "89"].join("\n"));
},
"test: transpose with non empty selection should be a noop": function() {
var session = new EditSession(["123", "4567", "89"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 1);
editor.getSelection().selectRight();
editor.transposeLetters();
assert.equal(session.getValue(), ["123", "4567", "89"].join("\n"));
},
"test: transpose should move the cursor behind the last swapped character": function() {
var session = new EditSession(["123", "4567", "89"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 2);
editor.transposeLetters();
assert.position(editor.getCursorPosition(), 1, 3);
},
"test: remove to line end": function() {
var session = new EditSession(["123", "4567", "89"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 2);
editor.removeToLineEnd();
assert.equal(session.getValue(), ["123", "45", "89"].join("\n"));
},
"test: remove to line end at line end should remove the new line": function() {
var session = new EditSession(["123", "4567", "89"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 4);
editor.removeToLineEnd();
assert.position(editor.getCursorPosition(), 1, 4);
assert.equal(session.getValue(), ["123", "456789"].join("\n"));
},
"test: transform selection to uppercase": function() {
var session = new EditSession(["ajax", "dot", "org"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.getSelection().selectLineEnd();
editor.toUpperCase()
assert.equal(session.getValue(), ["ajax", "DOT", "org"].join("\n"));
},
"test: transform word to uppercase": function() {
var session = new EditSession(["ajax", "dot", "org"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.toUpperCase()
assert.equal(session.getValue(), ["ajax", "DOT", "org"].join("\n"));
assert.position(editor.getCursorPosition(), 1, 0);
},
"test: transform selection to lowercase": function() {
var session = new EditSession(["AJAX", "DOT", "ORG"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.getSelection().selectLineEnd();
editor.toLowerCase()
assert.equal(session.getValue(), ["AJAX", "dot", "ORG"].join("\n"));
},
"test: transform word to lowercase": function() {
var session = new EditSession(["AJAX", "DOT", "ORG"]);
var editor = new Editor(new MockRenderer(), session);
editor.moveCursorTo(1, 0);
editor.toLowerCase()
assert.equal(session.getValue(), ["AJAX", "dot", "ORG"].join("\n"));
assert.position(editor.getCursorPosition(), 1, 0);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,57 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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 ***** */
// [WIP]
define(function(require, exports, module) {
"use strict";
var TokenIterator = require("ace/token_iterator").TokenIterator;
var phpTransform = require("./beautify/php_rules").transform;
exports.beautify = function(session) {
var iterator = new TokenIterator(session, 0, 0);
var token = iterator.getCurrentToken();
var context = session.$modeId.split("/").pop();
var code = phpTransform(iterator, context);
session.doc.setValue(code);
};
exports.commands = [{
name: "beautify",
exec: function(editor) {
exports.beautify(editor.session);
},
bindKey: "Ctrl-Shift-B"
}]
});
@@ -1,366 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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 TokenIterator = require("ace/token_iterator").TokenIterator;
exports.newLines = [{
type: 'support.php_tag',
value: '<?php'
}, {
type: 'support.php_tag',
value: '<?'
}, {
type: 'support.php_tag',
value: '?>'
}, {
type: 'paren.lparen',
value: '{',
indent: true
}, {
type: 'paren.rparen',
breakBefore: true,
value: '}',
indent: false
}, {
type: 'paren.rparen',
breakBefore: true,
value: '})',
indent: false,
dontBreak: true
}, {
type: 'comment'
}, {
type: 'text',
value: ';'
}, {
type: 'text',
value: ':',
context: 'php'
}, {
type: 'keyword',
value: 'case',
indent: true,
dontBreak: true
}, {
type: 'keyword',
value: 'default',
indent: true,
dontBreak: true
}, {
type: 'keyword',
value: 'break',
indent: false,
dontBreak: true
}, {
type: 'punctuation.doctype.end',
value: '>'
}, {
type: 'meta.tag.punctuation.end',
value: '>'
}, {
type: 'meta.tag.punctuation.begin',
value: '<',
blockTag: true,
indent: true,
dontBreak: true
}, {
type: 'meta.tag.punctuation.begin',
value: '</',
indent: false,
breakBefore: true,
dontBreak: true
}, {
type: 'punctuation.operator',
value: ';'
}];
exports.spaces = [{
type: 'xml-pe',
prepend: true
},{
type: 'entity.other.attribute-name',
prepend: true
}, {
type: 'storage.type',
value: 'var',
append: true
}, {
type: 'storage.type',
value: 'function',
append: true
}, {
type: 'keyword.operator',
value: '='
}, {
type: 'keyword',
value: 'as',
prepend: true,
append: true
}, {
type: 'keyword',
value: 'function',
append: true
}, {
type: 'support.function',
next: /[^\(]/,
append: true
}, {
type: 'keyword',
value: 'or',
append: true,
prepend: true
}, {
type: 'keyword',
value: 'and',
append: true,
prepend: true
}, {
type: 'keyword',
value: 'case',
append: true
}, {
type: 'keyword.operator',
value: '||',
append: true,
prepend: true
}, {
type: 'keyword.operator',
value: '&&',
append: true,
prepend: true
}];
exports.singleTags = ['!doctype','area','base','br','hr','input','img','link','meta'];
exports.transform = function(iterator, maxPos, context) {
var token = iterator.getCurrentToken();
var newLines = exports.newLines;
var spaces = exports.spaces;
var singleTags = exports.singleTags;
var code = '';
var indentation = 0;
var dontBreak = false;
var tag;
var lastTag;
var lastToken = {};
var nextTag;
var nextToken = {};
var breakAdded = false;
var value = '';
while (token!==null) {
console.log(token);
if( !token ){
token = iterator.stepForward();
continue;
}
//change syntax
//php
if( token.type == 'support.php_tag' && token.value != '?>' ){
context = 'php';
}
else if( token.type == 'support.php_tag' && token.value == '?>' ){
context = 'html';
}
//css
else if( token.type == 'meta.tag.name.style' && context != 'css' ){
context = 'css';
}
else if( token.type == 'meta.tag.name.style' && context == 'css' ){
context = 'html';
}
//js
else if( token.type == 'meta.tag.name.script' && context != 'js' ){
context = 'js';
}
else if( token.type == 'meta.tag.name.script' && context == 'js' ){
context = 'html';
}
nextToken = iterator.stepForward();
//tag name
if (nextToken && nextToken.type.indexOf('meta.tag.name') == 0) {
nextTag = nextToken.value;
}
//don't linebreak
if ( lastToken.type == 'support.php_tag' && lastToken.value == '<?=') {
dontBreak = true;
}
//lowercase
if (token.type == 'meta.tag.name') {
token.value = token.value.toLowerCase();
}
//trim spaces
if (token.type == 'text') {
token.value = token.value.trim();
}
//skip empty tokens
if (!token.value) {
token = nextToken;
continue;
}
//put spaces back in
value = token.value;
for (var i in spaces) {
if (
token.type == spaces[i].type &&
(!spaces[i].value || token.value == spaces[i].value) &&
(
nextToken &&
(!spaces[i].next || spaces[i].next.test(nextToken.value))
)
) {
if (spaces[i].prepend) {
value = ' ' + token.value;
}
if (spaces[i].append) {
value += ' ';
}
}
}
//tag name
if (token.type.indexOf('meta.tag.name') == 0) {
tag = token.value;
//console.log(tag);
}
//new line before
breakAdded = false;
//outdent
for (i in newLines) {
if (
token.type == newLines[i].type &&
(
!newLines[i].value ||
token.value == newLines[i].value
) &&
(
!newLines[i].blockTag ||
singleTags.indexOf(nextTag) === -1
) &&
(
!newLines[i].context ||
newLines[i].context === context
)
) {
if (newLines[i].indent === false) {
indentation--;
}
if (
newLines[i].breakBefore &&
( !newLines[i].prev || newLines[i].prev.test(lastToken.value) )
) {
code += "\n";
breakAdded = true;
//indent
for (i = 0; i < indentation; i++) {
code += "\t";
}
}
break;
}
}
if (dontBreak===false) {
for (i in newLines) {
if (
lastToken.type == newLines[i].type &&
(
!newLines[i].value || lastToken.value == newLines[i].value
) &&
(
!newLines[i].blockTag ||
singleTags.indexOf(tag) === -1
) &&
(
!newLines[i].context ||
newLines[i].context === context
)
) {
if (newLines[i].indent === true) {
indentation++;
}
if (!newLines[i].dontBreak && !breakAdded) {
code += "\n";
//indent
for (i = 0; i < indentation; i++) {
code += "\t";
}
}
break;
}
}
}
code += value;
//linebreaks back on after end short php tag
if ( lastToken.type == 'support.php_tag' && lastToken.value == '?>' ) {
dontBreak = false;
}
//next token
lastTag = tag;
lastToken = token;
token = nextToken;
if (token===null) {
break;
}
}
return code;
};
});
@@ -1,980 +0,0 @@
define(function(require, exports, module) {
/* ChromeVox Ace namespace. */
var cvoxAce = {};
/* Typedefs for Closure compiler. */
/**
* @typedef {{
rate: number,
pitch: number,
volume: number,
relativePitch: number,
punctuationEcho: string
}}
*/
/* TODO(peterxiao): Export this typedef through cvox.Api. */
cvoxAce.SpeechProperty;
/**
* @typedef {{
* row: number,
* column: number
* }}
*/
cvoxAce.Cursor;
/**
* @typedef {{
type: string,
value: string
}}
}
*/
cvoxAce.Token;
/**
* These are errors and information that Ace will display in the gutter.
* @typedef {{
row: number,
column: number,
value: string
}}
}
*/
cvoxAce.Annotation;
/* Speech Properties. */
/**
* Speech property for speaking constant tokens.
* @type {cvoxAce.SpeechProperty}
*/
var CONSTANT_PROP = {
'rate': 0.8,
'pitch': 0.4,
'volume': 0.9
};
/**
* Default speech property for speaking tokens.
* @type {cvoxAce.SpeechProperty}
*/
var DEFAULT_PROP = {
'rate': 1,
'pitch': 0.5,
'volume': 0.9
};
/**
* Speech property for speaking entity tokens.
* @type {cvoxAce.SpeechProperty}
*/
var ENTITY_PROP = {
'rate': 0.8,
'pitch': 0.8,
'volume': 0.9
};
/**
* Speech property for speaking keywords.
* @type {cvoxAce.SpeechProperty}
*/
var KEYWORD_PROP = {
'rate': 0.8,
'pitch': 0.3,
'volume': 0.9
};
/**
* Speech property for speaking storage tokens.
* @type {cvoxAce.SpeechProperty}
*/
var STORAGE_PROP = {
'rate': 0.8,
'pitch': 0.7,
'volume': 0.9
};
/**
* Speech property for speaking variable tokens.
* @type {cvoxAce.SpeechProperty}
*/
var VARIABLE_PROP = {
'rate': 0.8,
'pitch': 0.8,
'volume': 0.9
};
/**
* Speech property for speaking deleted text.
* @type {cvoxAce.SpeechProperty}
*/
var DELETED_PROP = {
'punctuationEcho': 'none',
'relativePitch': -0.6
};
/* Constants for Earcons. */
var ERROR_EARCON = 'ALERT_NONMODAL';
var MODE_SWITCH_EARCON = 'ALERT_MODAL';
var NO_MATCH_EARCON = 'INVALID_KEYPRESS';
/* Constants for vim state. */
var INSERT_MODE_STATE = 'insertMode';
var COMMAND_MODE_STATE = 'start';
var REPLACE_LIST = [
{
substr: ';',
newSubstr: ' semicolon '
},
{
substr: ':',
newSubstr: ' colon '
}
];
/**
* Context menu commands.
*/
var Command = {
SPEAK_ANNOT: 'annots',
SPEAK_ALL_ANNOTS: 'all_annots',
TOGGLE_LOCATION: 'toggle_location',
SPEAK_MODE: 'mode',
SPEAK_ROW_COL: 'row_col',
TOGGLE_DISPLACEMENT: 'toggle_displacement',
FOCUS_TEXT: 'focus_text'
};
/**
* Key prefix for each shortcut.
*/
var KEY_PREFIX = 'CONTROL + SHIFT ';
/* Globals. */
cvoxAce.editor = null;
/**
* Last cursor position.
* @type {cvoxAce.Cursor}
*/
var lastCursor = null;
/**
* Table of annotations.
* @typedef {!Object.<number, Object<number, cvoxAce.Annotation>>}
*/
var annotTable = {};
/**
* Whether to speak character, word, and then line. This allows blind users
* to know the location of the cursor when they change lines.
* @typedef {boolean}
*/
var shouldSpeakRowLocation = false;
/**
* Whether to speak displacement.
* @typedef {boolean}
*/
var shouldSpeakDisplacement = false;
/**
* Whether text was changed to cause a cursor change event.
* @typedef {boolean}
*/
var changed = false;
/**
* Current state vim is in.
*/
var vimState = null;
/**
* Mapping from key code to shortcut.
*/
var keyCodeToShortcutMap = {};
/**
* Mapping from command to shortcut.
*/
var cmdToShortcutMap = {};
/**
* Get shortcut string from keyCode.
* @param {number} keyCode Key code of shortcut.
* @return {string} String representation of shortcut.
*/
var getKeyShortcutString = function(keyCode) {
return KEY_PREFIX + String.fromCharCode(keyCode);
};
/**
* Return if in vim mode.
* @return {boolean} True if in Vim mode.
*/
var isVimMode = function() {
var keyboardHandler = cvoxAce.editor.keyBinding.getKeyboardHandler();
return keyboardHandler.$id === 'ace/keyboard/vim';
};
/**
* Gets the current token.
* @param {!cvoxAce.Cursor} cursor Current position of the cursor.
* @return {!cvoxAce.Token} Token at the current position.
*/
var getCurrentToken = function(cursor) {
return cvoxAce.editor.getSession().getTokenAt(cursor.row, cursor.column + 1);
};
/**
* Gets the current line the cursor is under.
* @param {!cvoxAce.Cursor} cursor Current cursor position.
*/
var getCurrentLine = function(cursor) {
return cvoxAce.editor.getSession().getLine(cursor.row);
};
/**
* Event handler for row changes. When the user changes rows we want to speak
* the line so the user can work on this line. If shouldSpeakRowLocation is on
* then we speak the character, then the row, then the line so the user knows
* where the cursor is.
* @param {!cvoxAce.Cursor} currCursor Current cursor position.
*/
var onRowChange = function(currCursor) {
/* Notify that this line has an annotation. */
if (annotTable[currCursor.row]) {
cvox.Api.playEarcon(ERROR_EARCON);
}
if (shouldSpeakRowLocation) {
cvox.Api.stop();
speakChar(currCursor);
speakTokenQueue(getCurrentToken(currCursor));
speakLine(currCursor.row, 1);
} else {
speakLine(currCursor.row, 0);
}
};
/**
* Returns whether the cursor is at the beginning of a word. A word is
* a grouping of alphanumeric characters including underscores.
* @param {!cvoxAce.Cursor} cursor Current cursor position.
* @return {boolean} Whether there is word.
*/
var isWord = function(cursor) {
var line = getCurrentLine(cursor);
var lineSuffix = line.substr(cursor.column - 1);
if (cursor.column === 0) {
lineSuffix = ' ' + line;
}
/* Use regex to tell if the suffix is at the start of a new word. */
var firstWordRegExp = /^\W(\w+)/;
var words = firstWordRegExp.exec(lineSuffix);
return words !== null;
};
/**
* A mapping of syntax type to speech properties / expanding rules.
*/
var rules = {
'constant': {
prop: CONSTANT_PROP
},
'entity': {
prop: ENTITY_PROP
},
'keyword': {
prop: KEYWORD_PROP
},
'storage': {
prop: STORAGE_PROP
},
'variable': {
prop: VARIABLE_PROP
},
'meta': {
prop: DEFAULT_PROP,
replace: [
{
substr: '</',
newSubstr: ' closing tag '
},
{
substr: '/>',
newSubstr: ' close tag '
},
{
substr: '<',
newSubstr: ' tag start '
},
{
substr: '>',
newSubstr: ' tag end '
}
]
}
};
/**
* Default rule to be used.
*/
var DEFAULT_RULE = {
prop: DEFAULT_RULE
};
/**
* Expands substrings to how they are read based on the given rules.
* @param {string} value Text to be expanded.
* @param {Array.<Object>} replaceRules Rules to determine expansion.
* @return {string} New expanded value.
*/
var expand = function(value, replaceRules) {
var newValue = value;
for (var i = 0; i < replaceRules.length; i++) {
var replaceRule = replaceRules[i];
var regexp = new RegExp(replaceRule.substr, 'g');
newValue = newValue.replace(regexp, replaceRule.newSubstr);
}
return newValue;
};
/**
* Merges tokens from start inclusive to end exclusive.
* @param {Array.<cvoxAce.Token>} Tokens to be merged.
* @param {number} start Start index inclusive.
* @param {number} end End index exclusive.
* @return {cvoxAce.Token} Merged token.
*/
var mergeTokens = function(tokens, start, end) {
/* Different type of token found! Merge all previous like tokens. */
var newToken = {};
newToken.value = '';
newToken.type = tokens[start].type;
for (var j = start; j < end; j++) {
newToken.value += tokens[j].value;
}
return newToken;
};
/**
* Merges tokens that use the same speech properties.
* @param {Array.<cvoxAce.Token>} tokens Tokens to be merged.
* @return {Array.<cvoxAce.Token>} Merged tokens.
*/
var mergeLikeTokens = function(tokens) {
if (tokens.length <= 1) {
return tokens;
}
var newTokens = [];
var lastLikeIndex = 0;
for (var i = 1; i < tokens.length; i++) {
var lastLikeToken = tokens[lastLikeIndex];
var currToken = tokens[i];
if (getTokenRule(lastLikeToken) !== getTokenRule(currToken)) {
newTokens.push(mergeTokens(tokens, lastLikeIndex, i));
lastLikeIndex = i;
}
}
newTokens.push(mergeTokens(tokens, lastLikeIndex, tokens.length));
return newTokens;
};
/**
* Returns if given row is a whitespace row.
* @param {number} row Row.
* @return {boolean} True if row is whitespaces.
*/
var isRowWhiteSpace = function(row) {
var line = cvoxAce.editor.getSession().getLine(row);
var whiteSpaceRegexp = /^\s*$/;
return whiteSpaceRegexp.exec(line) !== null;
};
/**
* Speak the line with syntax properties.
* @param {number} row Row to speak.
* @param {number} queue Queue mode to speak.
*/
var speakLine = function(row, queue) {
var tokens = cvoxAce.editor.getSession().getTokens(row);
if (tokens.length === 0 || isRowWhiteSpace(row)) {
cvox.Api.playEarcon('EDITABLE_TEXT');
return;
}
tokens = mergeLikeTokens(tokens);
var firstToken = tokens[0];
/* Filter out first token. */
tokens = tokens.filter(function(token) {
return token !== firstToken;
});
/* Speak first token separately to flush if queue. */
speakToken_(firstToken, queue);
/* Speak rest of tokens. */
tokens.forEach(speakTokenQueue);
};
/**
* Speak the token based on the syntax of the token, flushing.
* @param {!cvoxAce.Token} token Token to speak.
* @param {number} queue Queue mode.
*/
var speakTokenFlush = function(token) {
speakToken_(token, 0);
};
/**
* Speak the token based on the syntax of the token, queueing.
* @param {!cvoxAce.Token} token Token to speak.
* @param {number} queue Queue mode.
*/
var speakTokenQueue = function(token) {
speakToken_(token, 1);
};
/**
* @param {!cvoxAce.Token} token Token to speak.
* Get the token speech property.
*/
var getTokenRule = function(token) {
/* Types are period delimited. In this case, we only syntax speak the outer
* most type of token. */
if (!token || !token.type) {
return;
}
var split = token.type.split('.');
if (split.length === 0) {
return;
}
var type = split[0];
var rule = rules[type];
if (!rule) {
return DEFAULT_RULE;
}
return rule;
};
/**
* Speak the token based on the syntax of the token.
* @private
* @param {!cvoxAce.Token} token Token to speak.
* @param {number} queue Queue mode.
*/
var speakToken_ = function(token, queue) {
var rule = getTokenRule(token);
var value = expand(token.value, REPLACE_LIST);
if (rule.replace) {
value = expand(value, rule.replace);
}
cvox.Api.speak(value, queue, rule.prop);
};
/**
* Speaks the character under the cursor. This is queued.
* @param {!cvoxAce.Cursor} cursor Current cursor position.
* @return {string} Character.
*/
var speakChar = function(cursor) {
var line = getCurrentLine(cursor);
cvox.Api.speak(line[cursor.column], 1);
};
/**
* Speaks the jump from lastCursor to currCursor. This function assumes the
* jump takes place on the current line.
* @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
* @param {!cvoxAce.Cursor} currCursor Current cursor position.
*/
var speakDisplacement = function(lastCursor, currCursor) {
var line = getCurrentLine(currCursor);
/* Get the text that we jumped past. */
var displace = line.substring(lastCursor.column, currCursor.column);
/* Speak out loud spaces. */
displace = displace.replace(/ /g, ' space ');
cvox.Api.speak(displace);
};
/**
* Speaks the word if the cursor jumped to a new word or to the beginning
* of the line. Otherwise speak the charactor.
* @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
* @param {!cvoxAce.Cursor} currCursor Current cursor position.
*/
var speakCharOrWordOrLine = function(lastCursor, currCursor) {
/* Say word only if jump. */
if (Math.abs(lastCursor.column - currCursor.column) !== 1) {
var currLineLength = getCurrentLine(currCursor).length;
/* Speak line if jumping to beginning or end of line. */
if (currCursor.column === 0 || currCursor.column === currLineLength) {
speakLine(currCursor.row, 0);
return;
}
if (isWord(currCursor)) {
cvox.Api.stop();
speakTokenQueue(getCurrentToken(currCursor));
return;
}
}
speakChar(currCursor);
};
/**
* Event handler for column changes. If shouldSpeakDisplacement is on, then
* we just speak displacements in row changes. Otherwise, we either speak
* the character for single character movements, the word when jumping to the
* next word, or the entire line if jumping to beginning or end of the line.
* @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
* @param {!cvoxAce.Cursor} currCursor Current cursor position.
*/
var onColumnChange = function(lastCursor, currCursor) {
if (!cvoxAce.editor.selection.isEmpty()) {
speakDisplacement(lastCursor, currCursor);
cvox.Api.speak('selected', 1);
}
else if (shouldSpeakDisplacement) {
speakDisplacement(lastCursor, currCursor);
} else {
speakCharOrWordOrLine(lastCursor, currCursor);
}
};
/**
* Event handler for cursor changes. Classify cursor changes as either row or
* column changes, then delegate accordingly.
* @param {!Event} evt The event.
*/
var onCursorChange = function(evt) {
/* Do not speak if cursor change was a result of text insertion. We want to
* speak the text that was inserted and not where the cursor lands. */
if (changed) {
changed = false;
return;
}
var currCursor = cvoxAce.editor.selection.getCursor();
if (currCursor.row !== lastCursor.row) {
onRowChange(currCursor);
} else {
onColumnChange(lastCursor, currCursor);
}
lastCursor = currCursor;
};
/**
* Event handler for selection changes.
* @param {!Event} evt The event.
*/
var onSelectionChange = function(evt) {
/* Assumes that when selection changes to empty, the user has unselected. */
if (cvoxAce.editor.selection.isEmpty()) {
cvox.Api.speak('unselected');
}
};
/**
* Event handler for source changes. We want auditory feedback for inserting
* and deleting text.
* @param {!Event} evt The event.
*/
var onChange = function(evt) {
var data = evt.data;
switch (data.action) {
case 'removeText':
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':
cvox.Api.speak(data.text, 0);
/* Let the future cursor change event know it's from text change. */
changed = true;
break;
}
};
/**
* Returns whether or not the annotation is new.
* @param {!cvoxAce.Annotation} annot Annotation in question.
* @return {boolean} Whether annot is new.
*/
var isNewAnnotation = function(annot) {
var row = annot.row;
var col = annot.column;
return !annotTable[row] || !annotTable[row][col];
};
/**
* Populates the annotation table.
* @param {!Array.<cvoxAce.Annotation>} annotations Array of annotations.
*/
var populateAnnotations = function(annotations) {
annotTable = {};
for (var i = 0; i < annotations.length; i++) {
var annotation = annotations[i];
var row = annotation.row;
var col = annotation.column;
if (!annotTable[row]) {
annotTable[row] = {};
}
annotTable[row][col] = annotation;
}
};
/**
* Event handler for annotation changes. We want to notify the user when an
* a new annotation appears.
* @param {!Event} evt Event.
*/
var onAnnotationChange = function(evt) {
var annotations = cvoxAce.editor.getSession().getAnnotations();
var newAnnotations = annotations.filter(isNewAnnotation);
if (newAnnotations.length > 0) {
cvox.Api.playEarcon(ERROR_EARCON);
}
populateAnnotations(annotations);
};
/**
* Speak annotation.
* @param {!cvoxAce.Annotation} annot Annotation to speak.
*/
var speakAnnot = function(annot) {
var annotText = annot.type + ' ' + annot.text + ' on ' +
rowColToString(annot.row, annot.column);
annotText = annotText.replace(';', 'semicolon');
cvox.Api.speak(annotText, 1);
};
/**
* Speak annotations in a row.
* @param {number} row Row of annotations to speak.
*/
var speakAnnotsByRow = function(row) {
var annots = annotTable[row];
for (var col in annots) {
speakAnnot(annots[col]);
}
};
/**
* Get a string representation of a row and column.
* @param {boolean} row Zero indexed row.
* @param {boolean} col Zero indexed column.
* @return {string} Row and column to be spoken.
*/
var rowColToString = function(row, col) {
return 'row ' + (row + 1) + ' column ' + (col + 1);
};
/**
* Speaks the row and column.
*/
var speakCurrRowAndCol = function() {
cvox.Api.speak(rowColToString(lastCursor.row, lastCursor.column));
};
/**
* Speaks all annotations.
*/
var speakAllAnnots = function() {
for (var row in annotTable) {
speakAnnotsByRow(row);
}
};
/**
* Speak the vim mode. If no vim mode, this function does nothing.
*/
var speakMode = function() {
if (!isVimMode()) {
return;
}
switch (cvoxAce.editor.keyBinding.$data.state) {
case INSERT_MODE_STATE:
cvox.Api.speak('Insert mode');
break;
case COMMAND_MODE_STATE:
cvox.Api.speak('Command mode');
break;
}
};
/**
* Toggle speak location.
*/
var toggleSpeakRowLocation = function() {
shouldSpeakRowLocation = !shouldSpeakRowLocation;
/* Auditory feedback of the change. */
if (shouldSpeakRowLocation) {
cvox.Api.speak('Speak location on row change enabled.');
} else {
cvox.Api.speak('Speak location on row change disabled.');
}
};
/**
* Toggle speak displacement.
*/
var toggleSpeakDisplacement = function() {
shouldSpeakDisplacement = !shouldSpeakDisplacement;
/* Auditory feedback of the change. */
if (shouldSpeakDisplacement) {
cvox.Api.speak('Speak displacement on column changes.');
} else {
cvox.Api.speak('Speak current character or word on column changes.');
}
};
/**
* Event handler for key down events. Gets the right shortcut from the map,
* and calls the associated function.
* @param {!Event} evt Keyboard event.
*/
var onKeyDown = function(evt) {
if (evt.ctrlKey && evt.shiftKey) {
var shortcut = keyCodeToShortcutMap[evt.keyCode];
if (shortcut) {
shortcut.func();
}
}
};
/**
* Event handler for status change events. Auditory feedback of changing
* between vim states.
* @param {!Event} evt Change status event.
* @param {!Object} editor Editor state.
*/
var onChangeStatus = function(evt, editor) {
if (!isVimMode()) {
return;
}
var state = editor.keyBinding.$data.state;
if (state === vimState) {
/* State hasn't changed, do nothing. */
return;
}
switch (state) {
case INSERT_MODE_STATE:
cvox.Api.playEarcon(MODE_SWITCH_EARCON);
/* When in insert mode, we want to speak out keys as feedback. */
cvox.Api.setKeyEcho(true);
break;
case COMMAND_MODE_STATE:
cvox.Api.playEarcon(MODE_SWITCH_EARCON);
/* When in command mode, we want don't speak out keys because those keys
* are not being inserted in the document. */
cvox.Api.setKeyEcho(false);
break;
}
vimState = state;
};
/**
* Handles context menu events. This is a ChromeVox feature where hitting
* the shortcut ChromeVox + comma will open up a search bar where you can
* type in various commands. All keyboard shortcuts are also commands that
* can be invoked. This handles the event that ChromeVox sends to the page.
* @param {Event} evt Event received.
*/
var contextMenuHandler = function(evt) {
var cmd = evt.detail['customCommand'];
var shortcut = cmdToShortcutMap[cmd];
if (shortcut) {
shortcut.func();
/* ChromeVox will bring focus to an element near the cursor instead of the
* text input. */
cvoxAce.editor.focus();
}
};
/**
* Initialize the ChromeVox context menu.
*/
var initContextMenu = function() {
var ACTIONS = SHORTCUTS.map(function(shortcut) {
return {
desc: shortcut.desc + getKeyShortcutString(shortcut.keyCode),
cmd: shortcut.cmd
};
});
/* Attach ContextMenuActions. */
var body = document.querySelector('body');
body.setAttribute('contextMenuActions', JSON.stringify(ACTIONS));
/* Listen for ContextMenu events. */
body.addEventListener('ATCustomEvent', contextMenuHandler, true);
};
/**
* Event handler for find events. When there is a match, we want to speak the
* line we are now at. Otherwise, we want to notify the user there was no
* match
* @param {!Event} evt The event.
*/
var onFindSearchbox = function(evt) {
if (evt.match) {
/* There is still a match! Speak the line. */
speakLine(lastCursor.row, 0);
} else {
/* No match, give auditory feedback! */
cvox.Api.playEarcon(NO_MATCH_EARCON);
}
};
/**
* Focus to text input.
*/
var focus = function() {
cvoxAce.editor.focus();
};
/**
* Shortcut definitions.
*/
var SHORTCUTS = [
{
/* 1 key. */
keyCode: 49,
func: function() {
speakAnnotsByRow(lastCursor.row);
},
cmd: Command.SPEAK_ANNOT,
desc: 'Speak annotations on line'
},
{
/* 2 key. */
keyCode: 50,
func: speakAllAnnots,
cmd: Command.SPEAK_ALL_ANNOTS,
desc: 'Speak all annotations'
},
{
/* 3 key. */
keyCode: 51,
func: speakMode,
cmd: Command.SPEAK_MODE,
desc: 'Speak Vim mode'
},
{
/* 4 key. */
keyCode: 52,
func: toggleSpeakRowLocation,
cmd: Command.TOGGLE_LOCATION,
desc: 'Toggle speak row location'
},
{
/* 5 key. */
keyCode: 53,
func: speakCurrRowAndCol,
cmd: Command.SPEAK_ROW_COL,
desc: 'Speak row and column'
},
{
/* 6 key. */
keyCode: 54,
func: toggleSpeakDisplacement,
cmd: Command.TOGGLE_DISPLACEMENT,
desc: 'Toggle speak displacement'
},
{
/* 7 key. */
keyCode: 55,
func: focus,
cmd: Command.FOCUS_TEXT,
desc: 'Focus text'
}
];
/**
* Event handler for focus events.
*/
var onFocus = function() {
cvoxAce.editor = editor;
/* Set up listeners. */
editor.getSession().selection.on('changeCursor', onCursorChange);
editor.getSession().selection.on('changeSelection', onSelectionChange);
editor.getSession().on('change', onChange);
editor.getSession().on('changeAnnotation', onAnnotationChange);
editor.on('changeStatus', onChangeStatus);
editor.on('findSearchBox', onFindSearchbox);
editor.container.addEventListener('keydown', onKeyDown);
lastCursor = editor.selection.getCursor();
};
/**
* Initialize the theme.
* @param {Object} editor Editor to use.
*/
var init = function(editor) {
onFocus();
/* Construct maps. */
SHORTCUTS.forEach(function(shortcut) {
keyCodeToShortcutMap[shortcut.keyCode] = shortcut;
cmdToShortcutMap[shortcut.cmd] = shortcut;
});
editor.on('focus', onFocus);
/* Assume we start in command mode if vim. */
if (isVimMode()) {
cvox.Api.setKeyEcho(false);
}
initContextMenu();
};
/**
* Returns if cvox exists, and the api exists.
* @return {boolean} Whether not Cvox Api exists.
*/
function cvoxApiExists() {
return (typeof(cvox) !== 'undefined') && cvox && cvox.Api;
}
/**
* Number of tries for Cvox loading.
* @type {number}
*/
var tries = 0;
/**
* Max number of tries to watch for Cvox loading.
* @type {number}
*/
var MAX_TRIES = 15;
/**
* Check for ChromeVox load.
* @param {Object} editor Editor to use.
*/
function watchForCvoxLoad(editor) {
if (cvoxApiExists()) {
init(editor);
} else {
tries++;
if (tries >= MAX_TRIES) {
return;
}
window.setTimeout(watchForCvoxLoad, 500, editor);
}
}
var Editor = require('../editor').Editor;
require('../config').defineOptions(Editor.prototype, 'editor', {
enableChromevoxEnhancements: {
set: function(val) {
if (val) {
watchForCvoxLoad(this);
}
},
value: true // turn it on by default or check for window.cvox
}
});
});
@@ -1,319 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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 ElasticTabstopsLite = function(editor) {
this.$editor = editor;
var self = this;
var changedRows = [];
var recordChanges = false;
this.onAfterExec = function() {
recordChanges = false;
self.processRows(changedRows);
changedRows = [];
};
this.onExec = function() {
recordChanges = true;
};
this.onChange = function(e) {
var range = e.data.range
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);
}
};
};
(function() {
this.processRows = function(rows) {
this.$inChange = true;
var checkedRows = [];
for (var r = 0, rowCount = rows.length; r < rowCount; r++) {
var row = rows[r];
if (checkedRows.indexOf(row) > -1)
continue;
var cellWidthObj = this.$findCellWidthsForBlock(row);
var cellWidths = this.$setBlockCellWidthsToMax(cellWidthObj.cellWidths);
var rowIndex = cellWidthObj.firstRow;
for (var w = 0, l = cellWidths.length; w < l; w++) {
var widths = cellWidths[w];
checkedRows.push(rowIndex);
this.$adjustRow(rowIndex, widths);
rowIndex++;
}
}
this.$inChange = false;
};
this.$findCellWidthsForBlock = function(row) {
var cellWidths = [], widths;
// starting row and backward
var rowIter = row;
while (rowIter >= 0) {
widths = this.$cellWidthsForRow(rowIter);
if (widths.length == 0)
break;
cellWidths.unshift(widths);
rowIter--;
}
var firstRow = rowIter + 1;
// forward (not including starting row)
rowIter = row;
var numRows = this.$editor.session.getLength();
while (rowIter < numRows - 1) {
rowIter++;
widths = this.$cellWidthsForRow(rowIter);
if (widths.length == 0)
break;
cellWidths.push(widths);
}
return { cellWidths: cellWidths, firstRow: firstRow };
};
this.$cellWidthsForRow = function(row) {
var selectionColumns = this.$selectionColumnsForRow(row);
// todo: support multicursor
var tabs = [-1].concat(this.$tabsForRow(row));
var widths = tabs.map(function(el) { return 0; } ).slice(1);
var line = this.$editor.session.getLine(row);
for (var i = 0, len = tabs.length - 1; i < len; i++) {
var leftEdge = tabs[i]+1;
var rightEdge = tabs[i+1];
var rightmostSelection = this.$rightmostSelectionInCell(selectionColumns, rightEdge);
var cell = line.substring(leftEdge, rightEdge);
widths[i] = Math.max(cell.replace(/\s+$/g,'').length, rightmostSelection - leftEdge);
}
return widths;
};
this.$selectionColumnsForRow = function(row) {
var selections = [], cursor = this.$editor.getCursorPosition();
if (this.$editor.session.getSelection().isEmpty()) {
// todo: support multicursor
if (row == cursor.row)
selections.push(cursor.column);
}
return selections;
};
this.$setBlockCellWidthsToMax = function(cellWidths) {
var startingNewBlock = true, blockStartRow, blockEndRow, maxWidth;
var columnInfo = this.$izip_longest(cellWidths);
for (var c = 0, l = columnInfo.length; c < l; c++) {
var column = columnInfo[c];
if (!column.push) {
console.error(column);
continue;
}
// add an extra None to the end so that the end of the column automatically
// finishes a block
column.push(NaN);
for (var r = 0, s = column.length; r < s; r++) {
var width = column[r];
if (startingNewBlock) {
blockStartRow = r;
maxWidth = 0;
startingNewBlock = false;
}
if (isNaN(width)) {
// block ended
blockEndRow = r;
for (var j = blockStartRow; j < blockEndRow; j++) {
cellWidths[j][c] = maxWidth;
}
startingNewBlock = true;
}
maxWidth = Math.max(maxWidth, width);
}
}
return cellWidths;
};
this.$rightmostSelectionInCell = function(selectionColumns, cellRightEdge) {
var rightmost = 0;
if (selectionColumns.length) {
var lengths = [];
for (var s = 0, length = selectionColumns.length; s < length; s++) {
if (selectionColumns[s] <= cellRightEdge)
lengths.push(s);
else
lengths.push(0);
}
rightmost = Math.max.apply(Math, lengths);
}
return rightmost;
};
this.$tabsForRow = function(row) {
var rowTabs = [], line = this.$editor.session.getLine(row),
re = /\t/g, match;
while ((match = re.exec(line)) != null) {
rowTabs.push(match.index);
}
return rowTabs;
};
this.$adjustRow = function(row, widths) {
var rowTabs = this.$tabsForRow(row);
if (rowTabs.length == 0)
return;
var bias = 0, location = -1;
// this always only contains two elements, so we're safe in the loop below
var expandedSet = this.$izip(widths, rowTabs);
for (var i = 0, l = expandedSet.length; i < l; i++) {
var w = expandedSet[i][0], it = expandedSet[i][1];
location += 1 + w;
it += bias;
var difference = location - it;
if (difference == 0)
continue;
var partialLine = this.$editor.session.getLine(row).substr(0, it );
var strippedPartialLine = partialLine.replace(/\s*$/g, "");
var ispaces = partialLine.length - strippedPartialLine.length;
if (difference > 0) {
// put the spaces after the tab and then delete the tab, so any insertion
// points behave as expected
this.$editor.session.getDocument().insertInLine({row: row, column: it + 1}, Array(difference + 1).join(" ") + "\t");
this.$editor.session.getDocument().removeInLine(row, it, it + 1);
bias += difference;
}
if (difference < 0 && ispaces >= -difference) {
this.$editor.session.getDocument().removeInLine(row, it + difference, it);
bias += difference;
}
}
};
// the is a (naive) Python port--but works for these purposes
this.$izip_longest = function(iterables) {
if (!iterables[0])
return [];
var longest = iterables[0].length;
var iterablesLength = iterables.length;
for (var i = 1; i < iterablesLength; i++) {
var iLength = iterables[i].length;
if (iLength > longest)
longest = iLength;
}
var expandedSet = [];
for (var l = 0; l < longest; l++) {
var set = [];
for (var i = 0; i < iterablesLength; i++) {
if (iterables[i][l] === "")
set.push(NaN);
else
set.push(iterables[i][l]);
}
expandedSet.push(set);
}
return expandedSet;
};
// an even more (naive) Python port
this.$izip = function(widths, tabs) {
// grab the shorter size
var size = widths.length >= tabs.length ? tabs.length : widths.length;
var expandedSet = [];
for (var i = 0; i < size; i++) {
var set = [ widths[i], tabs[i] ];
expandedSet.push(set);
}
return expandedSet;
};
}).call(ElasticTabstopsLite.prototype);
exports.ElasticTabstopsLite = ElasticTabstopsLite;
var Editor = require("../editor").Editor;
require("../config").defineOptions(Editor.prototype, "editor", {
useElasticTabstops: {
set: function(val) {
if (val) {
if (!this.elasticTabstops)
this.elasticTabstops = new ElasticTabstopsLite(this);
this.commands.on("afterExec", this.elasticTabstops.onAfterExec);
this.commands.on("exec", this.elasticTabstops.onExec);
this.on("change", this.elasticTabstops.onChange);
} else if (this.elasticTabstops) {
this.commands.removeListener("afterExec", this.elasticTabstops.onAfterExec);
this.commands.removeListener("exec", this.elasticTabstops.onExec);
this.removeListener("change", this.elasticTabstops.onChange);
}
}
}
});
});
@@ -1,434 +0,0 @@
/* ***** 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 HashHandler = require("ace/keyboard/hash_handler").HashHandler;
var Editor = require("ace/editor").Editor;
var snippetManager = require("ace/snippets").snippetManager;
var Range = require("ace/range").Range;
var emmet, emmetPath;
/**
* Implementation of {@link IEmmetEditor} interface for Ace
*/
function AceEmmetEditor() {}
AceEmmetEditor.prototype = {
setupContext: function(editor) {
this.ace = editor;
this.indentation = editor.session.getTabString();
if (!emmet)
emmet = window.emmet;
emmet.require("resources").setVariable("indentation", this.indentation);
this.$syntax = null;
this.$syntax = this.getSyntax();
},
/**
* Returns character indexes of selected text: object with <code>start</code>
* and <code>end</code> properties. If there's no selection, should return
* object with <code>start</code> and <code>end</code> properties referring
* to current caret position
* @return {Object}
* @example
* var selection = editor.getSelectionRange();
* alert(selection.start + ', ' + selection.end);
*/
getSelectionRange: function() {
// TODO should start be caret position instead?
var range = this.ace.getSelectionRange();
var doc = this.ace.session.doc;
return {
start: doc.positionToIndex(range.start),
end: doc.positionToIndex(range.end)
};
},
/**
* Creates selection from <code>start</code> to <code>end</code> character
* indexes. If <code>end</code> is ommited, this method should place caret
* and <code>start</code> index
* @param {Number} start
* @param {Number} [end]
* @example
* editor.createSelection(10, 40);
*
* //move caret to 15th character
* editor.createSelection(15);
*/
createSelection: function(start, end) {
var doc = this.ace.session.doc;
this.ace.selection.setRange({
start: doc.indexToPosition(start),
end: doc.indexToPosition(end)
});
},
/**
* Returns current line's start and end indexes as object with <code>start</code>
* and <code>end</code> properties
* @return {Object}
* @example
* var range = editor.getCurrentLineRange();
* alert(range.start + ', ' + range.end);
*/
getCurrentLineRange: function() {
var ace = this.ace;
var row = ace.getCursorPosition().row;
var lineLength = ace.session.getLine(row).length;
var index = ace.session.doc.positionToIndex({row: row, column: 0});
return {
start: index,
end: index + lineLength
};
},
/**
* Returns current caret position
* @return {Number|null}
*/
getCaretPos: function(){
var pos = this.ace.getCursorPosition();
return this.ace.session.doc.positionToIndex(pos);
},
/**
* Set new caret position
* @param {Number} index Caret position
*/
setCaretPos: function(index){
var pos = this.ace.session.doc.indexToPosition(index);
this.ace.selection.moveToPosition(pos);
},
/**
* Returns content of current line
* @return {String}
*/
getCurrentLine: function() {
var row = this.ace.getCursorPosition().row;
return this.ace.session.getLine(row);
},
/**
* Replace editor's content or it's part (from <code>start</code> to
* <code>end</code> index). If <code>value</code> contains
* <code>caret_placeholder</code>, the editor will put caret into
* this position. If you skip <code>start</code> and <code>end</code>
* arguments, the whole target's content will be replaced with
* <code>value</code>.
*
* If you pass <code>start</code> argument only,
* the <code>value</code> will be placed at <code>start</code> string
* index of current content.
*
* If you pass <code>start</code> and <code>end</code> arguments,
* the corresponding substring of current target's content will be
* replaced with <code>value</code>.
* @param {String} value Content you want to paste
* @param {Number} [start] Start index of editor's content
* @param {Number} [end] End index of editor's content
* @param {Boolean} [noIndent] Do not auto indent <code>value</code>
*/
replaceContent: function(value, start, end, noIndent) {
if (end == null)
end = start == null ? this.getContent().length : start;
if (start == null)
start = 0;
var editor = this.ace;
var doc = editor.session.doc;
var range = Range.fromPoints(doc.indexToPosition(start), doc.indexToPosition(end));
editor.session.remove(range);
range.end = range.start;
//editor.selection.setRange(range);
value = this.$updateTabstops(value);
snippetManager.insertSnippet(editor, value);
},
/**
* Returns editor's content
* @return {String}
*/
getContent: function(){
return this.ace.getValue();
},
/**
* Returns current editor's syntax mode
* @return {String}
*/
getSyntax: function() {
if (this.$syntax)
return this.$syntax;
var syntax = this.ace.session.$modeId.split("/").pop();
if (syntax == "html" || syntax == "php") {
var cursor = this.ace.getCursorPosition();
var state = this.ace.session.getState(cursor.row);
if (typeof state != "string")
state = state[0];
if (state) {
state = state.split("-");
if (state.length > 1)
syntax = state[0];
else if (syntax == "php")
syntax = "html";
}
}
return syntax;
},
/**
* Returns current output profile name (@see emmet#setupProfile)
* @return {String}
*/
getProfileName: function() {
switch(this.getSyntax()) {
case "css": return "css";
case "xml":
case "xsl":
return "xml";
case "html":
var profile = emmet.require("resources").getVariable("profile");
// no forced profile, guess from content html or xhtml?
if (!profile)
profile = this.ace.session.getLines(0,2).join("").search(/<!DOCTYPE[^>]+XHTML/i) != -1 ? "xhtml": "html";
return profile;
}
return "xhtml";
},
/**
* Ask user to enter something
* @param {String} title Dialog title
* @return {String} Entered data
* @since 0.65
*/
prompt: function(title) {
return prompt(title);
},
/**
* Returns current selection
* @return {String}
* @since 0.65
*/
getSelection: function() {
return this.ace.session.getTextRange();
},
/**
* Returns current editor's file path
* @return {String}
* @since 0.65
*/
getFilePath: function() {
return "";
},
// update tabstops: make sure all caret placeholders are unique
// by default, abbreviation parser generates all unlinked (un-mirrored)
// tabstops as ${0}, so we have upgrade all caret tabstops with unique
// positions but make sure that all other tabstops are not linked accidentally
// based on https://github.com/sergeche/emmet-sublime/blob/master/editor.js#L119-L171
$updateTabstops: function(value) {
var base = 1000;
var zeroBase = 0;
var lastZero = null;
var range = emmet.require('range');
var ts = emmet.require('tabStops');
var settings = emmet.require('resources').getVocabulary("user");
var tabstopOptions = {
tabstop: function(data) {
var group = parseInt(data.group, 10);
var isZero = group === 0;
if (isZero)
group = ++zeroBase;
else
group += base;
var placeholder = data.placeholder;
if (placeholder) {
// recursively update nested tabstops
placeholder = ts.processText(placeholder, tabstopOptions);
}
var result = '${' + group + (placeholder ? ':' + placeholder : '') + '}';
if (isZero) {
lastZero = range.create(data.start, result);
}
return result;
},
escape: function(ch) {
if (ch == '$') return '\\$';
if (ch == '\\') return '\\\\';
return ch;
}
};
value = ts.processText(value, tabstopOptions);
if (settings.variables['insert_final_tabstop'] && !/\$\{0\}$/.test(value)) {
value += '${0}';
} else if (lastZero) {
value = emmet.require('utils').replaceSubstring(value, '${0}', lastZero);
}
return value;
}
};
var keymap = {
expand_abbreviation: {"mac": "ctrl+alt+e", "win": "alt+e"},
match_pair_outward: {"mac": "ctrl+d", "win": "ctrl+,"},
match_pair_inward: {"mac": "ctrl+j", "win": "ctrl+shift+0"},
matching_pair: {"mac": "ctrl+alt+j", "win": "alt+j"},
next_edit_point: "alt+right",
prev_edit_point: "alt+left",
toggle_comment: {"mac": "command+/", "win": "ctrl+/"},
split_join_tag: {"mac": "shift+command+'", "win": "shift+ctrl+`"},
remove_tag: {"mac": "command+'", "win": "shift+ctrl+;"},
evaluate_math_expression: {"mac": "shift+command+y", "win": "shift+ctrl+y"},
increment_number_by_1: "ctrl+up",
decrement_number_by_1: "ctrl+down",
increment_number_by_01: "alt+up",
decrement_number_by_01: "alt+down",
increment_number_by_10: {"mac": "alt+command+up", "win": "shift+alt+up"},
decrement_number_by_10: {"mac": "alt+command+down", "win": "shift+alt+down"},
select_next_item: {"mac": "shift+command+.", "win": "shift+ctrl+."},
select_previous_item: {"mac": "shift+command+,", "win": "shift+ctrl+,"},
reflect_css_value: {"mac": "shift+command+r", "win": "shift+ctrl+r"},
encode_decode_data_url: {"mac": "shift+ctrl+d", "win": "ctrl+'"},
// update_image_size: {"mac": "shift+ctrl+i", "win": "ctrl+u"},
// expand_as_you_type: "ctrl+alt+enter",
// wrap_as_you_type: {"mac": "shift+ctrl+g", "win": "shift+ctrl+g"},
expand_abbreviation_with_tab: "Tab",
wrap_with_abbreviation: {"mac": "shift+ctrl+a", "win": "shift+ctrl+a"}
};
var editorProxy = new AceEmmetEditor();
exports.commands = new HashHandler();
exports.runEmmetCommand = function(editor) {
try {
editorProxy.setupContext(editor);
if (editorProxy.getSyntax() == "php")
return false;
var actions = emmet.require("actions");
if (this.action == "expand_abbreviation_with_tab") {
if (!editor.selection.isEmpty())
return false;
}
if (this.action == "wrap_with_abbreviation") {
// without setTimeout prompt doesn't work on firefox
return setTimeout(function() {
actions.run("wrap_with_abbreviation", editorProxy);
}, 0);
}
var pos = editor.selection.lead;
var token = editor.session.getTokenAt(pos.row, pos.column);
if (token && /\btag\b/.test(token.type))
return false;
var result = actions.run(this.action, editorProxy);
} catch(e) {
editor._signal("changeStatus", typeof e == "string" ? e : e.message);
console.log(e);
result = false;
}
return result;
};
for (var command in keymap) {
exports.commands.addCommand({
name: "emmet:" + command,
action: command,
bindKey: keymap[command],
exec: exports.runEmmetCommand,
multiSelectAction: "forEach"
});
}
exports.updateCommands = function(editor, enabled) {
if (enabled) {
editor.keyBinding.addKeyboardHandler(exports.commands);
} else {
editor.keyBinding.removeKeyboardHandler(exports.commands);
}
};
exports.isSupportedMode = function(modeId) {
return modeId && /css|less|scss|sass|stylus|html|php|twig|ejs/.test(modeId);
};
var onChangeMode = function(e, target) {
var editor = target;
if (!editor)
return;
var enabled = exports.isSupportedMode(editor.session.$modeId);
if (e.enableEmmet === false)
enabled = false;
if (enabled) {
if (typeof emmetPath == "string") {
require("ace/config").loadModule(emmetPath, function() {
emmetPath = null;
});
}
}
exports.updateCommands(editor, enabled);
};
exports.AceEmmetEditor = AceEmmetEditor;
require("ace/config").defineOptions(Editor.prototype, "editor", {
enableEmmet: {
set: function(val) {
this[val ? "on" : "removeListener"]("changeMode", onChangeMode);
onChangeMode({enableEmmet: !!val}, this);
},
value: true
}
});
exports.setCore = function(e) {
if (typeof e == "string")
emmetPath = e;
else
emmet = e;
};
});
@@ -1,214 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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 LineWidgets = require("../line_widgets").LineWidgets;
var dom = require("../lib/dom");
var Range = require("../range").Range;
function binarySearch(array, needle, comparator) {
var first = 0;
var last = array.length - 1;
while (first <= last) {
var mid = (first + last) >> 1;
var c = comparator(needle, array[mid]);
if (c > 0)
first = mid + 1;
else if (c < 0)
last = mid - 1;
else
return mid;
}
// Return the nearest lesser index, "-1" means "0, "-2" means "1", etc.
return -(first + 1);
}
function findAnnotations(session, row, dir) {
var annotations = session.getAnnotations().sort(Range.comparePoints);
if (!annotations.length)
return;
var i = binarySearch(annotations, {row: row, column: -1}, Range.comparePoints);
if (i < 0)
i = -i - 1;
if (i >= annotations.length - 1)
i = dir > 0 ? 0 : annotations.length - 1;
else if (i === 0 && dir < 0)
i = annotations.length - 1;
var annotation = annotations[i];
if (!annotation || !dir)
return;
if (annotation.row === row) {
do {
annotation = annotations[i += dir];
} while (annotation && annotation.row === row);
if (!annotation)
return annotations.slice();
}
var matched = [];
row = annotation.row;
do {
matched[dir < 0 ? "unshift" : "push"](annotation);
annotation = annotations[i += dir];
} while (annotation && annotation.row == row);
return matched.length && matched;
}
exports.showErrorMarker = function(editor, dir) {
var session = editor.session;
if (!session.widgetManager) {
session.widgetManager = new LineWidgets(session);
session.widgetManager.attach(editor);
}
var pos = editor.getCursorPosition();
var row = pos.row;
var oldWidget = session.lineWidgets && session.lineWidgets[row];
if (oldWidget) {
oldWidget.destroy();
} else {
row -= dir;
}
var annotations = findAnnotations(session, row, dir);
var gutterAnno;
if (annotations) {
var annotation = annotations[0];
pos.column = (annotation.pos && typeof annotation.column != "number"
? annotation.pos.sc
: annotation.column) || 0;
pos.row = annotation.row;
gutterAnno = editor.renderer.$gutterLayer.$annotations[pos.row];
} else if (oldWidget) {
return;
} else {
gutterAnno = {
text: ["Looks good!"],
className: "ace_ok"
};
}
editor.session.unfold(pos.row);
editor.selection.moveToPosition(pos);
var w = {
row: pos.row,
fixedWidth: true,
coverGutter: true,
el: dom.createElement("div")
};
var el = w.el.appendChild(dom.createElement("div"));
var arrow = w.el.appendChild(dom.createElement("div"));
arrow.className = "error_widget_arrow " + gutterAnno.className;
var left = editor.renderer.$cursorLayer
.getPixelPosition(pos).left;
arrow.style.left = left + editor.renderer.gutterWidth - 5 + "px";
w.el.className = "error_widget_wrapper";
el.className = "error_widget " + gutterAnno.className;
el.innerHTML = gutterAnno.text.join("<br>");
el.appendChild(dom.createElement("div"));
var kb = function(_, hashId, keyString) {
if (hashId === 0 && (keyString === "esc" || keyString === "return")) {
w.destroy();
return {command: "null"};
}
};
w.destroy = function() {
if (editor.$mouseHandler.isMousePressed)
return;
editor.keyBinding.removeKeyboardHandler(kb);
session.widgetManager.removeLineWidget(w);
editor.off("changeSelection", w.destroy);
editor.off("changeSession", w.destroy);
editor.off("mouseup", w.destroy);
editor.off("change", w.destroy);
};
editor.keyBinding.addKeyboardHandler(kb);
editor.on("changeSelection", w.destroy);
editor.on("changeSession", w.destroy);
editor.on("mouseup", w.destroy);
editor.on("change", w.destroy);
editor.session.widgetManager.addLineWidget(w);
w.el.onmousedown = editor.focus.bind(editor);
editor.renderer.scrollCursorIntoView(null, 0.5, {bottom: w.el.offsetHeight});
};
dom.importCssString("\
.error_widget_wrapper {\
background: inherit;\
color: inherit;\
border:none\
}\
.error_widget {\
border-top: solid 2px;\
border-bottom: solid 2px;\
margin: 5px 0;\
padding: 10px 40px;\
white-space: pre-wrap;\
}\
.error_widget.ace_error, .error_widget_arrow.ace_error{\
border-color: #ff5a5a\
}\
.error_widget.ace_warning, .error_widget_arrow.ace_warning{\
border-color: #F1D817\
}\
.error_widget.ace_info, .error_widget_arrow.ace_info{\
border-color: #5a5a5a\
}\
.error_widget.ace_ok, .error_widget_arrow.ace_ok{\
border-color: #5aaa5a\
}\
.error_widget_arrow {\
position: absolute;\
border: solid 5px;\
border-top-color: transparent!important;\
border-right-color: transparent!important;\
border-left-color: transparent!important;\
top: -5px;\
}\
", "");
});
@@ -1,86 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define, require */
/**
* Show Keyboard Shortcuts
* @fileOverview Show Keyboard Shortcuts <br />
* Generates a menu which displays the keyboard shortcuts.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
define(function(require, exports, module) {
"use strict";
var Editor = require("ace/editor").Editor;
/**
* Generates a menu which displays the keyboard shortcuts.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {ace.Editor} editor An instance of the ace editor.
*/
function showKeyboardShortcuts (editor) {
// make sure the menu isn't open already.
if(!document.getElementById('kbshortcutmenu')) {
var overlayPage = require('./menu_tools/overlay_page').overlayPage;
var getEditorKeybordShortcuts = require('./menu_tools/get_editor_keyboard_shortcuts').getEditorKeybordShortcuts;
var kb = getEditorKeybordShortcuts(editor);
var el = document.createElement('div');
var commands = kb.reduce(function(previous, current) {
return previous + '<div class="ace_optionsMenuEntry"><span class="ace_optionsMenuCommand">'
+ current.command + '</span> : '
+ '<span class="ace_optionsMenuKey">' + current.key + '</span></div>';
}, '');
el.id = 'kbshortcutmenu';
el.innerHTML = '<h1>Keyboard Shortcuts</h1>' + commands + '</div>';
overlayPage(editor, el, '0', '0', '0', null);
}
};
module.exports.init = function(editor) {
Editor.prototype.showKeyboardShortcuts = function() {
showKeyboardShortcuts(this);
};
editor.commands.addCommands([{
name: "showKeyboardShortcuts",
bindKey: {win: "Ctrl-Alt-h", mac: "Command-Alt-h"},
exec: function(editor, line) {
editor.showKeyboardShortcuts();
}
}]);
};
});
@@ -1,226 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* 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
* 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 snippetManager = require("../snippets").snippetManager;
var Autocomplete = require("../autocomplete").Autocomplete;
var config = require("../config");
var lang = require("../lib/lang");
var util = require("../autocomplete/util");
var textCompleter = require("../autocomplete/text_completer");
var keyWordCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (session.$mode.completer) {
return session.$mode.completer.getCompletions(editor, session, pos, prefix, callback);
}
var state = editor.session.getState(pos.row);
var completions = session.$mode.getCompletions(state, session, pos, prefix);
callback(null, completions);
}
};
var snippetCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
var snippetMap = snippetManager.snippetMap;
var completions = [];
snippetManager.getActiveScopes(editor).forEach(function(scope) {
var snippets = snippetMap[scope] || [];
for (var i = snippets.length; i--;) {
var s = snippets[i];
var caption = s.name || s.tabTrigger;
if (!caption)
continue;
completions.push({
caption: caption,
snippet: s.content,
meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet",
type: "snippet"
});
}
}, this);
callback(null, completions);
},
getDocTooltip: function(item) {
if (item.type == "snippet" && !item.docHTML) {
item.docHTML = [
"<b>", lang.escapeHTML(item.caption), "</b>", "<hr></hr>",
lang.escapeHTML(item.snippet)
].join("");
}
}
};
var completers = [snippetCompleter, textCompleter, keyWordCompleter];
// Allows default completers to be removed or replaced with a explict set of completers
// A null argument here will result in an empty completer array, not a null attribute
exports.setCompleters = function(val) {
completers = val || [];
};
exports.addCompleter = function(completer) {
completers.push(completer);
};
// Exports existing completer so that user can construct his own set of completers.
exports.textCompleter = textCompleter;
exports.keyWordCompleter = keyWordCompleter;
exports.snippetCompleter = snippetCompleter;
var expandSnippet = {
name: "expandSnippet",
exec: function(editor) {
return snippetManager.expandWithTab(editor);
},
bindKey: "Tab"
};
var onChangeMode = function(e, editor) {
loadSnippetsForMode(editor.session.$mode);
};
var loadSnippetsForMode = function(mode) {
var id = mode.$id;
if (!snippetManager.files)
snippetManager.files = {};
loadSnippetFile(id);
if (mode.modes)
mode.modes.forEach(loadSnippetsForMode);
};
var loadSnippetFile = function(id) {
if (!id || snippetManager.files[id])
return;
var snippetFilePath = id.replace("mode", "snippets");
snippetManager.files[id] = {};
config.loadModule(snippetFilePath, function(m) {
if (m) {
snippetManager.files[id] = m;
if (!m.snippets && m.snippetText)
m.snippets = snippetManager.parseSnippetFile(m.snippetText);
snippetManager.register(m.snippets || [], m.scope);
if (m.includeScopes) {
snippetManager.snippetMap[m.scope].includeScopes = m.includeScopes;
m.includeScopes.forEach(function(x) {
loadSnippetFile("ace/mode/" + x);
});
}
}
});
};
function getCompletionPrefix(editor) {
var pos = editor.getCursorPosition();
var line = editor.session.getLine(pos.row);
var prefix;
// Try to find custom prefixes on the completers
editor.completers.forEach(function(completer) {
if (completer.identifierRegexps) {
completer.identifierRegexps.forEach(function(identifierRegex) {
if (!prefix && identifierRegex)
prefix = util.retrievePrecedingIdentifier(line, pos.column, identifierRegex);
});
}
});
return prefix || util.retrievePrecedingIdentifier(line, pos.column);
}
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
if (e.command.name === "backspace") {
if (hasCompleter && !getCompletionPrefix(editor))
editor.completer.detach();
}
else if (e.command.name === "insertstring") {
var prefix = getCompletionPrefix(editor);
// Only autocomplete if there's a prefix that can be matched
if (prefix && !hasCompleter) {
if (!editor.completer) {
// Create new autocompleter
editor.completer = new Autocomplete();
}
// Disable autoInsert
editor.completer.autoInsert = false;
editor.completer.showPopup(editor);
}
}
};
var Editor = require("../editor").Editor;
require("../config").defineOptions(Editor.prototype, "editor", {
enableBasicAutocompletion: {
set: function(val) {
if (val) {
if (!this.completers)
this.completers = Array.isArray(val)? val: completers;
this.commands.addCommand(Autocomplete.startCommand);
} else {
this.commands.removeCommand(Autocomplete.startCommand);
}
},
value: false
},
/**
* Enable live autocomplete. If the value is an array, it is assumed to be an array of completers
* and will use them instead of the default completers.
*/
enableLiveAutocompletion: {
set: function(val) {
if (val) {
if (!this.completers)
this.completers = Array.isArray(val)? val: completers;
// On each change automatically trigger the autocomplete
this.commands.on('afterExec', doLiveAutocomplete);
} else {
this.commands.removeListener('afterExec', doLiveAutocomplete);
}
},
value: false
},
enableSnippets: {
set: function(val) {
if (val) {
this.commands.addCommand(expandSnippet);
this.on("changeMode", onChangeMode);
onChangeMode(null, this);
} else {
this.commands.removeCommand(expandSnippet);
this.off("changeMode", onChangeMode);
}
},
value: false
}
});
});
@@ -1,78 +0,0 @@
/* ***** 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) {
var Editor = require("ace/editor").Editor;
require("../config").defineOptions(Editor.prototype, "editor", {
enableLinking: {
set: function(val) {
if (val) {
this.on("click", onClick);
this.on("mousemove", onMouseMove);
} else {
this.off("click", onClick);
this.off("mousemove", onMouseMove);
}
},
value: false
}
})
function onMouseMove(e) {
var editor = e.editor;
var ctrl = e.getAccelKey();
if (ctrl) {
var editor = e.editor;
var docPos = e.getDocumentPosition();
var session = editor.session;
var token = session.getTokenAt(docPos.row, docPos.column);
editor._emit("linkHover", {position: docPos, token: token});
}
}
function onClick(e) {
var ctrl = e.getAccelKey();
var button = e.getButton();
if (button == 0 && ctrl) {
var editor = e.editor;
var docPos = e.getDocumentPosition();
var session = editor.session;
var token = session.getTokenAt(docPos.row, docPos.column);
editor._emit("linkClick", {position: docPos, token: token});
}
}
});
@@ -1,109 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define, require */
/**
* Add Editor Menu Options
* @fileOverview Add Editor Menu Options <br />
* The menu options property needs to be added to the editor
* so that the settings menu can know about options for
* selection elements and track which option is selected.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
define(function(require, exports, module) {
'use strict';
/**
* The menu options property needs to be added to the editor
* so that the settings menu can know about options for
* selection elements and track which option is selected.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {ace.Editor} editor An instance of the ace editor.
*/
module.exports.addEditorMenuOptions = function addEditorMenuOptions (editor) {
var modelist = require('../modelist');
var themelist = require('../themelist');
editor.menuOptions = {
setNewLineMode: [{
textContent: "unix",
value: "unix"
}, {
textContent: "windows",
value: "windows"
}, {
textContent: "auto",
value: "auto"
}],
setTheme: [],
setMode: [],
setKeyboardHandler: [{
textContent: "ace",
value: ""
}, {
textContent: "vim",
value: "ace/keyboard/vim"
}, {
textContent: "emacs",
value: "ace/keyboard/emacs"
}, {
textContent: "textarea",
value: "ace/keyboard/textarea"
}, {
textContent: "sublime",
value: "ace/keyboard/sublime"
}]
};
editor.menuOptions.setTheme = themelist.themes.map(function(theme) {
return {
textContent: theme.caption,
value: theme.theme
};
});
editor.menuOptions.setMode = modelist.modes.map(function(mode) {
return {
textContent: mode.name,
value: mode.mode
};
});
};
});
@@ -1,148 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define, require */
/**
* Element Generator
* @fileOverview Element Generator <br />
* Contains methods for generating elements.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
define(function(require, exports, module) {
'use strict';
/**
* Creates a DOM option element
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {object} obj An object containing properties to add to the dom
* element. If one of those properties is named `selected` then it will be
* added as an attribute on the element instead.
*/
module.exports.createOption = function createOption (obj) {
var attribute;
var el = document.createElement('option');
for(attribute in obj) {
if(obj.hasOwnProperty(attribute)) {
if(attribute === 'selected') {
el.setAttribute(attribute, obj[attribute]);
} else {
el[attribute] = obj[attribute];
}
}
}
return el;
};
/**
* Creates a DOM checkbox element.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {string} id The id of the element.
* @param {boolean} checked Whether or not the element is checked.
* @param {string} clss The class of the element.
* @returns {DOMElement} Returns a checkbox element reference.
*/
module.exports.createCheckbox = function createCheckbox (id, checked, clss) {
var el = document.createElement('input');
el.setAttribute('type', 'checkbox');
el.setAttribute('id', id);
el.setAttribute('name', id);
el.setAttribute('value', checked);
el.setAttribute('class', clss);
if(checked) {
el.setAttribute('checked', 'checked');
}
return el;
};
/**
* Creates a DOM text input element.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {string} id The id of the element.
* @param {string} value The default value of the input element.
* @param {string} clss The class of the element.
* @returns {DOMElement} Returns an input element reference.
*/
module.exports.createInput = function createInput (id, value, clss) {
var el = document.createElement('input');
el.setAttribute('type', 'text');
el.setAttribute('id', id);
el.setAttribute('name', id);
el.setAttribute('value', value);
el.setAttribute('class', clss);
return el;
};
/**
* Creates a DOM label element.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {string} text The label text.
* @param {string} labelFor The id of the element being labeled.
* @returns {DOMElement} Returns a label element reference.
*/
module.exports.createLabel = function createLabel (text, labelFor) {
var el = document.createElement('label');
el.setAttribute('for', labelFor);
el.textContent = text;
return el;
};
/**
* Creates a DOM selection element.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {string} id The id of the element.
* @param {string} values An array of objects suitable for `createOption`
* @param {string} clss The class of the element.
* @returns {DOMElement} Returns a selection element reference.
* @see ace/ext/element_generator.createOption
*/
module.exports.createSelection = function createSelection (id, values, clss) {
var el = document.createElement('select');
el.setAttribute('id', id);
el.setAttribute('name', id);
el.setAttribute('class', clss);
values.forEach(function(item) {
el.appendChild(module.exports.createOption(item));
});
return el;
};
});
@@ -1,264 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define*/
/**
* Generates the settings menu
* @fileOverview Generates the settings menu.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
define(function(require, exports, module) {
'use strict';
var egen = require('./element_generator');
var addEditorMenuOptions = require('./add_editor_menu_options').addEditorMenuOptions;
var getSetFunctions = require('./get_set_functions').getSetFunctions;
/**
* Generates an interactive menu with settings useful to end users.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {ace.Editor} editor An instance of the ace editor.
*/
module.exports.generateSettingsMenu = function generateSettingsMenu (editor) {
/**
* container for dom elements that will go in the menu.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
var elements = [];
/**
* Sorts the menu entries (elements var) so they'll appear in alphabetical order
* the sort is performed based on the value of the contains property
* of each element. Since this is an `array.sort` the array is sorted
* in place.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
function cleanupElementsList() {
elements.sort(function(a, b) {
var x = a.getAttribute('contains');
var y = b.getAttribute('contains');
return x.localeCompare(y);
});
}
/**
* Wraps all dom elements contained in the elements var with a single
* div.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
function wrapElements() {
var topmenu = document.createElement('div');
topmenu.setAttribute('id', 'ace_settingsmenu');
elements.forEach(function(element) {
topmenu.appendChild(element);
});
var el = topmenu.appendChild(document.createElement('div'));
var version = "1.1.8";
el.style.padding = "1em";
el.textContent = "Ace version " + version;
return topmenu;
}
/**
* Creates a new menu entry.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {object} obj This is a reference to the object containing the
* set function. It is used to set up event listeners for when the
* menu options change.
* @param {string} clss Maps to the class of the dom element. This is
* the name of the object containing the set function e.g. `editor`,
* `session`, `renderer`.
* @param {string} item This is the set function name. It maps to the
* id of the dom element (check, select, input) and to the "contains"
* attribute of the div holding both the element and its label.
* @param {mixed} val This is the value of the setting. It is mapped to
* the dom element's value, checked, or selected option accordingly.
*/
function createNewEntry(obj, clss, item, val) {
var el;
var div = document.createElement('div');
div.setAttribute('contains', item);
div.setAttribute('class', 'ace_optionsMenuEntry');
div.setAttribute('style', 'clear: both;');
div.appendChild(egen.createLabel(
item.replace(/^set/, '').replace(/([A-Z])/g, ' $1').trim(),
item
));
if (Array.isArray(val)) {
el = egen.createSelection(item, val, clss);
el.addEventListener('change', function(e) {
try{
editor.menuOptions[e.target.id].forEach(function(x) {
if(x.textContent !== e.target.textContent) {
delete x.selected;
}
});
obj[e.target.id](e.target.value);
} catch (err) {
throw new Error(err);
}
});
} else if(typeof val === 'boolean') {
el = egen.createCheckbox(item, val, clss);
el.addEventListener('change', function(e) {
try{
// renderer['setHighlightGutterLine'](true);
obj[e.target.id](!!e.target.checked);
} catch (err) {
throw new Error(err);
}
});
} else {
// this aids in giving the ability to specify settings through
// post and get requests.
// /ace_editor.html?setMode=ace/mode/html&setOverwrite=true
el = egen.createInput(item, val, clss);
el.addEventListener('change', function(e) {
try{
if(e.target.value === 'true') {
obj[e.target.id](true);
} else if(e.target.value === 'false') {
obj[e.target.id](false);
} else {
obj[e.target.id](e.target.value);
}
} catch (err) {
throw new Error(err);
}
});
}
el.style.cssText = 'float:right;';
div.appendChild(el);
return div;
}
/**
* Generates selection fields for the menu and populates their options
* using information from `editor.menuOptions`
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {string} item The set function name.
* @param {object} esr A reference to the object having the set function.
* @param {string} clss The name of the object containing the set function.
* @param {string} fn The matching get function's function name.
* @returns {DOMElement} Returns a dom element containing a selection
* element populated with options. The option whose value matches that
* returned from `esr[fn]()` will be selected.
*/
function makeDropdown(item, esr, clss, fn) {
var val = editor.menuOptions[item];
var currentVal = esr[fn]();
if (typeof currentVal == 'object')
currentVal = currentVal.$id;
val.forEach(function(valuex) {
if (valuex.value === currentVal)
valuex.selected = 'selected';
});
return createNewEntry(esr, clss, item, val);
}
/**
* Processes the set functions returned from `getSetFunctions`. First it
* checks for menu options defined in `editor.menuOptions`. If no
* options are specified then it checks whether there is a get function
* (replace set with get) for the setting. When either of those
* conditions are met it will attempt to create a new entry for the
* settings menu and push it into the elements array defined above.
* It can only do so for get functions which return
* strings, numbers, and booleans. A special case is written in for
* `getMode` where it looks at the returned objects `$id` property and
* forwards that through instead. Other special cases could be written
* in but that would get a bit ridiculous.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {object} setObj An item from the array returned by
* `getSetFunctions`.
*/
function handleSet(setObj) {
var item = setObj.functionName;
var esr = setObj.parentObj;
var clss = setObj.parentName;
var val;
var fn = item.replace(/^set/, 'get');
if(editor.menuOptions[item] !== undefined) {
// has options for select element
elements.push(makeDropdown(item, esr, clss, fn));
} else if(typeof esr[fn] === 'function') {
// has get function
try {
val = esr[fn]();
if(typeof val === 'object') {
// setMode takes a string, getMode returns an object
// the $id property of that object is the string
// which may be given to setMode...
val = val.$id;
}
// the rest of the get functions return strings,
// booleans, or numbers.
elements.push(
createNewEntry(esr, clss, item, val)
);
} catch (e) {
// if there are errors it is because the element
// does not belong in the settings menu
}
}
}
addEditorMenuOptions(editor);
// gather the set functions
getSetFunctions(editor).forEach(function(setObj) {
// populate the elements array with good stuff.
handleSet(setObj);
});
// sort the menu entries in the elements list so people can find
// the settings in alphabetical order.
cleanupElementsList();
// dump the entries from the elements list and wrap them up in a div
return wrapElements();
};
});
@@ -1,91 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define, require */
/**
* Get Editor Keyboard Shortcuts
* @fileOverview Get Editor Keyboard Shortcuts <br />
* Gets a map of keyboard shortcuts to command names for the current platform.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
define(function(require, exports, module) {
"use strict";
var keys = require("../../lib/keys");
/**
* Gets a map of keyboard shortcuts to command names for the current platform.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {ace.Editor} editor An editor instance.
* @returns {Array} Returns an array of objects representing the keyboard
* shortcuts for the given editor.
* @example
* var getKbShortcuts = require('./get_keyboard_shortcuts');
* console.log(getKbShortcuts(editor));
* // [
* // {'command' : aCommand, 'key' : 'Control-d'},
* // {'command' : aCommand, 'key' : 'Control-d'}
* // ]
*/
module.exports.getEditorKeybordShortcuts = function(editor) {
var KEY_MODS = keys.KEY_MODS;
var keybindings = [];
var commandMap = {};
editor.keyBinding.$handlers.forEach(function(handler) {
var ckb = handler.commandKeyBinding;
for (var i in ckb) {
var key = i.replace(/(^|-)\w/g, function(x) { return x.toUpperCase(); });
var commands = ckb[i];
if (!Array.isArray(commands))
commands = [commands];
commands.forEach(function(command) {
if (typeof command != "string")
command = command.name
if (commandMap[command]) {
commandMap[command].key += "|" + key;
} else {
commandMap[command] = {key: key, command: command};
keybindings.push(commandMap[command]);
}
});
}
});
return keybindings;
};
});
@@ -1,141 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true */
/*global define*/
/**
* Get Set Functions
* @fileOverview Get Set Functions <br />
* Gets various functions for setting settings.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
define(function(require, exports, module) {
'use strict';
/**
* Generates a list of set functions for the settings menu.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {object} editor The editor instance
* @return {array} Returns an array of objects. Each object contains the
* following properties: functionName, parentObj, and parentName. The
* function name will be the name of a method beginning with the string
* `set` which was found. The parent object will be a reference to the
* object having the method matching the function name. The parent name
* will be a string representing the identifier of the parent object e.g.
* `editor`, `session`, or `renderer`.
*/
module.exports.getSetFunctions = function getSetFunctions (editor) {
/**
* Output array. Will hold the objects described above.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
var out = [];
/**
* This object provides a map between the objects which will be
* traversed and the parent name which will appear in the output.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
var my = {
'editor' : editor,
'session' : editor.session,
'renderer' : editor.renderer
};
/**
* This array will hold the set function names which have already been
* found so that they are not added to the output multiple times.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
var opts = [];
/**
* This is a list of set functions which will not appear in the settings
* menu. I don't know what to do with setKeyboardHandler. When I tried
* to use it, it didn't appear to be working. Someone who knows better
* could remove it from this list and add it's options to
* add_editor_menu_options.js
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
var skip = [
'setOption',
'setUndoManager',
'setDocument',
'setValue',
'setBreakpoints',
'setScrollTop',
'setScrollLeft',
'setSelectionStyle',
'setWrapLimitRange'
];
/**
* This will search the objects mapped to the `my` variable above. When
* it finds a set function in the object that is not listed in the
* `skip` list or the `opts` list it will push a new object to the
* output array.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
['renderer', 'session', 'editor'].forEach(function(esra) {
var esr = my[esra];
var clss = esra;
for(var fn in esr) {
if(skip.indexOf(fn) === -1) {
if(/^set/.test(fn) && opts.indexOf(fn) === -1) {
// found set function
opts.push(fn);
out.push({
'functionName' : fn,
'parentObj' : esr,
'parentName' : clss
});
}
}
}
});
return out;
};
});
@@ -1,116 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define, require */
/**
* Overlay Page
* @fileOverview Overlay Page <br />
* Generates an overlay for displaying menus. The overlay is an absolutely
* positioned div.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
*/
define(function(require, exports, module) {
'use strict';
var dom = require("../../lib/dom");
var cssText = require("../../requirejs/text!./settings_menu.css");
dom.importCssString(cssText);
/**
* Generates an overlay for displaying menus. The overlay is an absolutely
* positioned div.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* ☭ Hial Atropa!! ☭
* @param {DOMElement} contentElement Any element which may be presented inside
* a div.
* @param {string|number} top absolute position value.
* @param {string|number} right absolute position value.
* @param {string|number} bottom absolute position value.
* @param {string|number} left absolute position value.
*/
module.exports.overlayPage = function overlayPage(editor, contentElement, top, right, bottom, left) {
top = top ? 'top: ' + top + ';' : '';
bottom = bottom ? 'bottom: ' + bottom + ';' : '';
right = right ? 'right: ' + right + ';' : '';
left = left ? 'left: ' + left + ';' : '';
var closer = document.createElement('div');
var contentContainer = document.createElement('div');
function documentEscListener(e) {
if (e.keyCode === 27) {
closer.click();
}
}
closer.style.cssText = 'margin: 0; padding: 0; ' +
'position: fixed; top:0; bottom:0; left:0; right:0;' +
'z-index: 9990; ' +
'background-color: rgba(0, 0, 0, 0.3);';
closer.addEventListener('click', function() {
document.removeEventListener('keydown', documentEscListener);
closer.parentNode.removeChild(closer);
editor.focus();
closer = null;
});
// click closer if esc key is pressed
document.addEventListener('keydown', documentEscListener);
contentContainer.style.cssText = top + right + bottom + left;
contentContainer.addEventListener('click', function(e) {
e.stopPropagation();
});
var wrapper = dom.createElement("div");
wrapper.style.position = "relative";
var closeButton = dom.createElement("div");
closeButton.className = "ace_closeButton";
closeButton.addEventListener('click', function() {
closer.click();
});
wrapper.appendChild(closeButton);
contentContainer.appendChild(wrapper);
contentContainer.appendChild(contentElement);
closer.appendChild(contentContainer);
document.body.appendChild(closer);
editor.blur();
};
});
@@ -1,48 +0,0 @@
#ace_settingsmenu, #kbshortcutmenu {
background-color: #F7F7F7;
color: black;
box-shadow: -5px 4px 5px rgba(126, 126, 126, 0.55);
padding: 1em 0.5em 2em 1em;
overflow: auto;
position: absolute;
margin: 0;
bottom: 0;
right: 0;
top: 0;
z-index: 9991;
cursor: default;
}
.ace_dark #ace_settingsmenu, .ace_dark #kbshortcutmenu {
box-shadow: -20px 10px 25px rgba(126, 126, 126, 0.25);
background-color: rgba(255, 255, 255, 0.6);
color: black;
}
.ace_optionsMenuEntry:hover {
background-color: rgba(100, 100, 100, 0.1);
-webkit-transition: all 0.5s;
transition: all 0.3s
}
.ace_closeButton {
background: rgba(245, 146, 146, 0.5);
border: 1px solid #F48A8A;
border-radius: 50%;
padding: 7px;
position: absolute;
right: -8px;
top: -8px;
z-index: 1000;
}
.ace_closeButton{
background: rgba(245, 146, 146, 0.9);
}
.ace_optionsMenuKey {
color: darkslateblue;
font-weight: bold;
}
.ace_optionsMenuCommand {
color: darkcyan;
font-weight: normal;
}
@@ -1,195 +0,0 @@
define(function(require, exports, module) {
"use strict";
var modes = [];
/**
* Suggests a mode based on the file extension present in the given path
* @param {string} path The path to the file
* @returns {object} Returns an object containing information about the
* suggested mode.
*/
function getModeForPath(path) {
var mode = modesByName.text;
var fileName = path.split(/[\/\\]/).pop();
for (var i = 0; i < modes.length; i++) {
if (modes[i].supportsFile(fileName)) {
mode = modes[i];
break;
}
}
return mode;
}
var Mode = function(name, caption, extensions) {
this.name = name;
this.caption = caption;
this.mode = "ace/mode/" + name;
this.extensions = extensions;
if (/\^/.test(extensions)) {
var re = extensions.replace(/\|(\^)?/g, function(a, b){
return "$|" + (b ? "^" : "^.*\\.");
}) + "$";
} else {
var re = "^.*\\.(" + extensions + ")$";
}
this.extRe = new RegExp(re, "gi");
};
Mode.prototype.supportsFile = function(filename) {
return filename.match(this.extRe);
};
// todo firstlinematch
var supportedModes = {
ABAP: ["abap"],
ABC: ["abc"],
ActionScript:["as"],
ADA: ["ada|adb"],
Apache_Conf: ["^htaccess|^htgroups|^htpasswd|^conf|htaccess|htgroups|htpasswd"],
AsciiDoc: ["asciidoc"],
Assembly_x86:["asm"],
AutoHotKey: ["ahk"],
BatchFile: ["bat|cmd"],
C9Search: ["c9search_results"],
C_Cpp: ["cpp|c|cc|cxx|h|hh|hpp"],
Cirru: ["cirru|cr"],
Clojure: ["clj|cljs"],
Cobol: ["CBL|COB"],
coffee: ["coffee|cf|cson|^Cakefile"],
ColdFusion: ["cfm"],
CSharp: ["cs"],
CSS: ["css"],
Curly: ["curly"],
D: ["d|di"],
Dart: ["dart"],
Diff: ["diff|patch"],
Dockerfile: ["^Dockerfile"],
Dot: ["dot"],
Dummy: ["dummy"],
DummySyntax: ["dummy"],
Eiffel: ["e"],
EJS: ["ejs"],
Elixir: ["ex|exs"],
Elm: ["elm"],
Erlang: ["erl|hrl"],
Forth: ["frt|fs|ldr"],
FTL: ["ftl"],
Gcode: ["gcode"],
Gherkin: ["feature"],
Gitignore: ["^.gitignore"],
Glsl: ["glsl|frag|vert"],
golang: ["go"],
Groovy: ["groovy"],
HAML: ["haml"],
Handlebars: ["hbs|handlebars|tpl|mustache"],
Haskell: ["hs"],
haXe: ["hx"],
HTML: ["html|htm|xhtml"],
HTML_Ruby: ["erb|rhtml|html.erb"],
INI: ["ini|conf|cfg|prefs"],
Io: ["io"],
Jack: ["jack"],
Jade: ["jade"],
Java: ["java"],
JavaScript: ["js|jsm"],
JSON: ["json"],
JSONiq: ["jq"],
JSP: ["jsp"],
JSX: ["jsx"],
Julia: ["jl"],
LaTeX: ["tex|latex|ltx|bib"],
LESS: ["less"],
Liquid: ["liquid"],
Lisp: ["lisp"],
LiveScript: ["ls"],
LogiQL: ["logic|lql"],
LSL: ["lsl"],
Lua: ["lua"],
LuaPage: ["lp"],
Lucene: ["lucene"],
Makefile: ["^Makefile|^GNUmakefile|^makefile|^OCamlMakefile|make"],
Markdown: ["md|markdown"],
Mask: ["mask"],
MATLAB: ["matlab"],
MEL: ["mel"],
MUSHCode: ["mc|mush"],
MySQL: ["mysql"],
Nix: ["nix"],
ObjectiveC: ["m|mm"],
OCaml: ["ml|mli"],
Pascal: ["pas|p"],
Perl: ["pl|pm"],
pgSQL: ["pgsql"],
PHP: ["php|phtml"],
Powershell: ["ps1"],
Praat: ["praat|praatscript|psc|proc"],
Prolog: ["plg|prolog"],
Properties: ["properties"],
Protobuf: ["proto"],
Python: ["py"],
R: ["r"],
RDoc: ["Rd"],
RHTML: ["Rhtml"],
Ruby: ["rb|ru|gemspec|rake|^Guardfile|^Rakefile|^Gemfile"],
Rust: ["rs"],
SASS: ["sass"],
SCAD: ["scad"],
Scala: ["scala"],
Scheme: ["scm|rkt"],
SCSS: ["scss"],
SH: ["sh|bash|^.bashrc"],
SJS: ["sjs"],
Smarty: ["smarty|tpl"],
snippets: ["snippets"],
Soy_Template:["soy"],
Space: ["space"],
SQL: ["sql"],
Stylus: ["styl|stylus"],
SVG: ["svg"],
Tcl: ["tcl"],
Tex: ["tex"],
Text: ["txt"],
Textile: ["textile"],
Toml: ["toml"],
Twig: ["twig"],
Typescript: ["ts|typescript|str"],
Vala: ["vala"],
VBScript: ["vbs|vb"],
Velocity: ["vm"],
Verilog: ["v|vh|sv|svh"],
VHDL: ["vhd|vhdl"],
XML: ["xml|rdf|rss|wsdl|xslt|atom|mathml|mml|xul|xbl"],
XQuery: ["xq"],
YAML: ["yaml|yml"],
// Add the missing mode "Django" to ext-modelist
Django: ["html"]
};
var nameOverrides = {
ObjectiveC: "Objective-C",
CSharp: "C#",
golang: "Go",
C_Cpp: "C and C++",
coffee: "CoffeeScript",
HTML_Ruby: "HTML (Ruby)",
FTL: "FreeMarker"
};
var modesByName = {};
for (var name in supportedModes) {
var data = supportedModes[name];
var displayName = (nameOverrides[name] || name).replace(/_/g, " ");
var filename = name.toLowerCase();
var mode = new Mode(filename, displayName, data[0]);
modesByName[filename] = mode;
modes.push(mode);
}
module.exports = {
getModeForPath: getModeForPath,
modes: modes,
modesByName: modesByName
};
});
@@ -1,114 +0,0 @@
/* ***** 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 MAX_TOKEN_COUNT = 1000;
var useragent = require("../lib/useragent");
var TokenizerModule = require("../tokenizer");
function patch(obj, name, regexp, replacement) {
eval("obj['" + name + "']=" + obj[name].toString().replace(
regexp, replacement
));
}
if (useragent.isIE && useragent.isIE < 10 && window.top.document.compatMode === "BackCompat")
useragent.isOldIE = true;
if (typeof document != "undefined" && !document.documentElement.querySelector) {
useragent.isOldIE = true;
var qs = function(el, selector) {
if (selector.charAt(0) == ".") {
var classNeme = selector.slice(1);
} else {
var m = selector.match(/(\w+)=(\w+)/);
var attr = m && m[1];
var attrVal = m && m[2];
}
for (var i = 0; i < el.all.length; i++) {
var ch = el.all[i];
if (classNeme) {
if (ch.className.indexOf(classNeme) != -1)
return ch;
} else if (attr) {
if (ch.getAttribute(attr) == attrVal)
return ch;
}
}
};
var sb = require("./searchbox").SearchBox.prototype;
patch(
sb, "$initElements",
/([^\s=]*).querySelector\((".*?")\)/g,
"qs($1, $2)"
);
}
var compliantExecNpcg = /()??/.exec("")[1] === undefined;
if (compliantExecNpcg)
return;
var proto = TokenizerModule.Tokenizer.prototype;
TokenizerModule.Tokenizer_orig = TokenizerModule.Tokenizer;
proto.getLineTokens_orig = proto.getLineTokens;
patch(
TokenizerModule, "Tokenizer",
"ruleRegExps.push(adjustedregex);\n",
function(m) {
return m + '\
if (state[i].next && RegExp(adjustedregex).test(""))\n\
rule._qre = RegExp(adjustedregex, "g");\n\
';
}
);
TokenizerModule.Tokenizer.prototype = proto;
patch(
proto, "getLineTokens",
/if \(match\[i \+ 1\] === undefined\)\s*continue;/,
"if (!match[i + 1]) {\n\
if (value)continue;\n\
var qre = state[mapping[i]]._qre;\n\
if (!qre) continue;\n\
qre.lastIndex = lastIndex;\n\
if (!qre.exec(line) || qre.lastIndex != lastIndex)\n\
continue;\n\
}"
);
patch(
require("../mode/text").Mode.prototype, "getTokenizer",
/Tokenizer/,
"TokenizerModule.Tokenizer"
);
useragent.isOldIE = true;
});
@@ -1,77 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var assert = require("../test/assertions");
module.exports = {
"test: getTokenizer() (smoke test)" : function() {
var exec = RegExp.prototype.exec
var brokenExec = function(str) {
var result = exec.call(this, str);
if (result) {
for (var i = result.length; i--;)
if (!result[i])
result[i] = "";
}
return result;
}
try {
// break this to emulate old ie
RegExp.prototype.exec = brokenExec;
require("./old_ie");
var Tokenizer = require("../tokenizer").Tokenizer;
var JavaScriptHighlightRules = require("../mode/javascript_highlight_rules").JavaScriptHighlightRules;
var tokenizer = new Tokenizer((new JavaScriptHighlightRules).getRules());
var tokens = tokenizer.getLineTokens("'juhu'", "start").tokens;
assert.equal("string", tokens[0].type);
} finally {
// restore modified functions
RegExp.prototype.exec = exec;
var module = require("../tokenizer");
module.Tokenizer = module.Tokenizer_orig;
module.Tokenizer.prototype.getLineTokens = module.Tokenizer.prototype.getLineTokens_orig;
}
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,153 +0,0 @@
/* ------------------------------------------------------------------------------------------
* Editor Search Form
* --------------------------------------------------------------------------------------- */
.ace_search {
background-color: #ddd;
border: 1px solid #cbcbcb;
border-top: 0 none;
max-width: 325px;
overflow: hidden;
margin: 0;
padding: 4px;
padding-right: 6px;
padding-bottom: 0;
position: absolute;
top: 0px;
z-index: 99;
white-space: normal;
}
.ace_search.left {
border-left: 0 none;
border-radius: 0px 0px 5px 0px;
left: 0;
}
.ace_search.right {
border-radius: 0px 0px 0px 5px;
border-right: 0 none;
right: 0;
}
.ace_search_form, .ace_replace_form {
border-radius: 3px;
border: 1px solid #cbcbcb;
float: left;
margin-bottom: 4px;
overflow: hidden;
}
.ace_search_form.ace_nomatch {
outline: 1px solid red;
}
.ace_search_field {
background-color: white;
border-right: 1px solid #cbcbcb;
border: 0 none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
float: left;
height: 22px;
outline: 0;
padding: 0 7px;
width: 214px;
margin: 0;
}
.ace_searchbtn,
.ace_replacebtn {
background: #fff;
border: 0 none;
border-left: 1px solid #dcdcdc;
cursor: pointer;
float: left;
height: 22px;
margin: 0;
padding: 0;
position: relative;
}
.ace_searchbtn:last-child,
.ace_replacebtn:last-child {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
.ace_searchbtn:disabled {
background: none;
cursor: default;
}
.ace_searchbtn {
background-position: 50% 50%;
background-repeat: no-repeat;
width: 27px;
}
.ace_searchbtn.prev {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADFJREFUeNpiSU1NZUAC/6E0I0yACYskCpsJiySKIiY0SUZk40FyTEgCjGgKwTRAgAEAQJUIPCE+qfkAAAAASUVORK5CYII=);
}
.ace_searchbtn.next {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNpiTE1NZQCC/0DMyIAKwGJMUAYDEo3M/s+EpvM/mkKwCQxYjIeLMaELoLMBAgwAU7UJObTKsvAAAAAASUVORK5CYII=);
}
.ace_searchbtn_close {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAcCAYAAABRVo5BAAAAZ0lEQVR42u2SUQrAMAhDvazn8OjZBilCkYVVxiis8H4CT0VrAJb4WHT3C5xU2a2IQZXJjiQIRMdkEoJ5Q2yMqpfDIo+XY4k6h+YXOyKqTIj5REaxloNAd0xiKmAtsTHqW8sR2W5f7gCu5nWFUpVjZwAAAABJRU5ErkJggg==) no-repeat 50% 0;
border-radius: 50%;
border: 0 none;
color: #656565;
cursor: pointer;
float: right;
font: 16px/16px Arial;
height: 14px;
margin: 5px 1px 9px 5px;
padding: 0;
text-align: center;
width: 14px;
}
.ace_searchbtn_close:hover {
background-color: #656565;
background-position: 50% 100%;
color: white;
}
.ace_replacebtn.prev {
width: 54px
}
.ace_replacebtn.next {
width: 27px
}
.ace_button {
margin-left: 2px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
overflow: hidden;
opacity: 0.7;
border: 1px solid rgba(100,100,100,0.23);
padding: 1px;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: black;
}
.ace_button:hover {
background-color: #eee;
opacity:1;
}
.ace_button:active {
background-color: #ddd;
}
.ace_button.checked {
border-color: #3399ff;
opacity:1;
}
.ace_search_options{
margin-bottom: 3px;
text-align: right;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
}
@@ -1,308 +0,0 @@
/* ***** 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 dom = require("../lib/dom");
var lang = require("../lib/lang");
var event = require("../lib/event");
var searchboxCss = require("../requirejs/text!./searchbox.css");
var HashHandler = require("../keyboard/hash_handler").HashHandler;
var keyUtil = require("../lib/keys");
dom.importCssString(searchboxCss, "ace_searchbox");
var html = '<div class="ace_search right">\
<button type="button" action="hide" class="ace_searchbtn_close"></button>\
<div class="ace_search_form">\
<input class="ace_search_field" placeholder="Search for" spellcheck="false"></input>\
<button type="button" action="findNext" class="ace_searchbtn next"></button>\
<button type="button" action="findPrev" class="ace_searchbtn prev"></button>\
<button type="button" action="findAll" class="ace_searchbtn" title="Alt-Enter">All</button>\
</div>\
<div class="ace_replace_form">\
<input class="ace_search_field" placeholder="Replace with" spellcheck="false"></input>\
<button type="button" action="replaceAndFindNext" class="ace_replacebtn">Replace</button>\
<button type="button" action="replaceAll" class="ace_replacebtn">All</button>\
</div>\
<div class="ace_search_options">\
<span action="toggleRegexpMode" class="ace_button" title="RegExp Search">.*</span>\
<span action="toggleCaseSensitive" class="ace_button" title="CaseSensitive Search">Aa</span>\
<span action="toggleWholeWords" class="ace_button" title="Whole Word Search">\\b</span>\
</div>\
</div>'.replace(/>\s+/g, ">");
var SearchBox = function(editor, range, showReplaceForm) {
var div = dom.createElement("div");
div.innerHTML = html;
this.element = div.firstChild;
this.$init();
this.setEditor(editor);
};
(function() {
this.setEditor = function(editor) {
editor.searchBox = this;
editor.container.appendChild(this.element);
this.editor = editor;
};
this.$initElements = function(sb) {
this.searchBox = sb.querySelector(".ace_search_form");
this.replaceBox = sb.querySelector(".ace_replace_form");
this.searchOptions = sb.querySelector(".ace_search_options");
this.regExpOption = sb.querySelector("[action=toggleRegexpMode]");
this.caseSensitiveOption = sb.querySelector("[action=toggleCaseSensitive]");
this.wholeWordOption = sb.querySelector("[action=toggleWholeWords]");
this.searchInput = this.searchBox.querySelector(".ace_search_field");
this.replaceInput = this.replaceBox.querySelector(".ace_search_field");
};
this.$init = function() {
var sb = this.element;
this.$initElements(sb);
var _this = this;
event.addListener(sb, "mousedown", function(e) {
setTimeout(function(){
_this.activeInput.focus();
}, 0);
event.stopPropagation(e);
});
event.addListener(sb, "click", function(e) {
var t = e.target || e.srcElement;
var action = t.getAttribute("action");
if (action && _this[action])
_this[action]();
else if (_this.$searchBarKb.commands[action])
_this.$searchBarKb.commands[action].exec(_this);
event.stopPropagation(e);
});
event.addCommandKeyListener(sb, function(e, hashId, keyCode) {
var keyString = keyUtil.keyCodeToString(keyCode);
var command = _this.$searchBarKb.findKeyCommand(hashId, keyString);
if (command && command.exec) {
command.exec(_this);
event.stopEvent(e);
}
});
this.$onChange = lang.delayedCall(function() {
_this.find(false, false);
});
event.addListener(this.searchInput, "input", function() {
_this.$onChange.schedule(20);
});
event.addListener(this.searchInput, "focus", function() {
_this.activeInput = _this.searchInput;
_this.searchInput.value && _this.highlight();
});
event.addListener(this.replaceInput, "focus", function() {
_this.activeInput = _this.replaceInput;
_this.searchInput.value && _this.highlight();
});
};
//keybinging outsite of the searchbox
this.$closeSearchBarKb = new HashHandler([{
bindKey: "Esc",
name: "closeSearchBar",
exec: function(editor) {
editor.searchBox.hide();
}
}]);
//keybinging outsite of the searchbox
this.$searchBarKb = new HashHandler();
this.$searchBarKb.bindKeys({
"Ctrl-f|Command-f|Ctrl-H|Command-Option-F": function(sb) {
var isReplace = sb.isReplace = !sb.isReplace;
sb.replaceBox.style.display = isReplace ? "" : "none";
sb[isReplace ? "replaceInput" : "searchInput"].focus();
},
"Ctrl-G|Command-G": function(sb) {
sb.findNext();
},
"Ctrl-Shift-G|Command-Shift-G": function(sb) {
sb.findPrev();
},
"esc": function(sb) {
setTimeout(function() { sb.hide();});
},
"Return": function(sb) {
if (sb.activeInput == sb.replaceInput)
sb.replace();
sb.findNext();
},
"Shift-Return": function(sb) {
if (sb.activeInput == sb.replaceInput)
sb.replace();
sb.findPrev();
},
"Alt-Return": function(sb) {
if (sb.activeInput == sb.replaceInput)
sb.replaceAll();
sb.findAll();
},
"Tab": function(sb) {
(sb.activeInput == sb.replaceInput ? sb.searchInput : sb.replaceInput).focus();
}
});
this.$searchBarKb.addCommands([{
name: "toggleRegexpMode",
bindKey: {win: "Alt-R|Alt-/", mac: "Ctrl-Alt-R|Ctrl-Alt-/"},
exec: function(sb) {
sb.regExpOption.checked = !sb.regExpOption.checked;
sb.$syncOptions();
}
}, {
name: "toggleCaseSensitive",
bindKey: {win: "Alt-C|Alt-I", mac: "Ctrl-Alt-R|Ctrl-Alt-I"},
exec: function(sb) {
sb.caseSensitiveOption.checked = !sb.caseSensitiveOption.checked;
sb.$syncOptions();
}
}, {
name: "toggleWholeWords",
bindKey: {win: "Alt-B|Alt-W", mac: "Ctrl-Alt-B|Ctrl-Alt-W"},
exec: function(sb) {
sb.wholeWordOption.checked = !sb.wholeWordOption.checked;
sb.$syncOptions();
}
}]);
this.$syncOptions = function() {
dom.setCssClass(this.regExpOption, "checked", this.regExpOption.checked);
dom.setCssClass(this.wholeWordOption, "checked", this.wholeWordOption.checked);
dom.setCssClass(this.caseSensitiveOption, "checked", this.caseSensitiveOption.checked);
this.find(false, false);
};
this.highlight = function(re) {
this.editor.session.highlight(re || this.editor.$search.$options.re);
this.editor.renderer.updateBackMarkers()
};
this.find = function(skipCurrent, backwards) {
var range = this.editor.find(this.searchInput.value, {
skipCurrent: skipCurrent,
backwards: backwards,
wrap: true,
regExp: this.regExpOption.checked,
caseSensitive: this.caseSensitiveOption.checked,
wholeWord: this.wholeWordOption.checked
});
var noMatch = !range && this.searchInput.value;
dom.setCssClass(this.searchBox, "ace_nomatch", noMatch);
this.editor._emit("findSearchBox", { match: !noMatch });
this.highlight();
};
this.findNext = function() {
this.find(true, false);
};
this.findPrev = function() {
this.find(true, true);
};
this.findAll = function(){
var range = this.editor.findAll(this.searchInput.value, {
regExp: this.regExpOption.checked,
caseSensitive: this.caseSensitiveOption.checked,
wholeWord: this.wholeWordOption.checked
});
var noMatch = !range && this.searchInput.value;
dom.setCssClass(this.searchBox, "ace_nomatch", noMatch);
this.editor._emit("findSearchBox", { match: !noMatch });
this.highlight();
this.hide();
};
this.replace = function() {
if (!this.editor.getReadOnly())
this.editor.replace(this.replaceInput.value);
};
this.replaceAndFindNext = function() {
if (!this.editor.getReadOnly()) {
this.editor.replace(this.replaceInput.value);
this.findNext()
}
};
this.replaceAll = function() {
if (!this.editor.getReadOnly())
this.editor.replaceAll(this.replaceInput.value);
};
this.hide = function() {
this.element.style.display = "none";
this.editor.keyBinding.removeKeyboardHandler(this.$closeSearchBarKb);
this.editor.focus();
};
this.show = function(value, isReplace) {
this.element.style.display = "";
this.replaceBox.style.display = isReplace ? "" : "none";
this.isReplace = isReplace;
if (value)
this.searchInput.value = value;
this.searchInput.focus();
this.searchInput.select();
this.editor.keyBinding.addKeyboardHandler(this.$closeSearchBarKb);
};
this.isFocused = function() {
var el = document.activeElement;
return el == this.searchInput || el == this.replaceInput;
}
}).call(SearchBox.prototype);
exports.SearchBox = SearchBox;
exports.Search = function(editor, isReplace) {
var sb = editor.searchBox || new SearchBox(editor);
sb.show(editor.session.getTextRange(), isReplace);
};
});
/* ------------------------------------------------------------------------------------------
* TODO
* --------------------------------------------------------------------------------------- */
/*
- move search form to the left if it masks current word
- includ all options that search has. ex: regex
- searchbox.searchbox is not that pretty. we should have just searchbox
- disable prev button if it makes sence
*/
@@ -1,76 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define, require */
/**
* Show Settings Menu
* @fileOverview Show Settings Menu <br />
* Displays an interactive settings menu mostly generated on the fly based on
* the current state of the editor.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* Hial Atropa!!
*/
define(function(require, exports, module) {
"use strict";
var generateSettingsMenu = require('./menu_tools/generate_settings_menu').generateSettingsMenu;
var overlayPage = require('./menu_tools/overlay_page').overlayPage;
/**
* This displays the settings menu if it is not already being shown.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* Hial Atropa!!
* @param {ace.Editor} editor An instance of the ace editor.
*/
function showSettingsMenu(editor) {
// make sure the menu isn't open already.
var sm = document.getElementById('ace_settingsmenu');
if (!sm)
overlayPage(editor, generateSettingsMenu(editor), '0', '0', '0');
}
/**
* Initializes the settings menu extension. It adds the showSettingsMenu
* method to the given editor object and adds the showSettingsMenu command
* to the editor with appropriate keyboard shortcuts.
* @param {ace.Editor} editor An instance of the Editor.
*/
module.exports.init = function(editor) {
var Editor = require("ace/editor").Editor;
Editor.prototype.showSettingsMenu = function() {
showSettingsMenu(this);
};
};
});
@@ -1,69 +0,0 @@
define(function(require, exports, module) {
"use strict";
var event = require("../lib/event");
exports.contextMenuHandler = function(e){
var host = e.target;
var text = host.textInput.getElement();
if (!host.selection.isEmpty())
return;
var c = host.getCursorPosition();
var r = host.session.getWordRange(c.row, c.column);
var w = host.session.getTextRange(r);
host.session.tokenRe.lastIndex = 0;
if (!host.session.tokenRe.test(w))
return;
var PLACEHOLDER = "\x01\x01";
var value = w + " " + PLACEHOLDER;
text.value = value;
text.setSelectionRange(w.length, w.length + 1);
text.setSelectionRange(0, 0);
text.setSelectionRange(0, w.length);
var afterKeydown = false;
event.addListener(text, "keydown", function onKeydown() {
event.removeListener(text, "keydown", onKeydown);
afterKeydown = true;
});
host.textInput.setInputHandler(function(newVal) {
console.log(newVal , value, text.selectionStart, text.selectionEnd)
if (newVal == value)
return '';
if (newVal.lastIndexOf(value, 0) === 0)
return newVal.slice(value.length);
if (newVal.substr(text.selectionEnd) == value)
return newVal.slice(0, -value.length);
if (newVal.slice(-2) == PLACEHOLDER) {
var val = newVal.slice(0, -2);
if (val.slice(-1) == " ") {
if (afterKeydown)
return val.substring(0, text.selectionEnd);
val = val.slice(0, -1);
host.session.replace(r, val);
return "";
}
}
return newVal;
});
};
// todo support highlighting with typo.js
var Editor = require("../editor").Editor;
require("../config").defineOptions(Editor.prototype, "editor", {
spellcheck: {
set: function(val) {
var text = this.textInput.getElement();
text.spellcheck = !!val;
if (!val)
this.removeListener("nativecontextmenu", exports.contextMenuHandler);
else
this.on("nativecontextmenu", exports.contextMenuHandler);
},
value: true
}
});
});
@@ -1,40 +0,0 @@
/* ***** 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";
/**
* this is experimental, and subject to change, use at your own risk!
*/
module.exports = require("../split");
});
@@ -1,31 +0,0 @@
.ace_static_highlight {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Droid Sans Mono', monospace;
font-size: 12px;
}
.ace_static_highlight .ace_gutter {
width: 25px !important;
float: left;
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_gutter-cell {
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.ace_static_highlight .ace_gutter-cell:before {
content: counter(ace_line, decimal);
counter-increment: ace_line;
}
.ace_static_highlight {
counter-reset: ace_line;
}
@@ -1,190 +0,0 @@
/* ***** 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 EditSession = require("../edit_session").EditSession;
var TextLayer = require("../layer/text").Text;
var baseStyles = require("../requirejs/text!./static.css");
var config = require("../config");
var dom = require("../lib/dom");
var highlight = function(el, opts, callback) {
var m = el.className.match(/lang-(\w+)/);
var mode = opts.mode || m && ("ace/mode/" + m[1]);
if (!mode)
return false;
var theme = opts.theme || "ace/theme/textmate";
var data = "";
var nodes = [];
if (el.firstElementChild) {
var textLen = 0;
for (var i = 0; i < el.childNodes.length; i++) {
var ch = el.childNodes[i];
if (ch.nodeType == 3) {
textLen += ch.data.length;
data += ch.data;
} else {
nodes.push(textLen, ch);
}
}
} else {
data = dom.getInnerText(el);
if (opts.trim)
data = data.trim();
}
highlight.render(data, mode, theme, opts.firstLineNumber, !opts.showGutter, function (highlighted) {
dom.importCssString(highlighted.css, "ace_highlight");
el.innerHTML = highlighted.html;
var container = el.firstChild.firstChild;
for (var i = 0; i < nodes.length; i += 2) {
var pos = highlighted.session.doc.indexToPosition(nodes[i]);
var node = nodes[i + 1];
var lineEl = container.children[pos.row];
lineEl && lineEl.appendChild(node);
}
callback && callback();
});
};
/**
* Transforms a given input code snippet into HTML using the given mode
*
* @param {string} input Code snippet
* @param {string|mode} mode String specifying the mode to load such as
* `ace/mode/javascript` or, a mode loaded from `/ace/mode`
* (use 'ServerSideHiglighter.getMode').
* @param {string|theme} theme String specifying the theme to load such as
* `ace/theme/twilight` or, a theme loaded from `/ace/theme`.
* @param {number} lineStart A number indicating the first line number. Defaults
* to 1.
* @param {boolean} disableGutter Specifies whether or not to disable the gutter.
* `true` disables the gutter, `false` enables the gutter. Defaults to `false`.
* @param {function} callback When specifying the mode or theme as a string,
* this method has no return value and you must specify a callback function. The
* callback will receive the rendered object containing the properties `html`
* and `css`.
* @returns {object} An object containing the properties `html` and `css`.
*/
highlight.render = function(input, mode, theme, lineStart, disableGutter, callback) {
var waiting = 1;
var modeCache = EditSession.prototype.$modes;
// if either the theme or the mode were specified as objects
// then we need to lazily load them.
if (typeof theme == "string") {
waiting++;
config.loadModule(['theme', theme], function(m) {
theme = m;
--waiting || done();
});
}
// allow setting mode options e.h {path: "ace/mode/php", inline:true}
var modeOptions;
if (mode && typeof mode === "object" && !mode.getTokenizer) {
modeOptions = mode;
mode = modeOptions.path;
}
if (typeof mode == "string") {
waiting++;
config.loadModule(['mode', mode], function(m) {
if (!modeCache[mode] || modeOptions)
modeCache[mode] = new m.Mode(modeOptions);
mode = modeCache[mode];
--waiting || done();
});
}
// loads or passes the specified mode module then calls renderer
function done() {
var result = highlight.renderSync(input, mode, theme, lineStart, disableGutter);
return callback ? callback(result) : result;
}
return --waiting || done();
};
/**
* Transforms a given input code snippet into HTML using the given mode
* @param {string} input Code snippet
* @param {mode} mode Mode loaded from /ace/mode (use 'ServerSideHiglighter.getMode')
* @param {string} r Code snippet
* @returns {object} An object containing: html, css
*/
highlight.renderSync = function(input, mode, theme, lineStart, disableGutter) {
lineStart = parseInt(lineStart || 1, 10);
var session = new EditSession("");
session.setUseWorker(false);
session.setMode(mode);
var textLayer = new TextLayer(document.createElement("div"));
textLayer.setSession(session);
textLayer.config = {
characterWidth: 10,
lineHeight: 20
};
session.setValue(input);
var stringBuilder = [];
var length = session.getLength();
for(var ix = 0; ix < length; ix++) {
stringBuilder.push("<div class='ace_line'>");
if (!disableGutter)
stringBuilder.push("<span class='ace_gutter ace_gutter-cell' unselectable='on'>" + /*(ix + lineStart) + */ "</span>");
textLayer.$renderLine(stringBuilder, ix, true, false);
stringBuilder.push("\n</div>");
}
// let's prepare the whole html
var html = "<div class='" + theme.cssClass + "'>" +
"<div class='ace_static_highlight' style='counter-reset:ace_line " + (lineStart - 1) + "'>" +
stringBuilder.join("") +
"</div>" +
"</div>";
textLayer.destroy();
return {
css: baseStyles + theme.cssText,
html: html,
session: session
};
};
module.exports = highlight;
module.exports.highlight =highlight;
});
@@ -1,96 +0,0 @@
if (typeof process !== "undefined") {
require("amd-loader");
require("../test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var assert = require("assert");
var highlighter = require("./static_highlight");
var JavaScriptMode = require("../mode/javascript").Mode;
var TextMode = require("../mode/text").Mode;
// Execution ORDER: test.setUpSuite, setUp, testFn, tearDown, test.tearDownSuite
module.exports = {
timeout: 10000,
"test simple snippet": function(next) {
var theme = require("../theme/tomorrow");
var snippet = [
"/** this is a function",
"*",
"*/",
"function hello (a, b, c) {",
" console.log(a * b + c + 'sup$');",
"}"
].join("\n");
var mode = new JavaScriptMode();
var result = highlighter.render(snippet, mode, theme);
assert.equal(result.html, "<div class='ace-tomorrow'><div class='ace_static_highlight' style='counter-reset:ace_line 0'>"
+ "<div class='ace_line'><span class='ace_gutter ace_gutter-cell' unselectable='on'></span><span class='ace_comment ace_doc'>/**\xa0this\xa0is\xa0a\xa0function</span>\n</div>"
+ "<div class='ace_line'><span class='ace_gutter ace_gutter-cell' unselectable='on'></span><span class='ace_comment ace_doc'>*</span>\n</div>"
+ "<div class='ace_line'><span class='ace_gutter ace_gutter-cell' unselectable='on'></span><span class='ace_comment ace_doc'>*/</span>\n</div>"
+ "<div class='ace_line'><span class='ace_gutter ace_gutter-cell' unselectable='on'></span><span class='ace_storage ace_type'>function</span>\xa0<span class='ace_entity ace_name ace_function'>hello</span>\xa0<span class='ace_paren ace_lparen'>(</span><span class='ace_variable ace_parameter'>a</span><span class='ace_punctuation ace_operator'>,\xa0</span><span class='ace_variable ace_parameter'>b</span><span class='ace_punctuation ace_operator'>,\xa0</span><span class='ace_variable ace_parameter'>c</span><span class='ace_paren ace_rparen'>)</span>\xa0<span class='ace_paren ace_lparen'>{</span>\n</div>"
+ "<div class='ace_line'><span class='ace_gutter ace_gutter-cell' unselectable='on'></span>\xa0\xa0\xa0\xa0<span class='ace_storage ace_type'>console</span><span class='ace_punctuation ace_operator'>.</span><span class='ace_support ace_function ace_firebug'>log</span><span class='ace_paren ace_lparen'>(</span><span class='ace_identifier'>a</span>\xa0<span class='ace_keyword ace_operator'>*</span>\xa0<span class='ace_identifier'>b</span>\xa0<span class='ace_keyword ace_operator'>+</span>\xa0<span class='ace_identifier'>c</span>\xa0<span class='ace_keyword ace_operator'>+</span>\xa0<span class='ace_string'>'sup$'</span><span class='ace_paren ace_rparen'>)</span><span class='ace_punctuation ace_operator'>;</span>\n</div>"
+ "<div class='ace_line'><span class='ace_gutter ace_gutter-cell' unselectable='on'></span><span class='ace_paren ace_rparen'>}</span>\n</div>"
+ "</div></div>");
assert.ok(!!result.css);
next();
},
"test css from theme is used": function(next) {
var theme = require("../theme/tomorrow");
var snippet = [
"/** this is a function",
"*",
"*/",
"function hello (a, b, c) {",
" console.log(a * b + c + 'sup?');",
"}"
].join("\n");
var mode = new JavaScriptMode();
var result = highlighter.render(snippet, mode, theme);
assert.ok(result.css.indexOf(theme.cssText) !== -1);
next();
},
"test theme classname should be in output html": function(next) {
var theme = require("../theme/tomorrow");
var snippet = [
"/** this is a function",
"*",
"*/",
"function hello (a, b, c) {",
" console.log(a * b + c + 'sup?');",
"}"
].join("\n");
var mode = new JavaScriptMode();
var result = highlighter.render(snippet, mode, theme);
assert.equal(!!result.html.match(/<div class='ace-tomorrow'>/), true);
next();
},
"test js string replace specials": function(next) {
var theme = require("../theme/tomorrow");
var snippet = "$'$1$2$$$&";
var mode = new TextMode();
var result = highlighter.render(snippet, mode, theme);
assert.ok(result.html.indexOf(snippet) != -1);
next();
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec();
}
@@ -1,48 +0,0 @@
define(function(require, exports, module) {
"use strict";
/** simple statusbar **/
var dom = require("ace/lib/dom");
var lang = require("ace/lib/lang");
var StatusBar = function(editor, parentNode) {
this.element = dom.createElement("div");
this.element.className = "ace_status-indicator";
this.element.style.cssText = "display: inline-block;";
parentNode.appendChild(this.element);
var statusUpdate = lang.delayedCall(function(){
this.updateStatus(editor)
}.bind(this));
editor.on("changeStatus", function() {
statusUpdate.schedule(100);
});
editor.on("changeSelection", function() {
statusUpdate.schedule(100);
});
};
(function(){
this.updateStatus = function(editor) {
var status = [];
function add(str, separator) {
str && status.push(str, separator || "|");
}
add(editor.keyBinding.getStatusText(editor));
if (editor.commands.recording)
add("REC");
var c = editor.selection.lead;
add(c.row + ":" + c.column, " ");
if (!editor.selection.isEmpty()) {
var r = editor.getSelectionRange();
add("(" + (r.end.row - r.start.row) + ":" +(r.end.column - r.start.column) + ")");
}
status.pop();
this.element.textContent = status.join("");
};
}).call(StatusBar.prototype);
exports.StatusBar = StatusBar;
});
@@ -1,521 +0,0 @@
/* ***** 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 event = require("../lib/event");
var UA = require("../lib/useragent");
var net = require("../lib/net");
var ace = require("../ace");
require("../theme/textmate");
module.exports = exports = ace;
/*
* Returns the CSS property of element.
* 1) If the CSS property is on the style object of the element, use it, OR
* 2) Compute the CSS property
*
* If the property can't get computed, is 'auto' or 'intrinsic', the former
* calculated property is used (this can happen in cases where the textarea
* is hidden and has no dimension styles).
*/
var getCSSProperty = function(element, container, property) {
var ret = element.style[property];
if (!ret) {
if (window.getComputedStyle) {
ret = window.getComputedStyle(element, '').getPropertyValue(property);
} else {
ret = element.currentStyle[property];
}
}
if (!ret || ret == 'auto' || ret == 'intrinsic') {
ret = container.style[property];
}
return ret;
};
function applyStyles(elm, styles) {
for (var style in styles) {
elm.style[style] = styles[style];
}
}
function setupContainer(element, getValue) {
if (element.type != 'textarea') {
throw new Error("Textarea required!");
}
var parentNode = element.parentNode;
// This will hold the editor.
var container = document.createElement('div');
// To put Ace in the place of the textarea, we have to copy a few of the
// textarea's style attributes to the div container.
//
// The problem is that the properties have to get computed (they might be
// defined by a CSS file on the page - you can't access such rules that
// apply to an element via elm.style). Computed properties are converted to
// pixels although the dimension might be given as percentage. When the
// window resizes, the dimensions defined by percentages changes, so the
// properties have to get recomputed to get the new/true pixels.
var resizeEvent = function() {
var style = 'position:relative;';
[
'margin-top', 'margin-left', 'margin-right', 'margin-bottom'
].forEach(function(item) {
style += item + ':' +
getCSSProperty(element, container, item) + ';';
});
// Calculating the width/height of the textarea is somewhat tricky. To
// do it right, you have to include the paddings to the sides as well
// (eg. width = width + padding-left, -right). This works well, as
// long as the width of the element is not set or given in pixels. In
// this case and after the textarea is hidden, getCSSProperty(element,
// container, 'width') will still return pixel value. If the element
// has realtiv dimensions (e.g. width='95<percent>')
// getCSSProperty(...) will return pixel values only as long as the
// textarea is visible. After it is hidden getCSSProperty will return
// the relative dimensions as they are set on the element (in the case
// of width, 95<percent>).
// Making the sum of pixel vaules (e.g. padding) and realtive values
// (e.g. <percent>) is not possible. As such the padding styles are
// ignored.
// The complete width is the width of the textarea + the padding
// to the left and right.
var width = getCSSProperty(element, container, 'width') || (element.clientWidth + "px");
var height = getCSSProperty(element, container, 'height') || (element.clientHeight + "px");
style += 'height:' + height + ';width:' + width + ';';
// Set the display property to 'inline-block'.
style += 'display:inline-block;';
container.setAttribute('style', style);
};
event.addListener(window, 'resize', resizeEvent);
// Call the resizeEvent once, so that the size of the container is
// calculated.
resizeEvent();
// Insert the div container after the element.
parentNode.insertBefore(container, element.nextSibling);
// Override the forms onsubmit function. Set the innerHTML and value
// of the textarea before submitting.
while (parentNode !== document) {
if (parentNode.tagName.toUpperCase() === 'FORM') {
var oldSumit = parentNode.onsubmit;
// Override the onsubmit function of the form.
parentNode.onsubmit = function(evt) {
element.value = getValue();
// If there is a onsubmit function already, then call
// it with the current context and pass the event.
if (oldSumit) {
oldSumit.call(this, evt);
}
};
break;
}
parentNode = parentNode.parentNode;
}
return container;
}
exports.transformTextarea = function(element, options) {
var session;
var container = setupContainer(element, function() {
return session.getValue();
});
// Hide the element.
element.style.display = 'none';
container.style.background = 'white';
//
var editorDiv = document.createElement("div");
applyStyles(editorDiv, {
top: "0px",
left: "0px",
right: "0px",
bottom: "0px",
border: "1px solid gray",
position: "absolute"
});
container.appendChild(editorDiv);
var settingOpener = document.createElement("div");
applyStyles(settingOpener, {
position: "absolute",
right: "0px",
bottom: "0px",
background: "red",
cursor: "nw-resize",
borderStyle: "solid",
borderWidth: "9px 8px 10px 9px",
width: "2px",
borderColor: "lightblue gray gray lightblue",
zIndex: 101
});
var settingDiv = document.createElement("div");
var settingDivStyles = {
top: "0px",
left: "20%",
right: "0px",
bottom: "0px",
position: "absolute",
padding: "5px",
zIndex: 100,
color: "white",
display: "none",
overflow: "auto",
fontSize: "14px",
boxShadow: "-5px 2px 3px gray"
};
if (!UA.isOldIE) {
settingDivStyles.backgroundColor = "rgba(0, 0, 0, 0.6)";
} else {
settingDivStyles.backgroundColor = "#333";
}
applyStyles(settingDiv, settingDivStyles);
container.appendChild(settingDiv);
options = options || exports.defaultOptions;
// Power up ace on the textarea:
var editor = ace.edit(editorDiv);
session = editor.getSession();
session.setValue(element.value || element.innerHTML);
editor.focus();
// Add the settingPanel opener to the editor's div.
container.appendChild(settingOpener);
// Create the API.
setupApi(editor, editorDiv, settingDiv, ace, options, load);
// Create the setting's panel.
setupSettingPanel(settingDiv, settingOpener, editor);
var state = "";
event.addListener(settingOpener, "mousemove", function(e) {
var rect = this.getBoundingClientRect();
var x = e.clientX - rect.left, y = e.clientY - rect.top;
if (x + y < (rect.width + rect.height)/2) {
this.style.cursor = "pointer";
state = "toggle";
} else {
state = "resize";
this.style.cursor = "nw-resize";
}
});
event.addListener(settingOpener, "mousedown", function(e) {
if (state == "toggle") {
editor.setDisplaySettings();
return;
}
container.style.zIndex = 100000;
var rect = container.getBoundingClientRect();
var startX = rect.width + rect.left - e.clientX;
var startY = rect.height + rect.top - e.clientY;
event.capture(settingOpener, function(e) {
container.style.width = e.clientX - rect.left + startX + "px";
container.style.height = e.clientY - rect.top + startY + "px";
editor.resize();
}, function() {});
});
return editor;
};
function load(url, module, callback) {
net.loadScript(url, function() {
require([module], callback);
});
}
function setupApi(editor, editorDiv, settingDiv, ace, options, loader) {
var session = editor.getSession();
var renderer = editor.renderer;
loader = loader || load;
function toBool(value) {
return value === "true" || value == true;
}
editor.setDisplaySettings = function(display) {
if (display == null)
display = settingDiv.style.display == "none";
if (display) {
settingDiv.style.display = "block";
settingDiv.hideButton.focus();
editor.on("focus", function onFocus() {
editor.removeListener("focus", onFocus);
settingDiv.style.display = "none";
});
} else {
editor.focus();
}
};
editor.$setOption = editor.setOption;
editor.$getOption = editor.getOption;
editor.setOption = function(key, value) {
switch (key) {
case "mode":
editor.$setOption("mode", "ace/mode/" + value)
break;
case "theme":
editor.$setOption("theme", "ace/theme/" + value)
break;
case "keybindings":
switch (value) {
case "vim":
editor.setKeyboardHandler("ace/keyboard/vim");
break;
case "emacs":
editor.setKeyboardHandler("ace/keyboard/emacs");
break;
default:
editor.setKeyboardHandler(null);
}
break;
case "softWrap":
case "fontSize":
editor.$setOption(key, value);
break;
default:
editor.$setOption(key, toBool(value));
}
};
editor.getOption = function(key) {
switch (key) {
case "mode":
return editor.$getOption("mode").substr("ace/mode/".length)
break;
case "theme":
return editor.$getOption("theme").substr("ace/theme/".length)
break;
case "keybindings":
var value = editor.getKeyboardHandler()
switch (value && value.$id) {
case "ace/keyboard/vim":
return "vim";
case "ace/keyboard/emacs":
return "emacs";
default:
return "ace";
}
break;
default:
return editor.$getOption(key);
}
};
editor.setOptions(options);
return editor;
}
function setupSettingPanel(settingDiv, settingOpener, editor) {
var BOOL = null;
var desc = {
mode: "Mode:",
wrap: "Soft Wrap:",
theme: "Theme:",
fontSize: "Font Size:",
showGutter: "Display Gutter:",
keybindings: "Keyboard",
showPrintMargin: "Show Print Margin:",
useSoftTabs: "Use Soft Tabs:",
showInvisibles: "Show Invisibles"
};
var optionValues = {
mode: {
text: "Plain",
javascript: "JavaScript",
xml: "XML",
html: "HTML",
css: "CSS",
scss: "SCSS",
python: "Python",
php: "PHP",
java: "Java",
ruby: "Ruby",
c_cpp: "C/C++",
coffee: "CoffeeScript",
json: "json",
perl: "Perl",
clojure: "Clojure",
ocaml: "OCaml",
csharp: "C#",
haxe: "haXe",
svg: "SVG",
textile: "Textile",
groovy: "Groovy",
liquid: "Liquid",
Scala: "Scala"
},
theme: {
clouds: "Clouds",
clouds_midnight: "Clouds Midnight",
cobalt: "Cobalt",
crimson_editor: "Crimson Editor",
dawn: "Dawn",
eclipse: "Eclipse",
idle_fingers: "Idle Fingers",
kr_theme: "Kr Theme",
merbivore: "Merbivore",
merbivore_soft: "Merbivore Soft",
mono_industrial: "Mono Industrial",
monokai: "Monokai",
pastel_on_dark: "Pastel On Dark",
solarized_dark: "Solarized Dark",
solarized_light: "Solarized Light",
textmate: "Textmate",
twilight: "Twilight",
vibrant_ink: "Vibrant Ink"
},
showGutter: BOOL,
fontSize: {
"10px": "10px",
"11px": "11px",
"12px": "12px",
"14px": "14px",
"16px": "16px"
},
wrap: {
off: "Off",
40: "40",
80: "80",
free: "Free"
},
keybindings: {
ace: "ace",
vim: "vim",
emacs: "emacs"
},
showPrintMargin: BOOL,
useSoftTabs: BOOL,
showInvisibles: BOOL
};
var table = [];
table.push("<table><tr><th>Setting</th><th>Value</th></tr>");
function renderOption(builder, option, obj, cValue) {
if (!obj) {
builder.push(
"<input type='checkbox' title='", option, "' ",
cValue + "" == "true" ? "checked='true'" : "",
"'></input>"
);
return;
}
builder.push("<select title='" + option + "'>");
for (var value in obj) {
builder.push("<option value='" + value + "' ");
if (cValue == value) {
builder.push(" selected ");
}
builder.push(">",
obj[value],
"</option>");
}
builder.push("</select>");
}
for (var option in exports.defaultOptions) {
table.push("<tr><td>", desc[option], "</td>");
table.push("<td>");
renderOption(table, option, optionValues[option], editor.getOption(option));
table.push("</td></tr>");
}
table.push("</table>");
settingDiv.innerHTML = table.join("");
var onChange = function(e) {
var select = e.currentTarget;
editor.setOption(select.title, select.value);
};
var onClick = function(e) {
var cb = e.currentTarget;
editor.setOption(cb.title, cb.checked);
};
var selects = settingDiv.getElementsByTagName("select");
for (var i = 0; i < selects.length; i++)
selects[i].onchange = onChange;
var cbs = settingDiv.getElementsByTagName("input");
for (var i = 0; i < cbs.length; i++)
cbs[i].onclick = onClick;
var button = document.createElement("input");
button.type = "button";
button.value = "Hide";
event.addListener(button, "click", function() {
editor.setDisplaySettings(false);
});
settingDiv.appendChild(button);
settingDiv.hideButton = button;
}
// Default startup options.
exports.defaultOptions = {
mode: "javascript",
theme: "textmate",
wrap: "off",
fontSize: "12px",
showGutter: "false",
keybindings: "ace",
showPrintMargin: "false",
useSoftTabs: "true",
showInvisibles: "false"
};
});
@@ -1,100 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
* All rights reserved.
*
* Contributed to Ajax.org under the BSD license.
*
* 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 ***** */
/**
* Generates a list of themes available when ace was built.
* @fileOverview Generates a list of themes available when ace was built.
* @author <a href="mailto:matthewkastor@gmail.com">
* Matthew Christopher Kastor-Inare III </a><br />
* Hial Atropa!!
*/
define(function(require, exports, module) {
"use strict";
require("ace/lib/fixoldbrowsers");
var themeData = [
["Chrome" ],
["Clouds" ],
["Crimson Editor" ],
["Dawn" ],
["Dreamweaver" ],
["Eclipse" ],
["GitHub" ],
["Solarized Light"],
["TextMate" ],
["Tomorrow" ],
["XCode" ],
["Kuroir"],
["KatzenMilch"],
["Ambiance" ,"ambiance" , "dark"],
["Chaos" ,"chaos" , "dark"],
["Clouds Midnight" ,"clouds_midnight" , "dark"],
["Cobalt" ,"cobalt" , "dark"],
["idle Fingers" ,"idle_fingers" , "dark"],
["krTheme" ,"kr_theme" , "dark"],
["Merbivore" ,"merbivore" , "dark"],
["Merbivore Soft" ,"merbivore_soft" , "dark"],
["Mono Industrial" ,"mono_industrial" , "dark"],
["Monokai" ,"monokai" , "dark"],
["Pastel on dark" ,"pastel_on_dark" , "dark"],
["Solarized Dark" ,"solarized_dark" , "dark"],
["Terminal" ,"terminal" , "dark"],
["Tomorrow Night" ,"tomorrow_night" , "dark"],
["Tomorrow Night Blue" ,"tomorrow_night_blue" , "dark"],
["Tomorrow Night Bright","tomorrow_night_bright" , "dark"],
["Tomorrow Night 80s" ,"tomorrow_night_eighties" , "dark"],
["Twilight" ,"twilight" , "dark"],
["Vibrant Ink" ,"vibrant_ink" , "dark"]
];
exports.themesByName = {};
/**
* An array containing information about available themes.
*/
exports.themes = themeData.map(function(data) {
var name = data[1] || data[0].replace(/ /g, "_").toLowerCase();
var theme = {
caption: data[0],
theme: "ace/theme/" + name,
isDark: data[2] == "dark",
name: name
};
exports.themesByName[name] = theme;
return theme;
});
});
@@ -1,213 +0,0 @@
/* ***** 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 lang = require("../lib/lang");
// based on http://www.freehackers.org/Indent_Finder
exports.$detectIndentation = function(lines, fallback) {
var stats = [];
var changes = [];
var tabIndents = 0;
var prevSpaces = 0;
var max = Math.min(lines.length, 1000);
for (var i = 0; i < max; i++) {
var line = lines[i];
// ignore empty and comment lines
if (!/^\s*[^*+\-\s]/.test(line))
continue;
if (line[0] == "\t")
tabIndents++;
var spaces = line.match(/^ */)[0].length;
if (spaces && line[spaces] != "\t") {
var diff = spaces - prevSpaces;
if (diff > 0 && !(prevSpaces%diff) && !(spaces%diff))
changes[diff] = (changes[diff] || 0) + 1;
stats[spaces] = (stats[spaces] || 0) + 1;
}
prevSpaces = spaces;
// ignore lines ending with backslash
while (i < max && line[line.length - 1] == "\\")
line = lines[i++];
}
function getScore(indent) {
var score = 0;
for (var i = indent; i < stats.length; i += indent)
score += stats[i] || 0;
return score;
}
var changesTotal = changes.reduce(function(a,b){return a+b}, 0);
var first = {score: 0, length: 0};
var spaceIndents = 0;
for (var i = 1; i < 12; i++) {
var score = getScore(i);
if (i == 1) {
spaceIndents = score;
score = stats[1] ? 0.9 : 0.8;
if (!stats.length)
score = 0
} else
score /= spaceIndents;
if (changes[i])
score += changes[i] / changesTotal;
if (score > first.score)
first = {score: score, length: i};
}
if (first.score && first.score > 1.4)
var tabLength = first.length;
if (tabIndents > spaceIndents + 1)
return {ch: "\t", length: tabLength};
if (spaceIndents > tabIndents + 1)
return {ch: " ", length: tabLength};
};
exports.detectIndentation = function(session) {
var lines = session.getLines(0, 1000);
var indent = exports.$detectIndentation(lines) || {};
if (indent.ch)
session.setUseSoftTabs(indent.ch == " ");
if (indent.length)
session.setTabSize(indent.length);
return indent;
};
exports.trimTrailingSpace = function(session, trimEmpty) {
var doc = session.getDocument();
var lines = doc.getAllLines();
var min = trimEmpty ? -1 : 0;
for (var i = 0, l=lines.length; i < l; i++) {
var line = lines[i];
var index = line.search(/\s+$/);
if (index > min)
doc.removeInLine(i, index, line.length);
}
};
exports.convertIndentation = function(session, ch, len) {
var oldCh = session.getTabString()[0];
var oldLen = session.getTabSize();
if (!len) len = oldLen;
if (!ch) ch = oldCh;
var tab = ch == "\t" ? ch: lang.stringRepeat(ch, len);
var doc = session.doc;
var lines = doc.getAllLines();
var cache = {};
var spaceCache = {};
for (var i = 0, l=lines.length; i < l; i++) {
var line = lines[i];
var match = line.match(/^\s*/)[0];
if (match) {
var w = session.$getStringScreenWidth(match)[0];
var tabCount = Math.floor(w/oldLen);
var reminder = w%oldLen;
var toInsert = cache[tabCount] || (cache[tabCount] = lang.stringRepeat(tab, tabCount));
toInsert += spaceCache[reminder] || (spaceCache[reminder] = lang.stringRepeat(" ", reminder));
if (toInsert != match) {
doc.removeInLine(i, 0, match.length);
doc.insertInLine({row: i, column: 0}, toInsert);
}
}
}
session.setTabSize(len);
session.setUseSoftTabs(ch == " ");
};
exports.$parseStringArg = function(text) {
var indent = {};
if (/t/.test(text))
indent.ch = "\t";
else if (/s/.test(text))
indent.ch = " ";
var m = text.match(/\d+/);
if (m)
indent.length = parseInt(m[0], 10);
return indent;
};
exports.$parseArg = function(arg) {
if (!arg)
return {};
if (typeof arg == "string")
return exports.$parseStringArg(arg);
if (typeof arg.text == "string")
return exports.$parseStringArg(arg.text);
return arg;
};
exports.commands = [{
name: "detectIndentation",
exec: function(editor) {
exports.detectIndentation(editor.session);
// todo show message?
}
}, {
name: "trimTrailingSpace",
exec: function(editor) {
exports.trimTrailingSpace(editor.session);
}
}, {
name: "convertIndentation",
exec: function(editor, arg) {
var indent = exports.$parseArg(arg);
exports.convertIndentation(editor.session, indent.ch, indent.length);
}
}, {
name: "setIndentation",
exec: function(editor, arg) {
var indent = exports.$parseArg(arg);
indent.length && editor.session.setTabSize(indent.length);
indent.ch && editor.session.setUseSoftTabs(indent.ch == " ");
}
}];
});
@@ -1,116 +0,0 @@
if (typeof process !== "undefined") {
require("amd-loader");
require("../test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var assert = require("assert");
var EditSession = require("../edit_session").EditSession;
var whitespace = require("./whitespace");
// Execution ORDER: test.setUpSuite, setUp, testFn, tearDown, test.tearDownSuite
module.exports = {
timeout: 10000,
"test tab detection": function(next) {
var s = new EditSession([
"define({",
"\tfoo:1,",
"\tbar:2,",
"\tbaz:{,",
"\t\tx:3",
"\t}",
"})"
]);
var indent = whitespace.$detectIndentation(s.doc.$lines);
assert.equal(indent.ch, "\t");
assert.equal(indent.length, undefined);
s.insert({row: 0, column: 0}, " ");
indent = whitespace.$detectIndentation(s.doc.$lines);
assert.equal(indent.ch, "\t");
assert.equal(indent.length, 4);
s.setValue("");
indent = whitespace.$detectIndentation(s.doc.$lines);
assert.ok(!indent);
next();
},
"test empty session": function(next) {
var s = new EditSession([
"define({",
"foo:1,",
"})"
]);
var indent = whitespace.$detectIndentation(s.doc.$lines);
assert.ok(!indent);
s.insert({row: 1, column: 0}, " x\n ");
indent = whitespace.$detectIndentation(s.doc.$lines);
assert.equal(indent.ch, " ");
assert.equal(indent.length, 4);
next();
},
"!test one line": function(next) {
var s = new EditSession([
"define({",
" foo:1,",
"})"
]);
var indent = whitespace.$detectIndentation(s.doc.$lines);
assert.equal(indent.ch, " ");
assert.equal(indent.length, 4);
next();
},
"test 1 width indents": function(next) {
var s = new EditSession([
"define({",
" foo:1,",
"})",
"define({",
" bar:1,",
"})",
" t",
" t",
" t",
" t",
" t",
" t",
" t",
" t"
]);
var indent = whitespace.$detectIndentation(s.doc.$lines);
// assert.equal(indent.ch, " ");
// assert.equal(indent.length, 4);
s = new EditSession([
"{",
" foo:1,",
" bar: {",
" baz:2",
" }",
"}"
]);
indent = whitespace.$detectIndentation(s.doc.$lines);
assert.equal(indent.ch, " ");
assert.equal(indent.length, 1);
next();
},
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec();
}
@@ -1,317 +0,0 @@
/* ***** 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 Search = require("./search").Search;
var SearchHighlight = require("./search_highlight").SearchHighlight;
var iSearchCommandModule = require("./commands/incremental_search_commands");
var ISearchKbd = iSearchCommandModule.IncrementalSearchKeyboardHandler;
/**
* @class IncrementalSearch
*
* Implements immediate searching while the user is typing. When incremental
* search is activated, keystrokes into the editor will be used for composing
* a search term. Immediately after every keystroke the search is updated:
* - so-far-matching characters are highlighted
* - the cursor is moved to the next match
*
**/
/**
*
*
* Creates a new `IncrementalSearch` object.
*
* @constructor
**/
function IncrementalSearch() {
this.$options = {wrap: false, skipCurrent: false};
this.$keyboardHandler = new ISearchKbd(this);
}
oop.inherits(IncrementalSearch, Search);
// regexp handling
function isRegExp(obj) {
return obj instanceof RegExp;
}
function regExpToObject(re) {
var string = String(re),
start = string.indexOf('/'),
flagStart = string.lastIndexOf('/');
return {
expression: string.slice(start+1, flagStart),
flags: string.slice(flagStart+1)
}
}
function stringToRegExp(string, flags) {
try {
return new RegExp(string, flags);
} catch (e) { return string; }
}
function objectToRegExp(obj) {
return stringToRegExp(obj.expression, obj.flags);
}
// iSearch class
;(function() {
this.activate = function(ed, backwards) {
this.$editor = ed;
this.$startPos = this.$currentPos = ed.getCursorPosition();
this.$options.needle = '';
this.$options.backwards = backwards;
ed.keyBinding.addKeyboardHandler(this.$keyboardHandler);
// we need to completely intercept paste, just registering an event handler does not work
this.$originalEditorOnPaste = ed.onPaste; ed.onPaste = this.onPaste.bind(this);
this.$mousedownHandler = ed.addEventListener('mousedown', this.onMouseDown.bind(this));
this.selectionFix(ed);
this.statusMessage(true);
}
this.deactivate = function(reset) {
this.cancelSearch(reset);
var ed = this.$editor;
ed.keyBinding.removeKeyboardHandler(this.$keyboardHandler);
if (this.$mousedownHandler) {
ed.removeEventListener('mousedown', this.$mousedownHandler);
delete this.$mousedownHandler;
}
ed.onPaste = this.$originalEditorOnPaste;
this.message('');
}
this.selectionFix = function(editor) {
// Fix selection bug: When clicked inside the editor
// editor.selection.$isEmpty is false even if the mouse click did not
// open a selection. This is interpreted by the move commands to
// extend the selection. To only extend the selection when there is
// one, we clear it here
if (editor.selection.isEmpty() && !editor.session.$emacsMark) {
editor.clearSelection();
}
}
this.highlight = function(regexp) {
var sess = this.$editor.session,
hl = sess.$isearchHighlight = sess.$isearchHighlight || sess.addDynamicMarker(
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;
this.$prevNeedle = this.$options.needle;
this.$options.needle = '';
if (reset) {
e.moveCursorToPosition(this.$startPos);
this.$currentPos = this.$startPos;
} else {
e.pushEmacsMark && e.pushEmacsMark(this.$startPos, false);
}
this.highlight(null);
return Range.fromPoints(this.$currentPos, this.$currentPos);
}
this.highlightAndFindWithNeedle = function(moveToNext, needleUpdateFunc) {
if (!this.$editor) return null;
var options = this.$options;
// get search term
if (needleUpdateFunc) {
options.needle = needleUpdateFunc.call(this, options.needle || '') || '';
}
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;
var session = this.$editor.session,
found = this.find(session),
shouldSelect = this.$editor.emacsMark ?
!!this.$editor.emacsMark() : !this.$editor.selection.isEmpty();
if (found) {
if (options.backwards) found = Range.fromPoints(found.end, found.start);
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.statusMessage(found);
return found;
}
this.addString = function(s) {
return this.highlightAndFindWithNeedle(false, function(needle) {
if (!isRegExp(needle))
return needle + s;
var reObj = regExpToObject(needle);
reObj.expression += s;
return objectToRegExp(reObj);
});
}
this.removeChar = function(c) {
return this.highlightAndFindWithNeedle(false, function(needle) {
if (!isRegExp(needle))
return needle.substring(0, needle.length-1);
var reObj = regExpToObject(needle);
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
// earlier.
// options = {[backwards: BOOL], [useCurrentOrPrevSearch: BOOL]}
options = options || {};
this.$options.backwards = !!options.backwards;
this.$currentPos = this.$editor.getCursorPosition();
return this.highlightAndFindWithNeedle(true, function(needle) {
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 = '';
msg += options.backwards ? 'reverse-' : '';
msg += 'isearch: ' + options.needle;
msg += found ? '' : ' (not found)';
this.message(msg);
}
this.message = function(msg) {
if (this.$editor.showCommandLine) {
this.$editor.showCommandLine(msg);
this.$editor.focus();
} else {
console.log(msg);
}
}
}).call(IncrementalSearch.prototype);
exports.IncrementalSearch = IncrementalSearch;
/**
*
* Config settings for enabling/disabling [[IncrementalSearch `IncrementalSearch`]].
*
**/
var dom = require('./lib/dom');
dom.importCssString && dom.importCssString("\
.ace_marker-layer .ace_isearch-result {\
position: absolute;\
z-index: 6;\
-moz-box-sizing: border-box;\
-webkit-box-sizing: border-box;\
box-sizing: border-box;\
}\
div.ace_isearch-result {\
border-radius: 4px;\
background-color: rgba(255, 200, 0, 0.5);\
box-shadow: 0 0 4px rgb(255, 200, 0);\
}\
.ace_dark div.ace_isearch-result {\
background-color: rgb(100, 110, 160);\
box-shadow: 0 0 4px rgb(80, 90, 140);\
}", "incremental-search-highlighting");
// support for default keyboard handler
var commands = require("./commands/command_manager");
(function() {
this.setupIncrementalSearch = function(editor, val) {
if (this.usesIncrementalSearch == val) return;
this.usesIncrementalSearch = val;
var iSearchCommands = iSearchCommandModule.iSearchStartCommands;
var method = val ? 'addCommands' : 'removeCommands';
this[method](iSearchCommands);
};
}).call(commands.CommandManager.prototype);
// incremental search config option
var Editor = require("./editor").Editor;
require("./config").defineOptions(Editor.prototype, "editor", {
useIncrementalSearch: {
set: function(val) {
this.keyBinding.$handlers.forEach(function(handler) {
if (handler.setupIncrementalSearch) {
handler.setupIncrementalSearch(this, val);
}
});
this._emit('incrementalSearchSettingChanged', {isEnabled: val});
}
}
});
});
@@ -1,213 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var emacs = require('./keyboard/emacs');
var EditSession = require("./edit_session").EditSession;
var Editor = require("./editor").Editor;
var MockRenderer = require("./test/mockrenderer").MockRenderer;
var Range = require("./range").Range;
var MultiSelect = require("./multi_select").MultiSelect;
var assert = require("./test/assertions");
var IncrementalSearch = require("./incremental_search").IncrementalSearch;
require("./multi_select");
var editor, iSearch;
function testRanges(str, ranges) {
ranges = ranges || editor.selection.getAllRanges();
assert.equal(ranges + "", str + "");
}
// force "rerender"
function callHighlighterUpdate() {
var session = editor.session,
ranges = [],
mockMarkerLayer = {
drawSingleLineMarker: function(_, markerRanges) {
ranges = ranges.concat(markerRanges);
}
}
session.$isearchHighlight.update([], mockMarkerLayer, session, {
firstRow: 0, lastRow: session.getRowLength()});
return ranges;
}
module.exports = {
name: "ACE incremental_search.js",
setUp: function() {
var session = new EditSession(["abc123", "xyz124"]);
editor = new Editor(new MockRenderer(), session);
new MultiSelect(editor);
iSearch = new IncrementalSearch();
},
"test: keyboard handler setup" : function() {
iSearch.activate(editor);
assert.equal(editor.getKeyboardHandler(), iSearch.$keyboardHandler);
iSearch.deactivate();
assert.notEqual(editor.getKeyboardHandler(), iSearch.$keyboardHandler);
},
"test: isearch highlight setup" : function() {
var sess = editor.session;
iSearch.activate(editor);
iSearch.highlight('foo');
var highl = sess.$isearchHighlight.id;
assert.ok(sess.$isearchHighlight, 'session has no isearch highlighter');
assert.equal(sess.getMarkers()[highl.id], highl.id, 'isearch highlight not in markers');
iSearch.deactivate();
iSearch.activate(editor);
iSearch.highlight('bar');
var highl2 = sess.$isearchHighlight.id;
assert.equal(highl2, highl, 'multiple isearch highlights');
},
"test: find simple text incrementally" : function() {
iSearch.activate(editor);
var range = iSearch.addString('1'), // "1"
highlightRanges = callHighlighterUpdate(editor.session);
testRanges("Range: [0/3] -> [0/4]", [range], "range");
testRanges("Range: [0/3] -> [0/4],Range: [1/3] -> [1/4]", highlightRanges, "highlight");
range = iSearch.addString('2'); // "12"
highlightRanges = callHighlighterUpdate(editor.session);
testRanges("Range: [0/3] -> [0/5]", [range], "range");
testRanges("Range: [0/3] -> [0/5],Range: [1/3] -> [1/5]", highlightRanges, "highlight");
range = iSearch.addString('3'); // "123"
highlightRanges = callHighlighterUpdate(editor.session);
testRanges("Range: [0/3] -> [0/6]", [range], "range");
testRanges("Range: [0/3] -> [0/6]", highlightRanges, "highlight");
range = iSearch.removeChar(); // "12"
highlightRanges = callHighlighterUpdate(editor.session);
testRanges("Range: [0/3] -> [0/5]", [range], "range");
testRanges("Range: [0/3] -> [0/5],Range: [1/3] -> [1/5]", highlightRanges, "highlight");
},
"test: forward / backward" : function() {
iSearch.activate(editor);
iSearch.addString('1'); iSearch.addString('2');
var range = iSearch.next();
testRanges("Range: [1/3] -> [1/5]", [range], "range");
range = iSearch.next(); // nothing to find
testRanges("", [range], "range");
range = iSearch.next({backwards: true}); // backwards
testRanges("Range: [1/5] -> [1/3]", [range], "range");
},
"test: cancelSearch" : function() {
iSearch.activate(editor);
iSearch.addString('1'); iSearch.addString('2');
var range = iSearch.cancelSearch(true);
testRanges("Range: [0/0] -> [0/0]", [range], "range");
iSearch.addString('1'); range = iSearch.addString('2');
testRanges("Range: [0/3] -> [0/5]", [range], "range");
},
"test: failing search keeps pos" : function() {
iSearch.activate(editor);
iSearch.addString('1'); iSearch.addString('2');
var range = iSearch.addString('x');
testRanges("", [range], "range");
assert.position(editor.getCursorPosition(), 0, 5);
},
"test: backwards search" : function() {
editor.moveCursorTo(1,0);
iSearch.activate(editor, true);
iSearch.addString('1'); var range = iSearch.addString('2');;
testRanges("Range: [0/5] -> [0/3]", [range], "range");
assert.position(editor.getCursorPosition(), 0, 3);
},
"test: forwards then backwards, same result, reoriented range" : function() {
iSearch.activate(editor);
iSearch.addString('1'); var range = iSearch.addString('2');;
testRanges("Range: [0/3] -> [0/5]", [range], "range");
assert.position(editor.getCursorPosition(), 0, 5);
range = iSearch.next({backwards: true});
testRanges("Range: [0/5] -> [0/3]", [range], "range");
assert.position(editor.getCursorPosition(), 0, 3);
},
"test: reuse prev search via option" : function() {
iSearch.activate(editor);
iSearch.addString('1'); iSearch.addString('2');;
assert.position(editor.getCursorPosition(), 0, 5);
iSearch.deactivate();
iSearch.activate(editor);
iSearch.next({backwards: false, useCurrentOrPrevSearch: true});
assert.position(editor.getCursorPosition(), 1, 5);
},
"test: don't extend selection range if selection is empty" : function() {
iSearch.activate(editor);
iSearch.addString('1'); iSearch.addString('2');;
testRanges("Range: [0/5] -> [0/5]", [editor.getSelectionRange()], "sel range");
},
"test: extend selection range if selection exists" : function() {
iSearch.activate(editor);
editor.selection.selectTo(0, 1);
iSearch.addString('1'); iSearch.addString('2');;
testRanges("Range: [0/0] -> [0/5]", [editor.getSelectionRange()], "sel range");
},
"test: extend selection in emacs mark mode" : function() {
var emacs = require('./keyboard/emacs');
editor.keyBinding.addKeyboardHandler(emacs.handler);
emacs.handler.commands.setMark.exec(editor);
iSearch.activate(editor);
iSearch.addString('1'); iSearch.addString('2');
testRanges("Range: [0/0] -> [0/5]", [editor.getSelectionRange()], "sel range");
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,652 +0,0 @@
/* ***** 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 dom = require("../lib/dom");
require("../incremental_search");
var iSearchCommandModule = require("../commands/incremental_search_commands");
var screenToTextBlockCoordinates = function(x, y) {
var canvasPos = this.scroller.getBoundingClientRect();
var col = Math.floor(
(x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth
);
var row = Math.floor(
(y + this.scrollTop - canvasPos.top) / this.lineHeight
);
return this.session.screenToDocumentPosition(row, col);
};
var HashHandler = require("./hash_handler").HashHandler;
exports.handler = new HashHandler();
exports.handler.isEmacs = true;
exports.handler.$id = "ace/keyboard/emacs";
var initialized = false;
var $formerLongWords;
var $formerLineStart;
exports.handler.attach = function(editor) {
if (!initialized) {
initialized = true;
dom.importCssString('\
.emacs-mode .ace_cursor{\
border: 1px rgba(50,250,50,0.8) solid!important;\
-moz-box-sizing: border-box!important;\
-webkit-box-sizing: border-box!important;\
box-sizing: border-box!important;\
background-color: rgba(0,250,0,0.9);\
opacity: 0.5;\
}\
.emacs-mode .ace_hidden-cursors .ace_cursor{\
opacity: 1;\
background-color: transparent;\
}\
.emacs-mode .ace_overwrite-cursors .ace_cursor {\
opacity: 1;\
background-color: transparent;\
border-width: 0 0 2px 2px !important;\
}\
.emacs-mode .ace_text-layer {\
z-index: 4\
}\
.emacs-mode .ace_cursor-layer {\
z-index: 2\
}', 'emacsMode'
);
}
// in emacs, gotowordleft/right should not count a space as a word..
$formerLongWords = editor.session.$selectLongWords;
editor.session.$selectLongWords = true;
// CTRL-A should go to actual beginning of line
$formerLineStart = editor.session.$useEmacsStyleLineStart;
editor.session.$useEmacsStyleLineStart = true;
editor.session.$emacsMark = null; // the active mark
editor.session.$emacsMarkRing = editor.session.$emacsMarkRing || [];
editor.emacsMark = function() {
return this.session.$emacsMark;
};
editor.setEmacsMark = function(p) {
// to deactivate pass in a falsy value
this.session.$emacsMark = p;
};
editor.pushEmacsMark = function(p, activate) {
var prevMark = this.session.$emacsMark;
if (prevMark)
this.session.$emacsMarkRing.push(prevMark);
if (!p || activate) this.setEmacsMark(p);
else this.session.$emacsMarkRing.push(p);
};
editor.popEmacsMark = function() {
var mark = this.emacsMark();
if (mark) { this.setEmacsMark(null); return mark; }
return this.session.$emacsMarkRing.pop();
};
editor.getLastEmacsMark = function(p) {
return this.session.$emacsMark || this.session.$emacsMarkRing.slice(-1)[0];
};
editor.emacsMarkForSelection = function(replacement) {
// find the mark in $emacsMarkRing corresponding to the current
// selection
var sel = this.selection,
multiRangeLength = this.multiSelect ?
this.multiSelect.getAllRanges().length : 1,
selIndex = sel.index || 0,
markRing = this.session.$emacsMarkRing,
markIndex = markRing.length - (multiRangeLength - selIndex),
lastMark = markRing[markIndex] || sel.anchor;
if (replacement) {
markRing.splice(markIndex, 1,
"row" in replacement && "column" in replacement ?
replacement : undefined);
}
return lastMark;
}
editor.on("click", $resetMarkMode);
editor.on("changeSession", $kbSessionChange);
editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates;
editor.setStyle("emacs-mode");
editor.commands.addCommands(commands);
exports.handler.platform = editor.commands.platform;
editor.$emacsModeHandler = this;
editor.addEventListener('copy', this.onCopy);
editor.addEventListener('paste', this.onPaste);
};
exports.handler.detach = function(editor) {
delete editor.renderer.screenToTextCoordinates;
editor.session.$selectLongWords = $formerLongWords;
editor.session.$useEmacsStyleLineStart = $formerLineStart;
editor.removeEventListener("click", $resetMarkMode);
editor.removeEventListener("changeSession", $kbSessionChange);
editor.unsetStyle("emacs-mode");
editor.commands.removeCommands(commands);
editor.removeEventListener('copy', this.onCopy);
editor.removeEventListener('paste', this.onPaste);
editor.$emacsModeHandler = null;
};
var $kbSessionChange = function(e) {
if (e.oldSession) {
e.oldSession.$selectLongWords = $formerLongWords;
e.oldSession.$useEmacsStyleLineStart = $formerLineStart;
}
$formerLongWords = e.session.$selectLongWords;
e.session.$selectLongWords = true;
$formerLineStart = e.session.$useEmacsStyleLineStart;
e.session.$useEmacsStyleLineStart = true;
if (!e.session.hasOwnProperty('$emacsMark'))
e.session.$emacsMark = null;
if (!e.session.hasOwnProperty('$emacsMarkRing'))
e.session.$emacsMarkRing = [];
};
var $resetMarkMode = function(e) {
e.editor.session.$emacsMark = null;
};
var keys = require("../lib/keys").KEY_MODS;
var eMods = {C: "ctrl", S: "shift", M: "alt", CMD: "command"};
var combinations = ["C-S-M-CMD",
"S-M-CMD", "C-M-CMD", "C-S-CMD", "C-S-M",
"M-CMD", "S-CMD", "S-M", "C-CMD", "C-M", "C-S",
"CMD", "M", "S", "C"];
combinations.forEach(function(c) {
var hashId = 0;
c.split("-").forEach(function(c) {
hashId = hashId | keys[eMods[c]];
});
eMods[hashId] = c.toLowerCase() + "-";
});
exports.handler.onCopy = function(e, editor) {
if (editor.$handlesEmacsOnCopy) return;
editor.$handlesEmacsOnCopy = true;
exports.handler.commands.killRingSave.exec(editor);
editor.$handlesEmacsOnCopy = false;
};
exports.handler.onPaste = function(e, editor) {
editor.pushEmacsMark(editor.getCursorPosition());
};
exports.handler.bindKey = function(key, command) {
if (typeof key == "object")
key = key[this.platform];
if (!key)
return;
var ckb = this.commandKeyBinding;
key.split("|").forEach(function(keyPart) {
keyPart = keyPart.toLowerCase();
ckb[keyPart] = command;
// register all partial key combos as null commands
// to be able to activate key combos with arbitrary length
// Example: if keyPart is "C-c C-l t" then "C-c C-l t" will
// get command assigned and "C-c" and "C-c C-l" will get
// a null command assigned in this.commandKeyBinding. For
// the lookup logic see handleKeyboard()
var keyParts = keyPart.split(" ").slice(0,-1);
keyParts.reduce(function(keyMapKeys, keyPart, i) {
var prefix = keyMapKeys[i-1] ? keyMapKeys[i-1] + ' ' : '';
return keyMapKeys.concat([prefix + keyPart]);
}, []).forEach(function(keyPart) {
if (!ckb[keyPart]) ckb[keyPart] = "null";
});
}, this);
};
exports.handler.getStatusText = function(editor, data) {
var str = "";
if (data.count)
str += data.count;
if (data.keyChain)
str += " " + data.keyChain
return str;
};
exports.handler.handleKeyboard = function(data, hashId, key, keyCode) {
// if keyCode == -1 a non-printable key was pressed, such as just
// control. Handling those is currently not supported in this handler
if (keyCode === -1) return undefined;
var editor = data.editor;
editor._signal("changeStatus");
// insertstring data.count times
if (hashId == -1) {
editor.pushEmacsMark();
if (data.count) {
var str = new Array(data.count + 1).join(key);
data.count = null;
return {command: "insertstring", args: str};
}
}
var modifier = eMods[hashId];
// CTRL + number / universalArgument for setting data.count
if (modifier == "c-" || data.count) {
var count = parseInt(key[key.length - 1]);
if (typeof count === 'number' && !isNaN(count)) {
data.count = Math.max(data.count, 0) || 0;
data.count = 10 * data.count + count;
return {command: "null"};
}
}
// this.commandKeyBinding maps key specs like "c-p" (for CTRL + P) to
// command objects, for lookup key needs to include the modifier
if (modifier) key = modifier + key;
// Key combos like CTRL+X H build up the data.keyChain
if (data.keyChain) key = data.keyChain += " " + key;
// Key combo prefixes get stored as "null" (String!) in this
// this.commandKeyBinding. When encountered no command is invoked but we
// buld up data.keyChain
var command = this.commandKeyBinding[key];
data.keyChain = command == "null" ? key : "";
// there really is no command
if (!command) return undefined;
// we pass b/c of key combo or universalArgument
if (command === "null") return {command: "null"};
if (command === "universalArgument") {
// if no number pressed emacs repeats action 4 times.
// minus sign is needed to allow next keypress to replace it
data.count = -4;
return {command: "null"};
}
// lookup command
// TODO extract special handling of markmode
// TODO special case command.command is really unnecessary, remove
var args;
if (typeof command !== "string") {
args = command.args;
if (command.command) command = command.command;
if (command === "goorselect") {
command = editor.emacsMark() ? args[1] : args[0];
args = null;
}
}
if (typeof command === "string") {
if (command === "insertstring" ||
command === "splitline" ||
command === "togglecomment") {
editor.pushEmacsMark();
}
command = this.commands[command] || editor.commands.commands[command];
if (!command) return undefined;
}
if (!command.readOnly && !command.isYank)
data.lastCommand = null;
if (!command.readOnly && editor.emacsMark())
editor.setEmacsMark(null)
if (data.count) {
var count = data.count;
data.count = 0;
if (!command || !command.handlesCount) {
return {
args: args,
command: {
exec: function(editor, args) {
for (var i = 0; i < count; i++)
command.exec(editor, args);
},
multiSelectAction: command.multiSelectAction
}
};
} else {
if (!args) args = {};
if (typeof args === 'object') args.count = count;
}
}
return {command: command, args: args};
};
exports.emacsKeys = {
// movement
"Up|C-p" : {command: "goorselect", args: ["golineup","selectup"]},
"Down|C-n" : {command: "goorselect", args: ["golinedown","selectdown"]},
"Left|C-b" : {command: "goorselect", args: ["gotoleft","selectleft"]},
"Right|C-f" : {command: "goorselect", args: ["gotoright","selectright"]},
"C-Left|M-b" : {command: "goorselect", args: ["gotowordleft","selectwordleft"]},
"C-Right|M-f" : {command: "goorselect", args: ["gotowordright","selectwordright"]},
"Home|C-a" : {command: "goorselect", args: ["gotolinestart","selecttolinestart"]},
"End|C-e" : {command: "goorselect", args: ["gotolineend","selecttolineend"]},
"C-Home|S-M-,": {command: "goorselect", args: ["gotostart","selecttostart"]},
"C-End|S-M-." : {command: "goorselect", args: ["gotoend","selecttoend"]},
// selection
"S-Up|S-C-p" : "selectup",
"S-Down|S-C-n" : "selectdown",
"S-Left|S-C-b" : "selectleft",
"S-Right|S-C-f" : "selectright",
"S-C-Left|S-M-b" : "selectwordleft",
"S-C-Right|S-M-f" : "selectwordright",
"S-Home|S-C-a" : "selecttolinestart",
"S-End|S-C-e" : "selecttolineend",
"S-C-Home" : "selecttostart",
"S-C-End" : "selecttoend",
"C-l" : "recenterTopBottom",
"M-s" : "centerselection",
"M-g": "gotoline",
"C-x C-p": "selectall",
// todo fix these
"C-Down": {command: "goorselect", args: ["gotopagedown","selectpagedown"]},
"C-Up": {command: "goorselect", args: ["gotopageup","selectpageup"]},
"PageDown|C-v": {command: "goorselect", args: ["gotopagedown","selectpagedown"]},
"PageUp|M-v": {command: "goorselect", args: ["gotopageup","selectpageup"]},
"S-C-Down": "selectpagedown",
"S-C-Up": "selectpageup",
"C-s": "iSearch",
"C-r": "iSearchBackwards",
"M-C-s": "findnext",
"M-C-r": "findprevious",
"S-M-5": "replace",
// basic editing
"Backspace": "backspace",
"Delete|C-d": "del",
"Return|C-m": {command: "insertstring", args: "\n"}, // "newline"
"C-o": "splitline",
"M-d|C-Delete": {command: "killWord", args: "right"},
"C-Backspace|M-Backspace|M-Delete": {command: "killWord", args: "left"},
"C-k": "killLine",
"C-y|S-Delete": "yank",
"M-y": "yankRotate",
"C-g": "keyboardQuit",
"C-w|C-S-W": "killRegion",
"M-w": "killRingSave",
"C-Space": "setMark",
"C-x C-x": "exchangePointAndMark",
"C-t": "transposeletters",
"M-u": "touppercase", // Doesn't work
"M-l": "tolowercase",
"M-/": "autocomplete", // Doesn't work
"C-u": "universalArgument",
"M-;": "togglecomment",
"C-/|C-x u|S-C--|C-z": "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 "}
// todo
// "C-x C-t" "M-t" "M-c" "F11" "C-M- "M-q"
};
exports.handler.bindKeys(exports.emacsKeys);
exports.handler.addCommands({
recenterTopBottom: function(editor) {
var renderer = editor.renderer;
var pos = renderer.$cursorLayer.getPixelPosition();
var h = renderer.$size.scrollerHeight - renderer.lineHeight;
var scrollTop = renderer.scrollTop;
if (Math.abs(pos.top - scrollTop) < 2) {
scrollTop = pos.top - h;
} else if (Math.abs(pos.top - scrollTop - h * 0.5) < 2) {
scrollTop = pos.top;
} else {
scrollTop = pos.top - h * 0.5;
}
editor.session.setScrollTop(scrollTop);
},
selectRectangularRegion: function(editor) {
editor.multiSelect.toggleBlockSelection();
},
setMark: {
exec: function(editor, args) {
// Sets mark-mode and clears current selection.
// When mark is set, keyboard cursor movement commands become
// selection modification commands. That is,
// "goto" commands become "select" commands.
// Any insertion or mouse click resets mark-mode.
// setMark twice in a row at the same place resets markmode.
// in multi select mode, ea selection is handled individually
if (args && args.count) {
if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark);
else moveToMark();
moveToMark();
return;
}
var mark = editor.emacsMark(),
ranges = editor.selection.getAllRanges(),
rangePositions = ranges.map(function(r) { return {row: r.start.row, column: r.start.column}; }),
transientMarkModeActive = true,
hasNoSelection = ranges.every(function(range) { return range.isEmpty(); });
// if transientMarkModeActive then mark behavior is a little
// 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)})
else editor.clearSelection();
if (mark) editor.pushEmacsMark(null);
return;
}
if (!mark) {
rangePositions.forEach(function(pos) { editor.pushEmacsMark(pos); });
editor.setEmacsMark(rangePositions[rangePositions.length-1]);
return;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function moveToMark() {
var mark = editor.popEmacsMark();
mark && editor.moveCursorToPosition(mark);
}
},
readOnly: true,
handlesCount: true
},
exchangePointAndMark: {
exec: function exchangePointAndMark$exec(editor, args) {
var sel = editor.selection;
if (!args.count && !sel.isEmpty()) { // just invert selection
sel.setSelectionRange(sel.getRange(), !sel.isBackwards());
return;
}
if (args.count) { // replace mark and point
var pos = {row: sel.lead.row, column: sel.lead.column};
sel.clearSelection();
sel.moveCursorToPosition(editor.emacsMarkForSelection(pos));
} else { // create selection to last mark
sel.selectToPosition(editor.emacsMarkForSelection());
}
},
readOnly: true,
handlesCount: true,
multiSelectAction: "forEach"
},
killWord: {
exec: function(editor, dir) {
editor.clearSelection();
if (dir == "left")
editor.selection.selectWordLeft();
else
editor.selection.selectWordRight();
var range = editor.getSelectionRange();
var text = editor.session.getTextRange(range);
exports.killRing.add(text);
editor.session.remove(range);
editor.clearSelection();
},
multiSelectAction: "forEach"
},
killLine: function(editor) {
editor.pushEmacsMark(null);
var pos = editor.getCursorPosition();
if (pos.column === 0 &&
editor.session.doc.getLine(pos.row).length === 0) {
// If an already empty line is killed, remove
// the line entirely
editor.selection.selectLine();
} else {
// otherwise just remove from the current cursor position
// to the end (but don't delete the selection if it's before
// the cursor)
editor.clearSelection();
editor.selection.selectLineEnd();
}
var range = editor.getSelectionRange();
var text = editor.session.getTextRange(range);
exports.killRing.add(text);
editor.session.remove(range);
editor.clearSelection();
},
yank: function(editor) {
editor.onPaste(exports.killRing.get() || '');
editor.keyBinding.$data.lastCommand = "yank";
},
yankRotate: function(editor) {
if (editor.keyBinding.$data.lastCommand != "yank")
return;
editor.undo();
editor.session.$emacsMarkRing.pop(); // also undo recording mark
editor.onPaste(exports.killRing.rotate());
editor.keyBinding.$data.lastCommand = "yank";
},
killRegion: {
exec: function(editor) {
exports.killRing.add(editor.getCopyText());
editor.commands.byName.cut.exec(editor);
},
readOnly: true,
multiSelectAction: "forEach"
},
killRingSave: {
exec: function(editor) {
// copy text and deselect. will save marks for starts of the
// selection(s)
editor.$handlesEmacsOnCopy = true;
var marks = editor.session.$emacsMarkRing.slice(),
deselectedMarks = [];
exports.killRing.add(editor.getCopyText());
setTimeout(function() {
function deselect() {
var sel = editor.selection, range = sel.getRange(),
pos = sel.isBackwards() ? range.end : range.start;
deselectedMarks.push({row: pos.row, column: pos.column});
sel.clearSelection();
}
editor.$handlesEmacsOnCopy = false;
if (editor.inMultiSelectMode) editor.forEachSelection({exec: deselect});
else deselect();
editor.session.$emacsMarkRing = marks.concat(deselectedMarks.reverse());
}, 0);
},
readOnly: true
},
keyboardQuit: function(editor) {
editor.selection.clearSelection();
editor.setEmacsMark(null);
editor.keyBinding.$data.count = null;
},
focusCommandLine: function(editor, arg) {
if (editor.showCommandLine)
editor.showCommandLine(arg);
}
});
exports.handler.addCommands(iSearchCommandModule.iSearchStartCommands);
var commands = exports.handler.commands;
commands.yank.isYank = true;
commands.yankRotate.isYank = true;
exports.killRing = {
$data: [],
add: function(str) {
str && this.$data.push(str);
if (this.$data.length > 30)
this.$data.shift();
},
get: function(n) {
n = n || 1;
return this.$data.slice(this.$data.length-n, this.$data.length).reverse().join('\n');
},
pop: function() {
if (this.$data.length > 1)
this.$data.pop();
return this.get();
},
rotate: function() {
this.$data.unshift(this.$data.pop());
return this.get();
}
};
});
@@ -1,151 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
require("../multi_select");
var EditSession = require("./../edit_session").EditSession,
Editor = require("./../editor").Editor,
Range = require("./../range").Range,
MockRenderer = require("./../test/mockrenderer").MockRenderer,
emacs = require('./emacs'),
assert = require("./../test/assertions"),
editor, sel;
function initEditor(docString) {
var doc = new EditSession(docString.split("\n"));
editor = new Editor(new MockRenderer(), doc);
editor.setKeyboardHandler(emacs.handler);
sel = editor.selection;
}
function print(obj) {
return JSON.stringify(obj, null, 2);
}
function pluck(arr, what) {
return arr.map(function(ea) { return ea[what]; });
}
module.exports = {
"test: detach removes emacs commands from command manager": function() {
initEditor('');
assert.ok(!!editor.commands.byName["keyboardQuit"], 'setup error: emacs commands not installed');
editor.keyBinding.removeKeyboardHandler(editor.getKeyboardHandler());
assert.ok(!editor.commands.byName["keyboardQuit"], 'emacs commands not removed');
},
"test: keyboardQuit clears selection": function() {
initEditor('foo');
editor.selectAll();
editor.execCommand('keyboardQuit');
assert.ok(editor.selection.isEmpty(), 'selection non-empty');
},
"test: exchangePointAndMark without mark set": function() {
initEditor('foo');
sel.setRange(Range.fromPoints({row: 0, column: 1}, {row: 0, column: 3}));
editor.execCommand('exchangePointAndMark');
assert.deepEqual({row: 0, column: 1}, editor.getCursorPosition(), print(editor.getCursorPosition()));
},
"test: exchangePointAndMark with mark set": function() {
initEditor('foo');
editor.pushEmacsMark({row: 0, column: 1});
editor.pushEmacsMark({row: 0, column: 2});
editor.execCommand('exchangePointAndMark', {count: 4});
assert.deepEqual({row: 0, column: 2}, editor.getCursorPosition(), print(editor.getCursorPosition()));
assert.deepEqual([{row: 0, column: 1}, {row: 0, column: 0}], editor.session.$emacsMarkRing, print(editor.session.$emacsMarkRing));
},
"test: exchangePointAndMark with selection": function() {
initEditor('foo');
editor.pushEmacsMark({row: 0, column: 1});
editor.pushEmacsMark({row: 0, column: 2});
sel.setRange(Range.fromPoints({row: 0, column: 0}, {row: 0, column: 1}), true);
editor.execCommand('exchangePointAndMark');
assert.deepEqual({row: 0, column: 1}, editor.getCursorPosition(), print(editor.getCursorPosition()));
assert.deepEqual([{row: 0, column: 1}, {row: 0, column: 2}], editor.session.$emacsMarkRing, print(editor.session.$emacsMarkRing));
},
"test: exchangePointAndMark with multi selection": function() {
initEditor('foo\nhello world\n123');
var ranges = [[{row: 0, column: 0}, {row: 0, column: 3}],
[{row: 1, column: 0}, {row: 1, column: 5}],
[{row: 1, column: 6}, {row: 1, column: 11}]]
ranges.forEach(function(r) {
sel.addRange(Range.fromPoints(r[0], r[1]));
});
assert.equal("foo\nhello\nworld", editor.getSelectedText());
editor.execCommand('exchangePointAndMark');
assert.equal("foo\nhello\nworld", editor.getSelectedText());
assert.deepEqual(pluck(ranges, 0), pluck(sel.getAllRanges(), 'cursor'), "selections dir not inverted");
},
"test: exchangePointAndMark with multi cursors": function() {
initEditor('foo\nhello world\n123');
var ranges = [[{row: 0, column: 0}, {row: 0, column: 3}],
[{row: 1, column: 0}, {row: 1, column: 5}],
[{row: 1, column: 6}, {row: 1, column: 11}]];
// move cursors to the start of each range and set a mark to its end
// without selecting anything
ranges.forEach(function(r) {
editor.pushEmacsMark(r[1]);
sel.addRange(Range.fromPoints(r[0], r[0]));
});
assert.deepEqual(pluck(ranges, 0), pluck(sel.getAllRanges(), 'cursor'), print(sel.getAllRanges()));
editor.execCommand('exchangePointAndMark');
assert.deepEqual(pluck(ranges, 1), pluck(sel.getAllRanges(), 'cursor'), "not inverted: " + print(sel.getAllRanges()));
},
"test: setMark with multi cursors": function() {
initEditor('foo\nhello world\n123');
var positions = [{row: 0, column: 0},
{row: 1, column: 0},
{row: 1, column: 6}];
positions.forEach(function(p) { sel.addRange(Range.fromPoints(p,p)); });
editor.execCommand('setMark');
assert.deepEqual(positions, editor.session.$emacsMarkRing, print(editor.session.$emacsMarkRing));
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,231 +0,0 @@
/* ***** 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 keyUtil = require("../lib/keys");
var useragent = require("../lib/useragent");
var KEY_MODS = keyUtil.KEY_MODS;
function HashHandler(config, platform) {
this.platform = platform || (useragent.isMac ? "mac" : "win");
this.commands = {};
this.commandKeyBinding = {};
this.addCommands(config);
this.$singleCommand = true;
}
function MultiHashHandler(config, platform) {
HashHandler.call(this, config, platform);
this.$singleCommand = false;
}
MultiHashHandler.prototype = HashHandler.prototype;
(function() {
this.addCommand = function(command) {
if (this.commands[command.name])
this.removeCommand(command);
this.commands[command.name] = command;
if (command.bindKey)
this._buildKeyHash(command);
};
this.removeCommand = function(command, keepCommand) {
var name = command && (typeof command === 'string' ? command : command.name);
command = this.commands[name];
if (!keepCommand)
delete this.commands[name];
// exhaustive search is brute force but since removeCommand is
// not a performance critical operation this should be OK
var ckb = this.commandKeyBinding;
for (var keyId in ckb) {
var cmdGroup = ckb[keyId];
if (cmdGroup == command) {
delete ckb[keyId];
} else if (Array.isArray(cmdGroup)) {
var i = cmdGroup.indexOf(command);
if (i != -1) {
cmdGroup.splice(i, 1);
if (cmdGroup.length == 1)
ckb[keyId] = cmdGroup[0];
}
}
}
};
this.bindKey = function(key, command, asDefault) {
if (typeof key == "object")
key = key[this.platform];
if (!key)
return;
if (typeof command == "function")
return this.addCommand({exec: command, bindKey: key, name: command.name || key});
key.split("|").forEach(function(keyPart) {
var chain = "";
if (keyPart.indexOf(" ") != -1) {
var parts = keyPart.split(/\s+/);
keyPart = parts.pop();
parts.forEach(function(keyPart) {
var binding = this.parseKeys(keyPart);
var id = KEY_MODS[binding.hashId] + binding.key;
chain += (chain ? " " : "") + id;
this._addCommandToBinding(chain, "chainKeys");
}, this);
chain += " ";
}
var binding = this.parseKeys(keyPart);
var id = KEY_MODS[binding.hashId] + binding.key;
this._addCommandToBinding(chain + id, command, asDefault);
}, this);
};
this._addCommandToBinding = function(keyId, command, asDefault) {
var ckb = this.commandKeyBinding, i;
if (!command) {
delete ckb[keyId];
} else if (!ckb[keyId] || this.$singleCommand) {
ckb[keyId] = command;
} else {
if (!Array.isArray(ckb[keyId])) {
ckb[keyId] = [ckb[keyId]];
} 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);
}
};
this.addCommands = function(commands) {
commands && Object.keys(commands).forEach(function(name) {
var command = commands[name];
if (!command)
return;
if (typeof command === "string")
return this.bindKey(command, name);
if (typeof command === "function")
command = { exec: command };
if (typeof command !== "object")
return;
if (!command.name)
command.name = name;
this.addCommand(command);
}, this);
};
this.removeCommands = function(commands) {
Object.keys(commands).forEach(function(name) {
this.removeCommand(commands[name]);
}, this);
};
this.bindKeys = function(keyList) {
Object.keys(keyList).forEach(function(key) {
this.bindKey(key, keyList[key]);
}, this);
};
this._buildKeyHash = function(command) {
this.bindKey(command.bindKey, command);
};
// accepts keys in the form ctrl+Enter or ctrl-Enter
// keys without modifiers or shift only
this.parseKeys = function(keys) {
var parts = keys.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(x){return x});
var key = parts.pop();
var keyCode = keyUtil[key];
if (keyUtil.FUNCTION_KEYS[keyCode])
key = keyUtil.FUNCTION_KEYS[keyCode].toLowerCase();
else if (!parts.length)
return {key: key, hashId: -1};
else if (parts.length == 1 && parts[0] == "shift")
return {key: key.toUpperCase(), hashId: -1};
var hashId = 0;
for (var i = parts.length; i--;) {
var modifier = keyUtil.KEY_MODS[parts[i]];
if (modifier == null) {
if (typeof console != "undefined")
console.error("invalid modifier " + parts[i] + " in " + keys);
return false;
}
hashId |= modifier;
}
return {key: key, hashId: hashId};
};
this.findKeyCommand = function findKeyCommand(hashId, keyString) {
var key = KEY_MODS[hashId] + keyString;
return this.commandKeyBinding[key];
};
this.handleKeyboard = function(data, hashId, keyString, keyCode) {
var key = KEY_MODS[hashId] + keyString;
var command = this.commandKeyBinding[key];
if (data.$keyChain) {
data.$keyChain += " " + key;
command = this.commandKeyBinding[data.$keyChain] || command;
}
if (command) {
if (command == "chainKeys" || command[command.length - 1] == "chainKeys") {
data.$keyChain = data.$keyChain || key;
return {command: "null"};
}
}
if (data.$keyChain && keyCode > 0)
data.$keyChain = "";
return {command: command};
};
}).call(HashHandler.prototype);
exports.HashHandler = HashHandler;
exports.MultiHashHandler = MultiHashHandler;
});
@@ -1,145 +0,0 @@
/* ***** 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 keyUtil = require("../lib/keys");
var event = require("../lib/event");
var KeyBinding = function(editor) {
this.$editor = editor;
this.$data = {editor: editor};
this.$handlers = [];
this.setDefaultHandler(editor.commands);
};
(function() {
this.setDefaultHandler = function(kb) {
this.removeKeyboardHandler(this.$defaultHandler);
this.$defaultHandler = kb;
this.addKeyboardHandler(kb, 0);
};
this.setKeyboardHandler = function(kb) {
var h = this.$handlers;
if (h[h.length - 1] == kb)
return;
while (h[h.length - 1] && h[h.length - 1] != this.$defaultHandler)
this.removeKeyboardHandler(h[h.length - 1]);
this.addKeyboardHandler(kb, 1);
};
this.addKeyboardHandler = function(kb, pos) {
if (!kb)
return;
if (typeof kb == "function" && !kb.handleKeyboard)
kb.handleKeyboard = kb;
var i = this.$handlers.indexOf(kb);
if (i != -1)
this.$handlers.splice(i, 1);
if (pos == undefined)
this.$handlers.push(kb);
else
this.$handlers.splice(pos, 0, kb);
if (i == -1 && kb.attach)
kb.attach(this.$editor);
};
this.removeKeyboardHandler = function(kb) {
var i = this.$handlers.indexOf(kb);
if (i == -1)
return false;
this.$handlers.splice(i, 1);
kb.detach && kb.detach(this.$editor);
return true;
};
this.getKeyboardHandler = function() {
return this.$handlers[this.$handlers.length - 1];
};
this.getStatusText = function() {
var data = this.$data;
var editor = data.editor;
return this.$handlers.map(function(h) {
return h.getStatusText && h.getStatusText(editor, data) || "";
}).filter(Boolean).join(" ");
};
this.$callKeyboardHandlers = function(hashId, keyString, keyCode, e) {
var toExecute;
var success = false;
var commands = this.$editor.commands;
for (var i = this.$handlers.length; i--;) {
toExecute = this.$handlers[i].handleKeyboard(
this.$data, hashId, keyString, keyCode, e
);
if (!toExecute || !toExecute.command)
continue;
// allow keyboardHandler to consume keys
if (toExecute.command == "null") {
success = true;
} else {
success = commands.exec(toExecute.command, this.$editor, toExecute.args, e);
}
// do not stop input events to not break repeating
if (success && e && hashId != -1 &&
toExecute.passEvent != true && toExecute.command.passEvent != true
) {
event.stopEvent(e);
}
if (success)
break;
}
return success;
};
this.onCommandKey = function(e, hashId, keyCode) {
var keyString = keyUtil.keyCodeToString(keyCode);
this.$callKeyboardHandlers(hashId, keyString, keyCode, e);
};
this.onTextInput = function(text) {
var success = this.$callKeyboardHandlers(-1, text);
if (!success)
this.$editor.commands.exec("insertstring", this.$editor, text);
};
}).call(KeyBinding.prototype);
exports.KeyBinding = KeyBinding;
});
@@ -1,69 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var EditSession = require("./../edit_session").EditSession,
Editor = require("./../editor").Editor,
MockRenderer = require("./../test/mockrenderer").MockRenderer,
assert = require("./../test/assertions"),
HashHandler = require('./hash_handler').HashHandler,
keys = require('../lib/keys'),
editor;
function initEditor(docString) {
var doc = new EditSession(docString.split("\n"));
editor = new Editor(new MockRenderer(), doc);
}
module.exports = {
"test: adding a new keyboard handler does not remove the default handler": function() {
initEditor('abc');
var handler = new HashHandler({'del': 'f1'});
editor.keyBinding.setKeyboardHandler(handler);
editor.onCommandKey({}, 0, keys['f1']);
assert.equal('bc', editor.getValue(), "binding of new handler");
editor.onCommandKey({}, 0, keys['delete']);
assert.equal('c', editor.getValue(), "bindings of the old handler should still work");
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,249 +0,0 @@
/* ***** 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";
// If you're developing a new keymapping and want to get an idea what's going
// on, then enable debugging.
var DEBUG = false;
function StateHandler(keymapping) {
this.keymapping = this.$buildKeymappingRegex(keymapping);
}
StateHandler.prototype = {
/*
* Build the RegExp from the keymapping as RegExp can't stored directly
* in the metadata JSON and as the RegExp used to match the keys/buffer
* need to be adapted.
*/
$buildKeymappingRegex: function(keymapping) {
for (var state in keymapping) {
this.$buildBindingsRegex(keymapping[state]);
}
return keymapping;
},
$buildBindingsRegex: function(bindings) {
// Escape a given Regex string.
bindings.forEach(function(binding) {
if (binding.key) {
binding.key = new RegExp('^' + binding.key + '$');
} else if (Array.isArray(binding.regex)) {
if (!('key' in binding))
binding.key = new RegExp('^' + binding.regex[1] + '$');
binding.regex = new RegExp(binding.regex.join('') + '$');
} else if (binding.regex) {
binding.regex = new RegExp(binding.regex + '$');
}
});
},
$composeBuffer: function(data, hashId, key, e) {
// Initialize the data object.
if (data.state == null || data.buffer == null) {
data.state = "start";
data.buffer = "";
}
var keyArray = [];
if (hashId & 1) keyArray.push("ctrl");
if (hashId & 8) keyArray.push("command");
if (hashId & 2) keyArray.push("option");
if (hashId & 4) keyArray.push("shift");
if (key) keyArray.push(key);
var symbolicName = keyArray.join("-");
var bufferToUse = data.buffer + symbolicName;
// Don't add the symbolic name to the key buffer if the alt_ key is
// part of the symbolic name. If it starts with alt_, this means
// that the user hit an alt keycombo and there will be a single,
// new character detected after this event, which then will be
// added to the buffer (e.g. alt_j will result in ∆).
//
// We test for 2 and not for & 2 as we only want to exclude the case where
// the option key is pressed alone.
if (hashId != 2) {
data.buffer = bufferToUse;
}
var bufferObj = {
bufferToUse: bufferToUse,
symbolicName: symbolicName
};
if (e) {
bufferObj.keyIdentifier = e.keyIdentifier;
}
return bufferObj;
},
$find: function(data, buffer, symbolicName, hashId, key, keyIdentifier) {
// Holds the command to execute and the args if a command matched.
var result = {};
// Loop over all the bindings of the keymap until a match is found.
this.keymapping[data.state].some(function(binding) {
var match;
// Check if the key matches.
if (binding.key && !binding.key.test(symbolicName)) {
return false;
}
// Check if the regex matches.
if (binding.regex && !(match = binding.regex.exec(buffer))) {
return false;
}
// Check if the match function matches.
if (binding.match && !binding.match(buffer, hashId, key, symbolicName, keyIdentifier)) {
return false;
}
// Check for disallowed matches.
if (binding.disallowMatches) {
for (var i = 0; i < binding.disallowMatches.length; i++) {
if (!!match[binding.disallowMatches[i]]) {
return false;
}
}
}
// If there is a command to execute, then figure out the
// command and the arguments.
if (binding.exec) {
result.command = binding.exec;
// Build the arguments.
if (binding.params) {
var value;
result.args = {};
binding.params.forEach(function(param) {
if (param.match != null && match != null) {
value = match[param.match] || param.defaultValue;
} else {
value = param.defaultValue;
}
if (param.type === 'number') {
value = parseInt(value);
}
result.args[param.name] = value;
});
}
data.buffer = "";
}
// Handle the 'then' property.
if (binding.then) {
data.state = binding.then;
data.buffer = "";
}
// If no command is set, then execute the "null" fake command.
if (result.command == null) {
result.command = "null";
}
if (DEBUG) {
console.log("KeyboardStateMapper#find", binding);
}
return true;
});
if (result.command) {
return result;
} else {
data.buffer = "";
return false;
}
},
/*
* This function is called by keyBinding.
*/
handleKeyboard: function(data, hashId, key, keyCode, e) {
if (hashId == -1)
hashId = 0
// If we pressed any command key but no other key, then ignore the input.
// Otherwise "shift-" is added to the buffer, and later on "shift-g"
// which results in "shift-shift-g" which doesn't make sense.
if (hashId != 0 && (key == "" || key == String.fromCharCode(0))) {
return null;
}
// Compute the current value of the keyboard input buffer.
var r = this.$composeBuffer(data, hashId, key, e);
var buffer = r.bufferToUse;
var symbolicName = r.symbolicName;
var keyId = r.keyIdentifier;
r = this.$find(data, buffer, symbolicName, hashId, key, keyId);
if (DEBUG) {
console.log("KeyboardStateMapper#match", buffer, symbolicName, r);
}
return r;
}
}
/*
* This is a useful matching function and therefore is defined here so that
* users of KeyboardStateMapper can use it.
*
* @return {Boolean} If no command key (Command|Option|Shift|Ctrl) is pressed, it
* returns true. If the only the Shift key is pressed + a character
* true is returned as well. Otherwise, false is returned.
* Summing up, the function returns true whenever the user typed
* a normal character on the keyboard and no shortcut.
*/
exports.matchCharacterOnly = function(buffer, hashId, key, symbolicName) {
// If no command keys are pressed, then catch the input.
if (hashId == 0) {
return true;
}
// If only the shift key is pressed and a character key, then
// catch that input as well.
else if ((hashId == 4) && key.length == 1) {
return true;
}
// Otherwise, we let the input got through.
else {
return false;
}
};
exports.StateHandler = StateHandler;
});
@@ -1,82 +0,0 @@
/* ***** 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 HashHandler = require("./hash_handler").HashHandler;
exports.handler = new HashHandler();
[{
bindKey: "Shift-Tab|Tab",
command: "passKeysToBrowser"
}, {
bindKey: {win: "Ctrl-L", mac: "Cmd-L"},
command: "passKeysToBrowser"
}, {
bindKey: {win: "Ctrl-G", mac: "Cmd-G"},
command: "gotoline"
}, {
bindKey: {win: "Ctrl-T|Ctrl-Shift-T", mac: "Cmd-T|Cmd-Shift-T"},
command: "passKeysToBrowser"
}, {
bindKey: {win: "Ctrl-G", mac: "Cmd-G"},
command: "passKeysToBrowser"
}, {
bindKey: {win: "Ctrl-G", mac: "Cmd-G"},
command: "passKeysToBrowser"
}, {
name: "golineup",
bindKey: {win: null, mac: "Ctrl-P"},
}, {
name: "golinedown",
bindKey: {win: null, mac: "Ctrl-N"},
}, {
name: "gotoleft",
bindKey: {win: null, mac: "Ctrl-B"},
}, {
name: "gotoright",
bindKey: {win: null, mac: "Ctrl-F"},
}, {
name: "gotolineend",
bindKey: {win: null, mac: "Ctrl-E"},
}, {
name: "gotolinestart",
bindKey: {win: null, mac: "Ctrl-A"},
}
].forEach(function(k) {
var bindKey = k.bindKey;
if (typeof bindKey == "object")
bindKey = bindKey[exports.handler.platform];
exports.handler.bindKey(bindKey, k.command);
});
exports.handler.$id = "ace/keyboard/textarea";
});
@@ -1,509 +0,0 @@
/* ***** 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 event = require("../lib/event");
var useragent = require("../lib/useragent");
var dom = require("../lib/dom");
var lang = require("../lib/lang");
var BROKEN_SETDATA = useragent.isChrome < 18;
var USE_IE_MIME_TYPE = useragent.isIE;
var TextInput = function(parentNode, host) {
var text = dom.createElement("textarea");
text.className = "ace_text-input";
if (useragent.isTouchPad)
text.setAttribute("x-palm-disable-auto-cap", true);
text.setAttribute("wrap", "off");
text.setAttribute("autocorrect", "off");
text.setAttribute("autocapitalize", "off");
text.setAttribute("spellcheck", false);
text.style.opacity = "0";
if (useragent.isOldIE) text.style.top = "-100px";
parentNode.insertBefore(text, parentNode.firstChild);
var PLACEHOLDER = "\x01\x01";
var copied = false;
var pasted = false;
var inComposition = false;
var tempStyle = '';
var isSelectionEmpty = true;
// FOCUS
// ie9 throws error if document.activeElement is accessed too soon
try { var isFocused = document.activeElement === text; } catch(e) {}
event.addListener(text, "blur", function(e) {
host.onBlur(e);
isFocused = false;
});
event.addListener(text, "focus", function(e) {
isFocused = true;
host.onFocus(e);
resetSelection();
});
this.focus = function() {
text.style.position = "fixed";
text.style.top = "-10000000px";
text.focus();
setTimeout(function() {
text.style.position = "";
}, 0);
};
this.blur = function() { text.blur(); };
this.isFocused = function() {
return isFocused;
};
// modifying selection of blured textarea can focus it (chrome mac/linux)
var syncSelection = lang.delayedCall(function() {
isFocused && resetSelection(isSelectionEmpty);
});
var syncValue = lang.delayedCall(function() {
if (!inComposition) {
text.value = PLACEHOLDER;
isFocused && resetSelection();
}
});
function resetSelection(isEmpty) {
if (inComposition)
return;
// this prevents infinite recursion on safari 8
// see https://github.com/ajaxorg/ace/issues/2114
inComposition = true;
if (inputHandler) {
selectionStart = 0;
selectionEnd = isEmpty ? 0 : text.value.length - 1;
} else {
var selectionStart = isEmpty ? 2 : 1;
var selectionEnd = 2;
}
// on firefox this throws if textarea is hidden
try {
text.setSelectionRange(selectionStart, selectionEnd);
} catch(e){}
inComposition = false;
}
function resetValue() {
if (inComposition)
return;
text.value = PLACEHOLDER;
//http://code.google.com/p/chromium/issues/detail?id=76516
if (useragent.isWebKit)
syncValue.schedule();
}
useragent.isWebKit || host.addEventListener('changeSelection', function() {
if (host.selection.isEmpty() != isSelectionEmpty) {
isSelectionEmpty = !isSelectionEmpty;
syncSelection.schedule();
}
});
resetValue();
if (isFocused)
host.onFocus();
var isAllSelected = function(text) {
return text.selectionStart === 0 && text.selectionEnd === text.value.length;
};
// IE8 does not support setSelectionRange
if (!text.setSelectionRange && text.createTextRange) {
text.setSelectionRange = function(selectionStart, selectionEnd) {
var range = this.createTextRange();
range.collapse(true);
range.moveStart('character', selectionStart);
range.moveEnd('character', selectionEnd);
range.select();
};
isAllSelected = function(text) {
try {
var range = text.ownerDocument.selection.createRange();
}catch(e) {}
if (!range || range.parentElement() != text) return false;
return range.text == text.value;
}
}
if (useragent.isOldIE) {
var inPropertyChange = false;
var onPropertyChange = function(e){
if (inPropertyChange)
return;
var data = text.value;
if (inComposition || !data || data == PLACEHOLDER)
return;
// can happen either after delete or during insert operation
if (e && data == PLACEHOLDER[0])
return syncProperty.schedule();
sendText(data);
// ie8 calls propertychange handlers synchronously!
inPropertyChange = true;
resetValue();
inPropertyChange = false;
};
var syncProperty = lang.delayedCall(onPropertyChange);
event.addListener(text, "propertychange", onPropertyChange);
var keytable = { 13:1, 27:1 };
event.addListener(text, "keyup", function (e) {
if (inComposition && (!text.value || keytable[e.keyCode]))
setTimeout(onCompositionEnd, 0);
if ((text.value.charCodeAt(0)||0) < 129) {
return syncProperty.call();
}
inComposition ? onCompositionUpdate() : onCompositionStart();
});
// when user presses backspace after focusing the editor
// propertychange isn't called for the next character
event.addListener(text, "keydown", function (e) {
syncProperty.schedule(50);
});
}
var onSelect = function(e) {
if (copied) {
copied = false;
} else if (isAllSelected(text)) {
host.selectAll();
resetSelection();
} else if (inputHandler) {
resetSelection(host.selection.isEmpty());
}
};
var inputHandler = null;
this.setInputHandler = function(cb) {inputHandler = cb};
this.getInputHandler = function() {return inputHandler};
var afterContextMenu = false;
var sendText = function(data) {
if (inputHandler) {
data = inputHandler(data);
inputHandler = null;
}
if (pasted) {
resetSelection();
if (data)
host.onPaste(data);
pasted = false;
} else if (data == PLACEHOLDER.charAt(0)) {
if (afterContextMenu)
host.execCommand("del", {source: "ace"});
else // some versions of android do not fire keydown when pressing backspace
host.execCommand("backspace", {source: "ace"});
} else {
if (data.substring(0, 2) == PLACEHOLDER)
data = data.substr(2);
else if (data.charAt(0) == PLACEHOLDER.charAt(0))
data = data.substr(1);
else if (data.charAt(data.length - 1) == PLACEHOLDER.charAt(0))
data = data.slice(0, -1);
// can happen if undo in textarea isn't stopped
if (data.charAt(data.length - 1) == PLACEHOLDER.charAt(0))
data = data.slice(0, -1);
if (data)
host.onTextInput(data);
}
if (afterContextMenu)
afterContextMenu = false;
};
var onInput = function(e) {
// console.log("onInput", inComposition)
if (inComposition)
return;
var data = text.value;
sendText(data);
resetValue();
};
var handleClipboardData = function(e, data) {
var clipboardData = e.clipboardData || window.clipboardData;
if (!clipboardData || BROKEN_SETDATA)
return;
// using "Text" doesn't work on old webkit but ie needs it
// TODO are there other browsers that require "Text"?
var mime = USE_IE_MIME_TYPE ? "Text" : "text/plain";
if (data) {
// Safari 5 has clipboardData object, but does not handle setData()
return clipboardData.setData(mime, data) !== false;
} else {
return clipboardData.getData(mime);
}
};
var doCopy = function(e, isCut) {
var data = host.getCopyText();
if (!data)
return event.preventDefault(e);
if (handleClipboardData(e, data)) {
isCut ? host.onCut() : host.onCopy();
event.preventDefault(e);
} else {
copied = true;
text.value = data;
text.select();
setTimeout(function(){
copied = false;
resetValue();
resetSelection();
isCut ? host.onCut() : host.onCopy();
});
}
};
var onCut = function(e) {
doCopy(e, true);
};
var onCopy = function(e) {
doCopy(e, false);
};
var onPaste = function(e) {
var data = handleClipboardData(e);
if (typeof data == "string") {
if (data)
host.onPaste(data);
if (useragent.isIE)
setTimeout(resetSelection);
event.preventDefault(e);
}
else {
text.value = "";
pasted = true;
}
};
event.addCommandKeyListener(text, host.onCommandKey.bind(host));
event.addListener(text, "select", onSelect);
event.addListener(text, "input", onInput);
event.addListener(text, "cut", onCut);
event.addListener(text, "copy", onCopy);
event.addListener(text, "paste", onPaste);
// Opera has no clipboard events
if (!('oncut' in text) || !('oncopy' in text) || !('onpaste' in text)){
event.addListener(parentNode, "keydown", function(e) {
if ((useragent.isMac && !e.metaKey) || !e.ctrlKey)
return;
switch (e.keyCode) {
case 67:
onCopy(e);
break;
case 86:
onPaste(e);
break;
case 88:
onCut(e);
break;
}
});
}
// COMPOSITION
var onCompositionStart = function(e) {
if (inComposition || !host.onCompositionStart || host.$readOnly)
return;
// console.log("onCompositionStart", inComposition)
inComposition = {};
host.onCompositionStart();
setTimeout(onCompositionUpdate, 0);
host.on("mousedown", onCompositionEnd);
if (!host.selection.isEmpty()) {
host.insert("");
host.session.markUndoGroup();
host.selection.clearSelection();
}
host.session.markUndoGroup();
};
var onCompositionUpdate = function() {
// console.log("onCompositionUpdate", inComposition && JSON.stringify(text.value))
if (!inComposition || !host.onCompositionUpdate || host.$readOnly)
return;
var val = text.value.replace(/\x01/g, "");
if (inComposition.lastValue === val) return;
host.onCompositionUpdate(val);
if (inComposition.lastValue)
host.undo();
inComposition.lastValue = val;
if (inComposition.lastValue) {
var r = host.selection.getRange();
host.insert(inComposition.lastValue);
host.session.markUndoGroup();
inComposition.range = host.selection.getRange();
host.selection.setRange(r);
host.selection.clearSelection();
}
};
var onCompositionEnd = function(e) {
if (!host.onCompositionEnd || host.$readOnly) return;
// console.log("onCompositionEnd", inComposition &&inComposition.lastValue)
var c = inComposition;
inComposition = false;
var timer = setTimeout(function() {
timer = null;
var str = text.value.replace(/\x01/g, "");
// console.log(str, c.lastValue)
if (inComposition)
return;
else if (str == c.lastValue)
resetValue();
else if (!c.lastValue && str) {
resetValue();
sendText(str);
}
});
inputHandler = function compositionInputHandler(str) {
// console.log("onCompositionEnd", str, c.lastValue)
if (timer)
clearTimeout(timer);
str = str.replace(/\x01/g, "");
if (str == c.lastValue)
return "";
if (c.lastValue && timer)
host.undo();
return str;
};
host.onCompositionEnd();
host.removeListener("mousedown", onCompositionEnd);
if (e.type == "compositionend" && c.range) {
host.selection.setRange(c.range);
}
};
var syncComposition = lang.delayedCall(onCompositionUpdate, 50);
event.addListener(text, "compositionstart", onCompositionStart);
if (useragent.isGecko) {
event.addListener(text, "text", function(){syncComposition.schedule()});
} else {
event.addListener(text, "keyup", function(){syncComposition.schedule()});
event.addListener(text, "keydown", function(){syncComposition.schedule()});
}
event.addListener(text, "compositionend", onCompositionEnd);
this.getElement = function() {
return text;
};
this.setReadOnly = function(readOnly) {
text.readOnly = readOnly;
};
this.onContextMenu = function(e) {
afterContextMenu = true;
resetSelection(host.selection.isEmpty());
host._emit("nativecontextmenu", {target: host, domEvent: e});
this.moveToMouse(e, true);
};
this.moveToMouse = function(e, bringToFront) {
if (!bringToFront && useragent.isOldIE)
return;
if (!tempStyle)
tempStyle = text.style.cssText;
text.style.cssText = (bringToFront ? "z-index:100000;" : "")
+ "height:" + text.style.height + ";"
+ (useragent.isIE ? "opacity:0.1;" : "");
var rect = host.container.getBoundingClientRect();
var style = dom.computedStyle(host.container);
var top = rect.top + (parseInt(style.borderTopWidth) || 0);
var left = rect.left + (parseInt(rect.borderLeftWidth) || 0);
var maxTop = rect.bottom - top - text.clientHeight -2;
var move = function(e) {
text.style.left = e.clientX - left - 2 + "px";
text.style.top = Math.min(e.clientY - top - 2, maxTop) + "px";
};
move(e);
if (e.type != "mousedown")
return;
if (host.renderer.$keepTextAreaAtCursor)
host.renderer.$keepTextAreaAtCursor = null;
// on windows context menu is opened after mouseup
if (useragent.isWin && !useragent.isOldIE)
event.capture(host.container, move, onContextMenuClose);
};
this.onContextMenuClose = onContextMenuClose;
var closeTimeout;
function onContextMenuClose() {
clearTimeout(closeTimeout)
closeTimeout = setTimeout(function () {
if (tempStyle) {
text.style.cssText = tempStyle;
tempStyle = '';
}
if (host.renderer.$keepTextAreaAtCursor == null) {
host.renderer.$keepTextAreaAtCursor = true;
host.renderer.$moveTextAreaToCursor();
}
}, useragent.isOldIE ? 200 : 0);
}
var onContextMenu = function(e) {
host.textInput.onContextMenu(e);
onContextMenuClose();
};
event.addListener(host.renderer.scroller, "contextmenu", onContextMenu);
event.addListener(text, "contextmenu", onContextMenu);
};
exports.TextInput = TextInput;
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,240 +0,0 @@
/* ***** 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 dom = require("../lib/dom");
var IE8;
var Cursor = function(parentEl) {
this.element = dom.createElement("div");
this.element.className = "ace_layer ace_cursor-layer";
parentEl.appendChild(this.element);
if (IE8 === undefined)
IE8 = "opacity" in this.element;
this.isVisible = false;
this.isBlinking = true;
this.blinkInterval = 1000;
this.smoothBlinking = false;
this.cursors = [];
this.cursor = this.addCursor();
dom.addCssClass(this.element, "ace_hidden-cursors");
this.$updateCursors = this.$updateVisibility.bind(this);
};
(function() {
this.$updateVisibility = function(val) {
var cursors = this.cursors;
for (var i = cursors.length; i--; )
cursors[i].style.visibility = val ? "" : "hidden";
};
this.$updateOpacity = function(val) {
var cursors = this.cursors;
for (var i = cursors.length; i--; )
cursors[i].style.opacity = val ? "" : "0";
};
this.$padding = 0;
this.setPadding = function(padding) {
this.$padding = padding;
};
this.setSession = function(session) {
this.session = session;
};
this.setBlinking = function(blinking) {
if (blinking != this.isBlinking){
this.isBlinking = blinking;
this.restartTimer();
}
};
this.setBlinkInterval = function(blinkInterval) {
if (blinkInterval != this.blinkInterval){
this.blinkInterval = blinkInterval;
this.restartTimer();
}
};
this.setSmoothBlinking = function(smoothBlinking) {
if (smoothBlinking != this.smoothBlinking && !IE8) {
this.smoothBlinking = smoothBlinking;
dom.setCssClass(this.element, "ace_smooth-blinking", smoothBlinking);
this.$updateCursors(true);
this.$updateCursors = (smoothBlinking
? this.$updateOpacity
: this.$updateVisibility).bind(this);
this.restartTimer();
}
};
this.addCursor = function() {
var el = dom.createElement("div");
el.className = "ace_cursor";
this.element.appendChild(el);
this.cursors.push(el);
return el;
};
this.removeCursor = function() {
if (this.cursors.length > 1) {
var el = this.cursors.pop();
el.parentNode.removeChild(el);
return el;
}
};
this.hideCursor = function() {
this.isVisible = false;
dom.addCssClass(this.element, "ace_hidden-cursors");
this.restartTimer();
};
this.showCursor = function() {
this.isVisible = true;
dom.removeCssClass(this.element, "ace_hidden-cursors");
this.restartTimer();
};
this.restartTimer = function() {
var update = this.$updateCursors;
clearInterval(this.intervalId);
clearTimeout(this.timeoutId);
if (this.smoothBlinking) {
dom.removeCssClass(this.element, "ace_smooth-blinking");
}
update(true);
if (!this.isBlinking || !this.blinkInterval || !this.isVisible)
return;
if (this.smoothBlinking) {
setTimeout(function(){
dom.addCssClass(this.element, "ace_smooth-blinking");
}.bind(this));
}
var blink = function(){
this.timeoutId = setTimeout(function() {
update(false);
}, 0.6 * this.blinkInterval);
}.bind(this);
this.intervalId = setInterval(function() {
update(true);
blink();
}, this.blinkInterval);
blink();
};
this.getPixelPosition = function(position, onScreen) {
if (!this.config || !this.session)
return {left : 0, top : 0};
if (!position)
position = this.session.selection.getCursor();
var pos = this.session.documentToScreenPosition(position);
var cursorLeft = this.$padding + pos.column * this.config.characterWidth;
var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) *
this.config.lineHeight;
return {left : cursorLeft, top : cursorTop};
};
this.update = function(config) {
this.config = config;
var selections = this.session.$selectionMarkers;
var i = 0, cursorIndex = 0;
if (selections === undefined || selections.length === 0){
selections = [{cursor: null}];
}
for (var i = 0, n = selections.length; i < n; i++) {
var pixelPos = this.getPixelPosition(selections[i].cursor, true);
if ((pixelPos.top > config.height + config.offset ||
pixelPos.top < 0) && i > 1) {
continue;
}
var style = (this.cursors[cursorIndex++] || this.addCursor()).style;
if (!this.drawCursor) {
style.left = pixelPos.left + "px";
style.top = pixelPos.top + "px";
style.width = config.characterWidth + "px";
style.height = config.lineHeight + "px";
} else {
this.drawCursor(style, pixelPos, config, selections[i], this.session);
}
}
while (this.cursors.length > cursorIndex)
this.removeCursor();
var overwrite = this.session.getOverwrite();
this.$setOverwrite(overwrite);
// cache for textarea and gutter highlight
this.$pixelPos = pixelPos;
this.restartTimer();
};
this.drawCursor = null;
this.$setOverwrite = function(overwrite) {
if (overwrite != this.overwrite) {
this.overwrite = overwrite;
if (overwrite)
dom.addCssClass(this.element, "ace_overwrite-cursors");
else
dom.removeCssClass(this.element, "ace_overwrite-cursors");
}
};
this.destroy = function() {
clearInterval(this.intervalId);
clearTimeout(this.timeoutId);
};
}).call(Cursor.prototype);
exports.Cursor = Cursor;
});
@@ -1,176 +0,0 @@
/* ***** 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) {
var oop = require("../lib/oop");
var dom = require("../lib/dom");
var lang = require("../lib/lang");
var useragent = require("../lib/useragent");
var EventEmitter = require("../lib/event_emitter").EventEmitter;
var CHAR_COUNT = 0;
var FontMetrics = exports.FontMetrics = function(parentEl, interval) {
this.el = dom.createElement("div");
this.$setMeasureNodeStyles(this.el.style, true);
this.$main = dom.createElement("div");
this.$setMeasureNodeStyles(this.$main.style);
this.$measureNode = dom.createElement("div");
this.$setMeasureNodeStyles(this.$measureNode.style);
this.el.appendChild(this.$main);
this.el.appendChild(this.$measureNode);
parentEl.appendChild(this.el);
if (!CHAR_COUNT)
this.$testFractionalRect();
this.$measureNode.innerHTML = lang.stringRepeat("X", CHAR_COUNT);
this.$characterSize = {width: 0, height: 0};
this.checkForSizeChanges();
};
(function() {
oop.implement(this, EventEmitter);
this.$characterSize = {width: 0, height: 0};
this.$testFractionalRect = function() {
var el = dom.createElement("div");
this.$setMeasureNodeStyles(el.style);
el.style.width = "0.2px";
document.documentElement.appendChild(el);
var w = el.getBoundingClientRect().width;
if (w > 0 && w < 1)
CHAR_COUNT = 50;
else
CHAR_COUNT = 100;
el.parentNode.removeChild(el);
};
this.$setMeasureNodeStyles = function(style, isRoot) {
style.width = style.height = "auto";
style.left = style.top = "0px";
style.visibility = "hidden";
style.position = "absolute";
style.whiteSpace = "pre";
if (useragent.isIE < 8) {
style["font-family"] = "inherit";
} else {
style.font = "inherit";
}
style.overflow = isRoot ? "hidden" : "visible";
};
this.checkForSizeChanges = function() {
var size = this.$measureSizes();
if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) {
this.$measureNode.style.fontWeight = "bold";
var boldSize = this.$measureSizes();
this.$measureNode.style.fontWeight = "";
this.$characterSize = size;
this.charSizes = Object.create(null);
this.allowBoldFonts = boldSize && boldSize.width === size.width && boldSize.height === size.height;
this._emit("changeCharacterSize", {data: size});
}
};
this.$pollSizeChanges = function() {
if (this.$pollSizeChangesTimer)
return this.$pollSizeChangesTimer;
var self = this;
return this.$pollSizeChangesTimer = setInterval(function() {
self.checkForSizeChanges();
}, 500);
};
this.setPolling = function(val) {
if (val) {
this.$pollSizeChanges();
} else {
if (this.$pollSizeChangesTimer)
this.$pollSizeChangesTimer;
}
};
this.$measureSizes = function() {
if (CHAR_COUNT === 50) {
var rect = null;
try {
rect = this.$measureNode.getBoundingClientRect();
} catch(e) {
rect = {width: 0, height:0 };
};
var size = {
height: rect.height,
width: rect.width / CHAR_COUNT
};
} else {
var size = {
height: this.$measureNode.clientHeight,
width: this.$measureNode.clientWidth / CHAR_COUNT
};
}
// Size and width can be null if the editor is not visible or
// detached from the document
if (size.width === 0 || size.height === 0)
return null;
return size;
};
this.$measureCharWidth = function(ch) {
this.$main.innerHTML = lang.stringRepeat(ch, CHAR_COUNT);
var rect = this.$main.getBoundingClientRect();
return rect.width / CHAR_COUNT;
};
this.getCharacterWidth = function(ch) {
var w = this.charSizes[ch];
if (w === undefined) {
this.charSizes[ch] = this.$measureCharWidth(ch) / this.$characterSize.width;
}
return w;
};
this.destroy = function() {
clearInterval(this.$pollSizeChangesTimer);
if (this.el && this.el.parentNode)
this.el.parentNode.removeChild(this.el);
};
}).call(FontMetrics.prototype);
});
@@ -1,287 +0,0 @@
/* ***** 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 dom = require("../lib/dom");
var oop = require("../lib/oop");
var lang = require("../lib/lang");
var EventEmitter = require("../lib/event_emitter").EventEmitter;
var Gutter = function(parentEl) {
this.element = dom.createElement("div");
this.element.className = "ace_layer ace_gutter-layer";
parentEl.appendChild(this.element);
this.setShowFoldWidgets(this.$showFoldWidgets);
this.gutterWidth = 0;
this.$annotations = [];
this.$updateAnnotations = this.$updateAnnotations.bind(this);
this.$cells = [];
};
(function() {
oop.implement(this, EventEmitter);
this.setSession = function(session) {
if (this.session)
this.session.removeEventListener("change", this.$updateAnnotations);
this.session = session;
if (session)
session.on("change", this.$updateAnnotations);
};
this.addGutterDecoration = function(row, className){
if (window.console)
console.warn && console.warn("deprecated use session.addGutterDecoration");
this.session.addGutterDecoration(row, className);
};
this.removeGutterDecoration = function(row, className){
if (window.console)
console.warn && console.warn("deprecated use session.removeGutterDecoration");
this.session.removeGutterDecoration(row, className);
};
this.setAnnotations = function(annotations) {
// iterate over sparse array
this.$annotations = [];
for (var i = 0; i < annotations.length; i++) {
var annotation = annotations[i];
var row = annotation.row;
var rowInfo = this.$annotations[row];
if (!rowInfo)
rowInfo = this.$annotations[row] = {text: []};
var annoText = annotation.text;
annoText = annoText ? lang.escapeHTML(annoText) : annotation.html || "";
if (rowInfo.text.indexOf(annoText) === -1)
rowInfo.text.push(annoText);
var type = annotation.type;
if (type == "error")
rowInfo.className = " ace_error";
else if (type == "warning" && rowInfo.className != " ace_error")
rowInfo.className = " ace_warning";
else if (type == "info" && (!rowInfo.className))
rowInfo.className = " ace_info";
}
};
this.$updateAnnotations = function (e) {
if (!this.$annotations.length)
return;
var delta = e.data;
var range = delta.range;
var firstRow = range.start.row;
var len = range.end.row - firstRow;
if (len === 0) {
// do nothing
} else if (delta.action == "removeText" || delta.action == "removeLines") {
this.$annotations.splice(firstRow, len + 1, null);
} else {
var args = new Array(len + 1);
args.unshift(firstRow, 1);
this.$annotations.splice.apply(this.$annotations, args);
}
};
this.update = function(config) {
var session = this.session;
var firstRow = config.firstRow;
var lastRow = Math.min(config.lastRow + config.gutterOffset, // needed to compensate for hor scollbar
session.getLength() - 1);
var fold = session.getNextFoldLine(firstRow);
var foldStart = fold ? fold.start.row : Infinity;
var foldWidgets = this.$showFoldWidgets && session.foldWidgets;
var breakpoints = session.$breakpoints;
var decorations = session.$decorations;
var firstLineNumber = session.$firstLineNumber;
var lastLineNumber = 0;
var gutterRenderer = session.gutterRenderer || this.$renderer;
var cell = null;
var index = -1;
var row = firstRow;
while (true) {
if (row > foldStart) {
row = fold.end.row + 1;
fold = session.getNextFoldLine(row, fold);
foldStart = fold ? fold.start.row : Infinity;
}
if (row > lastRow) {
while (this.$cells.length > index + 1) {
cell = this.$cells.pop();
this.element.removeChild(cell.element);
}
break;
}
cell = this.$cells[++index];
if (!cell) {
cell = {element: null, textNode: null, foldWidget: null};
cell.element = dom.createElement("div");
cell.textNode = document.createTextNode('');
cell.element.appendChild(cell.textNode);
this.element.appendChild(cell.element);
this.$cells[index] = cell;
}
var className = "ace_gutter-cell ";
if (breakpoints[row])
className += breakpoints[row];
if (decorations[row])
className += decorations[row];
if (this.$annotations[row])
className += this.$annotations[row].className;
if (cell.element.className != className)
cell.element.className = className;
var height = session.getRowLength(row) * config.lineHeight + "px";
if (height != cell.element.style.height)
cell.element.style.height = height;
if (foldWidgets) {
var c = foldWidgets[row];
// check if cached value is invalidated and we need to recompute
if (c == null)
c = foldWidgets[row] = session.getFoldWidget(row);
}
if (c) {
if (!cell.foldWidget) {
cell.foldWidget = dom.createElement("span");
cell.element.appendChild(cell.foldWidget);
}
var className = "ace_fold-widget ace_" + c;
if (c == "start" && row == foldStart && row < fold.end.row)
className += " ace_closed";
else
className += " ace_open";
if (cell.foldWidget.className != className)
cell.foldWidget.className = className;
var height = config.lineHeight + "px";
if (cell.foldWidget.style.height != height)
cell.foldWidget.style.height = height;
} else {
if (cell.foldWidget) {
cell.element.removeChild(cell.foldWidget);
cell.foldWidget = null;
}
}
var text = lastLineNumber = gutterRenderer
? gutterRenderer.getText(session, row)
: row + firstLineNumber;
if (text != cell.textNode.data)
cell.textNode.data = text;
row++;
}
this.element.style.height = config.minHeight + "px";
if (this.$fixedWidth || session.$useWrapMode)
lastLineNumber = session.getLength() + firstLineNumber;
var gutterWidth = gutterRenderer
? gutterRenderer.getWidth(session, lastLineNumber, config)
: lastLineNumber.toString().length * config.characterWidth;
var padding = this.$padding || this.$computePadding();
gutterWidth += padding.left + padding.right;
if (gutterWidth !== this.gutterWidth && !isNaN(gutterWidth)) {
this.gutterWidth = gutterWidth;
this.element.style.width = Math.ceil(this.gutterWidth) + "px";
this._emit("changeGutterWidth", gutterWidth);
}
};
this.$fixedWidth = false;
this.$showLineNumbers = true;
this.$renderer = "";
this.setShowLineNumbers = function(show) {
this.$renderer = !show && {
getWidth: function() {return ""},
getText: function() {return ""}
};
};
this.getShowLineNumbers = function() {
return this.$showLineNumbers;
};
this.$showFoldWidgets = true;
this.setShowFoldWidgets = function(show) {
if (show)
dom.addCssClass(this.element, "ace_folding-enabled");
else
dom.removeCssClass(this.element, "ace_folding-enabled");
this.$showFoldWidgets = show;
this.$padding = null;
};
this.getShowFoldWidgets = function() {
return this.$showFoldWidgets;
};
this.$computePadding = function() {
if (!this.element.firstChild)
return {left: 0, right: 0};
var style = dom.computedStyle(this.element.firstChild);
this.$padding = {};
this.$padding.left = parseInt(style.paddingLeft) + 1 || 0;
this.$padding.right = parseInt(style.paddingRight) || 0;
return this.$padding;
};
this.getRegion = function(point) {
var padding = this.$padding || this.$computePadding();
var rect = this.element.getBoundingClientRect();
if (point.x < padding.left + rect.left)
return "markers";
if (this.$showFoldWidgets && point.x > rect.right - padding.right)
return "foldWidgets";
};
}).call(Gutter.prototype);
exports.Gutter = Gutter;
});
@@ -1,218 +0,0 @@
/* ***** 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 Range = require("../range").Range;
var dom = require("../lib/dom");
var Marker = function(parentEl) {
this.element = dom.createElement("div");
this.element.className = "ace_layer ace_marker-layer";
parentEl.appendChild(this.element);
};
(function() {
this.$padding = 0;
this.setPadding = function(padding) {
this.$padding = padding;
};
this.setSession = function(session) {
this.session = session;
};
this.setMarkers = function(markers) {
this.markers = markers;
};
this.update = function(config) {
var config = config || this.config;
if (!config)
return;
this.config = config;
var html = [];
for (var key in this.markers) {
var marker = this.markers[key];
if (!marker.range) {
marker.update(html, this, this.session, config);
continue;
}
var range = marker.range.clipRows(config.firstRow, config.lastRow);
if (range.isEmpty()) continue;
range = range.toScreenRange(this.session);
if (marker.renderer) {
var top = this.$getTop(range.start.row, config);
var left = this.$padding + range.start.column * config.characterWidth;
marker.renderer(html, range, left, top, config);
} else if (marker.type == "fullLine") {
this.drawFullLineMarker(html, range, marker.clazz, config);
} else if (marker.type == "screenLine") {
this.drawScreenLineMarker(html, range, marker.clazz, config);
} else if (range.isMultiLine()) {
if (marker.type == "text")
this.drawTextMarker(html, range, marker.clazz, config);
else
this.drawMultiLineMarker(html, range, marker.clazz, config);
} else {
this.drawSingleLineMarker(html, range, marker.clazz + " ace_start", config);
}
}
this.element.innerHTML = html.join("");
};
this.$getTop = function(row, layerConfig) {
return (row - layerConfig.firstRowScreen) * layerConfig.lineHeight;
};
// 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);
}
};
// Draws a multi line marker, where lines span the full width
this.drawMultiLineMarker = function(stringBuilder, range, clazz, config, extraStyle) {
// from selection start to the end of the line
var padding = this.$padding;
var height = config.lineHeight;
var top = this.$getTop(range.start.row, config);
var left = padding + range.start.column * config.characterWidth;
extraStyle = extraStyle || "";
stringBuilder.push(
"<div class='", clazz, " ace_start' style='",
"height:", height, "px;",
"right:0;",
"top:", top, "px;",
"left:", left, "px;", extraStyle, "'></div>"
);
// from start of the last line to the selection end
top = this.$getTop(range.end.row, config);
var width = range.end.column * config.characterWidth;
stringBuilder.push(
"<div class='", clazz, "' style='",
"height:", height, "px;",
"width:", width, "px;",
"top:", top, "px;",
"left:", padding, "px;", extraStyle, "'></div>"
);
// all the complete lines
height = (range.end.row - range.start.row - 1) * config.lineHeight;
if (height < 0)
return;
top = this.$getTop(range.start.row + 1, config);
stringBuilder.push(
"<div class='", clazz, "' style='",
"height:", height, "px;",
"right:0;",
"top:", top, "px;",
"left:", padding, "px;", extraStyle, "'></div>"
);
};
// Draws a marker which covers part or whole width of a single screen line
this.drawSingleLineMarker = function(stringBuilder, range, clazz, config, extraLength, extraStyle) {
var height = config.lineHeight;
var width = (range.end.column + (extraLength || 0) - range.start.column) * config.characterWidth;
var top = this.$getTop(range.start.row, config);
var left = this.$padding + range.start.column * config.characterWidth;
stringBuilder.push(
"<div class='", clazz, "' style='",
"height:", height, "px;",
"width:", width, "px;",
"top:", top, "px;",
"left:", left, "px;", extraStyle || "", "'></div>"
);
};
this.drawFullLineMarker = function(stringBuilder, range, clazz, config, extraStyle) {
var top = this.$getTop(range.start.row, config);
var height = config.lineHeight;
if (range.start.row != range.end.row)
height += this.$getTop(range.end.row, config) - top;
stringBuilder.push(
"<div class='", clazz, "' style='",
"height:", height, "px;",
"top:", top, "px;",
"left:0;right:0;", extraStyle || "", "'></div>"
);
};
this.drawScreenLineMarker = function(stringBuilder, range, clazz, config, extraStyle) {
var top = this.$getTop(range.start.row, config);
var height = config.lineHeight;
stringBuilder.push(
"<div class='", clazz, "' style='",
"height:", height, "px;",
"top:", top, "px;",
"left:0;right:0;", extraStyle || "", "'></div>"
);
};
}).call(Marker.prototype);
exports.Marker = Marker;
});
@@ -1,575 +0,0 @@
/* ***** 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 dom = require("../lib/dom");
var lang = require("../lib/lang");
var useragent = require("../lib/useragent");
var EventEmitter = require("../lib/event_emitter").EventEmitter;
var Text = function(parentEl) {
this.element = dom.createElement("div");
this.element.className = "ace_layer ace_text-layer";
parentEl.appendChild(this.element);
this.$updateEolChar = this.$updateEolChar.bind(this);
};
(function() {
oop.implement(this, EventEmitter);
this.EOF_CHAR = "\xB6";
this.EOL_CHAR_LF = "\xAC";
this.EOL_CHAR_CRLF = "\xa4";
this.EOL_CHAR = this.EOL_CHAR_LF;
this.TAB_CHAR = "\u2192"; //"\u21E5";
this.SPACE_CHAR = "\xB7";
this.$padding = 0;
this.$updateEolChar = function() {
var EOL_CHAR = this.session.doc.getNewLineCharacter() == "\n"
? this.EOL_CHAR_LF
: this.EOL_CHAR_CRLF;
if (this.EOL_CHAR != EOL_CHAR) {
this.EOL_CHAR = EOL_CHAR;
return true;
}
}
this.setPadding = function(padding) {
this.$padding = padding;
this.element.style.padding = "0 " + padding + "px";
};
this.getLineHeight = function() {
return this.$fontMetrics.$characterSize.height || 0;
};
this.getCharacterWidth = function() {
return this.$fontMetrics.$characterSize.width || 0;
};
this.$setFontMetrics = function(measure) {
this.$fontMetrics = measure;
this.$fontMetrics.on("changeCharacterSize", function(e) {
this._signal("changeCharacterSize", e);
}.bind(this));
this.$pollSizeChanges();
}
this.checkForSizeChanges = function() {
this.$fontMetrics.checkForSizeChanges();
};
this.$pollSizeChanges = function() {
return this.$pollSizeChangesTimer = this.$fontMetrics.$pollSizeChanges();
};
this.setSession = function(session) {
this.session = session;
if (session)
this.$computeTabString();
};
this.showInvisibles = false;
this.setShowInvisibles = function(showInvisibles) {
if (this.showInvisibles == showInvisibles)
return false;
this.showInvisibles = showInvisibles;
this.$computeTabString();
return true;
};
this.displayIndentGuides = true;
this.setDisplayIndentGuides = function(display) {
if (this.displayIndentGuides == display)
return false;
this.displayIndentGuides = display;
this.$computeTabString();
return true;
};
this.$tabStrings = [];
this.onChangeTabSize =
this.$computeTabString = function() {
var tabSize = this.session.getTabSize();
this.tabSize = tabSize;
var tabStr = this.$tabStrings = [0];
for (var i = 1; i < tabSize + 1; i++) {
if (this.showInvisibles) {
tabStr.push("<span class='ace_invisible ace_invisible_tab'>"
+ this.TAB_CHAR
+ lang.stringRepeat("\xa0", i - 1)
+ "</span>");
} else {
tabStr.push(lang.stringRepeat("\xa0", i));
}
}
if (this.displayIndentGuides) {
this.$indentGuideRe = /\s\S| \t|\t |\s$/;
var className = "ace_indent-guide";
var spaceClass = "";
var tabClass = "";
if (this.showInvisibles) {
className += " ace_invisible";
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);
} else{
var spaceContent = lang.stringRepeat("\xa0", this.tabSize);
var tabContent = spaceContent;
}
this.$tabStrings[" "] = "<span class='" + className + spaceClass + "'>" + spaceContent + "</span>";
this.$tabStrings["\t"] = "<span class='" + className + tabClass + "'>" + tabContent + "</span>";
}
};
this.updateLines = function(config, firstRow, lastRow) {
// Due to wrap line changes there can be new lines if e.g.
// the line to updated wrapped in the meantime.
if (this.config.lastRow != config.lastRow ||
this.config.firstRow != config.firstRow) {
this.scrollLines(config);
}
this.config = config;
var first = Math.max(firstRow, config.firstRow);
var last = Math.min(lastRow, config.lastRow);
var lineElements = this.element.childNodes;
var lineElementsIdx = 0;
for (var row = config.firstRow; row < first; row++) {
var foldLine = this.session.getFoldLine(row);
if (foldLine) {
if (foldLine.containsRow(first)) {
first = foldLine.start.row;
break;
} else {
row = foldLine.end.row;
}
}
lineElementsIdx ++;
}
var row = first;
var foldLine = this.session.getNextFoldLine(row);
var foldStart = foldLine ? foldLine.start.row : Infinity;
while (true) {
if (row > foldStart) {
row = foldLine.end.row+1;
foldLine = this.session.getNextFoldLine(row, foldLine);
foldStart = foldLine ? foldLine.start.row :Infinity;
}
if (row > last)
break;
var lineElement = lineElements[lineElementsIdx++];
if (lineElement) {
var html = [];
this.$renderLine(
html, row, !this.$useLineGroups(), row == foldStart ? foldLine : false
);
lineElement.style.height = config.lineHeight * this.session.getRowLength(row) + "px";
lineElement.innerHTML = html.join("");
}
row++;
}
};
this.scrollLines = function(config) {
var oldConfig = this.config;
this.config = config;
if (!oldConfig || oldConfig.lastRow < config.firstRow)
return this.update(config);
if (config.lastRow < oldConfig.firstRow)
return this.update(config);
var el = this.element;
if (oldConfig.firstRow < config.firstRow)
for (var row=this.session.getFoldedRowCount(oldConfig.firstRow, config.firstRow - 1); row>0; row--)
el.removeChild(el.firstChild);
if (oldConfig.lastRow > config.lastRow)
for (var row=this.session.getFoldedRowCount(config.lastRow + 1, oldConfig.lastRow); row>0; row--)
el.removeChild(el.lastChild);
if (config.firstRow < oldConfig.firstRow) {
var fragment = this.$renderLinesFragment(config, config.firstRow, oldConfig.firstRow - 1);
if (el.firstChild)
el.insertBefore(fragment, el.firstChild);
else
el.appendChild(fragment);
}
if (config.lastRow > oldConfig.lastRow) {
var fragment = this.$renderLinesFragment(config, oldConfig.lastRow + 1, config.lastRow);
el.appendChild(fragment);
}
};
this.$renderLinesFragment = function(config, firstRow, lastRow) {
var fragment = this.element.ownerDocument.createDocumentFragment();
var row = firstRow;
var foldLine = this.session.getNextFoldLine(row);
var foldStart = foldLine ? foldLine.start.row : Infinity;
while (true) {
if (row > foldStart) {
row = foldLine.end.row+1;
foldLine = this.session.getNextFoldLine(row, foldLine);
foldStart = foldLine ? foldLine.start.row : Infinity;
}
if (row > lastRow)
break;
var container = dom.createElement("div");
var html = [];
// Get the tokens per line as there might be some lines in between
// beeing folded.
this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
// don't use setInnerHtml since we are working with an empty DIV
container.innerHTML = html.join("");
if (this.$useLineGroups()) {
container.className = 'ace_line_group';
fragment.appendChild(container);
container.style.height = config.lineHeight * this.session.getRowLength(row) + "px";
} else {
while(container.firstChild)
fragment.appendChild(container.firstChild);
}
row++;
}
return fragment;
};
this.update = function(config) {
this.config = config;
var html = [];
var firstRow = config.firstRow, lastRow = config.lastRow;
var row = firstRow;
var foldLine = this.session.getNextFoldLine(row);
var foldStart = foldLine ? foldLine.start.row : Infinity;
while (true) {
if (row > foldStart) {
row = foldLine.end.row+1;
foldLine = this.session.getNextFoldLine(row, foldLine);
foldStart = foldLine ? foldLine.start.row :Infinity;
}
if (row > lastRow)
break;
if (this.$useLineGroups())
html.push("<div class='ace_line_group' style='height:", config.lineHeight*this.session.getRowLength(row), "px'>")
this.$renderLine(html, row, false, row == foldStart ? foldLine : false);
if (this.$useLineGroups())
html.push("</div>"); // end the line group
row++;
}
this.element.innerHTML = html.join("");
};
this.$textToken = {
"text": true,
"rparen": true,
"lparen": true
};
this.$renderToken = function(stringBuilder, screenColumn, token, value) {
var self = this;
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 ?
"<span class='ace_invisible ace_invisible_space'>" + lang.stringRepeat(self.SPACE_CHAR, c.length) + "</span>" :
lang.stringRepeat("\xa0", c.length);
} else if (c == "&") {
return "&#38;";
} else if (c == "<") {
return "&#60;";
} else if (c == "\t") {
var tabSize = self.session.getScreenTabSize(screenColumn + tabIdx);
screenColumn += tabSize - 1;
return self.$tabStrings[tabSize];
} else if (c == "\u3000") {
// U+3000 is both invisible AND full-width, so must be handled uniquely
var classToUse = self.showInvisibles ? "ace_cjk ace_invisible ace_invisible_space" : "ace_cjk";
var space = self.showInvisibles ? self.SPACE_CHAR : "";
screenColumn += 1;
return "<span class='" + classToUse + "' style='width:" +
(self.config.characterWidth * 2) +
"px'>" + space + "</span>";
} else if (b) {
return "<span class='ace_invisible ace_invisible_space ace_invalid'>" + self.SPACE_CHAR + "</span>";
} else {
screenColumn += 1;
return "<span class='ace_cjk' style='width:" +
(self.config.characterWidth * 2) +
"px'>" + c + "</span>";
}
};
var output = value.replace(replaceReg, replaceFunc);
if (!this.$textToken[token.type]) {
var classes = "ace_" + token.type.replace(/\./g, " ace_");
var style = "";
if (token.type == "fold")
style = " style='width:" + (token.value.length * this.config.characterWidth) + "px;' ";
stringBuilder.push("<span class='", classes, "'", style, ">", output, "</span>");
}
else {
stringBuilder.push(output);
}
return screenColumn + value.length;
};
this.renderIndentGuide = function(stringBuilder, value, max) {
var cols = value.search(this.$indentGuideRe);
if (cols <= 0 || cols >= max)
return value;
if (value[0] == " ") {
cols -= cols % this.tabSize;
stringBuilder.push(lang.stringRepeat(this.$tabStrings[" "], cols/this.tabSize));
return value.substr(cols);
} else if (value[0] == "\t") {
stringBuilder.push(lang.stringRepeat(this.$tabStrings["\t"], cols));
return value.substr(cols);
}
return value;
};
this.$renderWrappedLine = function(stringBuilder, tokens, splits, onlyContents) {
var chars = 0;
var split = 0;
var splitChars = splits[0];
var screenColumn = 0;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
var value = token.value;
if (i == 0 && this.displayIndentGuides) {
chars = value.length;
value = this.renderIndentGuide(stringBuilder, value, splitChars);
if (!value)
continue;
chars -= value.length;
}
if (chars + value.length < splitChars) {
screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
chars += value.length;
} else {
while (chars + value.length >= splitChars) {
screenColumn = this.$renderToken(
stringBuilder, screenColumn,
token, value.substring(0, splitChars - chars)
);
value = value.substring(splitChars - chars);
chars = splitChars;
if (!onlyContents) {
stringBuilder.push("</div>",
"<div class='ace_line' style='height:",
this.config.lineHeight, "px'>"
);
}
split ++;
screenColumn = 0;
splitChars = splits[split] || Number.MAX_VALUE;
}
if (value.length != 0) {
chars += value.length;
screenColumn = this.$renderToken(
stringBuilder, screenColumn, token, value
);
}
}
}
};
this.$renderSimpleLine = function(stringBuilder, tokens) {
var screenColumn = 0;
var token = tokens[0];
var value = token.value;
if (this.displayIndentGuides)
value = this.renderIndentGuide(stringBuilder, value);
if (value)
screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
for (var i = 1; i < tokens.length; i++) {
token = tokens[i];
value = token.value;
screenColumn = this.$renderToken(stringBuilder, screenColumn, token, value);
}
};
// row is either first row of foldline or not in fold
this.$renderLine = function(stringBuilder, row, onlyContents, foldLine) {
if (!foldLine && foldLine != false)
foldLine = this.session.getFoldLine(row);
if (foldLine)
var tokens = this.$getFoldLineTokens(row, foldLine);
else
var tokens = this.session.getTokens(row);
if (!onlyContents) {
stringBuilder.push(
"<div class='ace_line' style='height:",
this.config.lineHeight * (
this.$useLineGroups() ? 1 :this.session.getRowLength(row)
), "px'>"
);
}
if (tokens.length) {
var splits = this.session.getRowSplitData(row);
if (splits && splits.length)
this.$renderWrappedLine(stringBuilder, tokens, splits, onlyContents);
else
this.$renderSimpleLine(stringBuilder, tokens);
}
if (this.showInvisibles) {
if (foldLine)
row = foldLine.end.row
stringBuilder.push(
"<span class='ace_invisible ace_invisible_eol'>",
row == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR,
"</span>"
);
}
if (!onlyContents)
stringBuilder.push("</div>");
};
this.$getFoldLineTokens = function(row, foldLine) {
var session = this.session;
var renderTokens = [];
function addTokens(tokens, from, to) {
var idx = 0, col = 0;
while ((col + tokens[idx].value.length) < from) {
col += tokens[idx].value.length;
idx++;
if (idx == tokens.length)
return;
}
if (col != from) {
var value = tokens[idx].value.substring(from - col);
// Check if the token value is longer then the from...to spacing.
if (value.length > (to - from))
value = value.substring(0, to - from);
renderTokens.push({
type: tokens[idx].type,
value: value
});
col = from + value.length;
idx += 1;
}
while (col < to && idx < tokens.length) {
var value = tokens[idx].value;
if (value.length + col > to) {
renderTokens.push({
type: tokens[idx].type,
value: value.substring(0, to - col)
});
} else
renderTokens.push(tokens[idx]);
col += value.length;
idx += 1;
}
}
var tokens = session.getTokens(row);
foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) {
if (placeholder != null) {
renderTokens.push({
type: "fold",
value: placeholder
});
} else {
if (isNewRow)
tokens = session.getTokens(row);
if (tokens.length)
addTokens(tokens, lastColumn, column);
}
}, foldLine.end.row, this.session.getLine(foldLine.end.row).length);
return renderTokens;
};
this.$useLineGroups = function() {
// For the updateLines function to work correctly, it's important that the
// child nodes of this.element correspond on a 1-to-1 basis to rows in the
// document (as distinct from lines on the screen). For sessions that are
// wrapped, this means we need to add a layer to the node hierarchy (tagged
// with the class name ace_line_group).
return this.session.getUseWrapMode();
};
this.destroy = function() {
clearInterval(this.$pollSizeChangesTimer);
if (this.$measureNode)
this.$measureNode.parentNode.removeChild(this.$measureNode);
delete this.$measureNode;
};
}).call(Text.prototype);
exports.Text = Text;
});
@@ -1,126 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
require("../test/mockdom");
}
define(function(require, exports, module) {
"use strict";
var assert = require("../test/assertions");
var EditSession = require("../edit_session").EditSession;
var TextLayer = require("./text").Text;
var JavaScriptMode = require("../mode/javascript").Mode;
module.exports = {
setUp: function(next) {
this.session = new EditSession("");
this.session.setMode(new JavaScriptMode());
this.textLayer = new TextLayer(document.createElement("div"));
this.textLayer.setSession(this.session);
this.textLayer.config = {
characterWidth: 10,
lineHeight: 20
};
next()
},
"test: render line with hard tabs should render the same as lines with soft tabs" : function() {
this.session.setValue("a\ta\ta\t\na a a \n");
this.textLayer.$computeTabString();
// row with hard tabs
var stringBuilder = [];
this.textLayer.$renderLine(stringBuilder, 0);
// row with soft tabs
var stringBuilder2 = [];
this.textLayer.$renderLine(stringBuilder2, 1);
assert.equal(stringBuilder.join(""), stringBuilder2.join(""));
},
"test rendering width of ideographic space (U+3000)" : function() {
this.session.setValue("\u3000");
var stringBuilder = [];
this.textLayer.$renderLine(stringBuilder, 0, true);
assert.equal(stringBuilder.join(""), "<span class='ace_cjk' style='width:20px'></span>");
this.textLayer.setShowInvisibles(true);
var stringBuilder = [];
this.textLayer.$renderLine(stringBuilder, 0, true);
assert.equal(
stringBuilder.join(""),
"<span class='ace_cjk ace_invisible ace_invisible_space' style='width:20px'>" + this.textLayer.SPACE_CHAR + "</span>"
+ "<span class='ace_invisible ace_invisible_eol'>\xB6</span>"
);
},
"test rendering of indent guides" : function() {
var textLayer = this.textLayer
var EOL = "<span class='ace_invisible ace_invisible_eol'>" + textLayer.EOL_CHAR + "</span>";
var SPACE = function(i) {return Array(i+1).join("\xa0")}
var DOT = function(i) {return Array(i+1).join(textLayer.SPACE_CHAR)}
var TAB = function(i) {return textLayer.TAB_CHAR + SPACE(i-1)}
function testRender(results) {
for (var i = results.length; i--; ) {
var stringBuilder = [];
textLayer.$renderLine(stringBuilder, i, true);
assert.equal(stringBuilder.join(""), results[i]);
}
}
this.session.setValue(" \n\t\tf\n ");
testRender([
"<span class='ace_indent-guide'>" + SPACE(4) + "</span>" + SPACE(2),
"<span class='ace_indent-guide'>" + SPACE(4) + "</span>" + SPACE(4) + "<span class='ace_identifier'>f</span>",
SPACE(3)
]);
this.textLayer.setShowInvisibles(true);
testRender([
"<span class='ace_indent-guide ace_invisible ace_invisible_space'>" + DOT(4) + "</span><span class='ace_invisible ace_invisible_space'>" + DOT(2) + "</span>" + EOL,
"<span class='ace_indent-guide ace_invisible ace_invisible_tab'>" + TAB(4) + "</span><span class='ace_invisible ace_invisible_tab'>" + TAB(4) + "</span><span class='ace_identifier'>f</span>" + EOL,
]);
this.textLayer.setDisplayIndentGuides(false);
testRender([
"<span class='ace_invisible ace_invisible_space'>" + DOT(6) + "</span>" + EOL,
"<span class='ace_invisible ace_invisible_tab'>" + TAB(4) + "</span><span class='ace_invisible ace_invisible_tab'>" + TAB(4) + "</span><span class='ace_identifier'>f</span>" + EOL
]);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,157 +0,0 @@
/* ***** 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) {
"no use strict";
var oop = require("./oop");
var EventEmitter = require("./event_emitter").EventEmitter;
var optionsProvider = {
setOptions: function(optList) {
Object.keys(optList).forEach(function(key) {
this.setOption(key, optList[key]);
}, this);
},
getOptions: function(optionNames) {
var result = {};
if (!optionNames) {
optionNames = Object.keys(this.$options);
} else if (!Array.isArray(optionNames)) {
result = optionNames;
optionNames = Object.keys(result);
}
optionNames.forEach(function(key) {
result[key] = this.getOption(key);
}, this);
return result;
},
setOption: function(name, value) {
if (this["$" + name] === value)
return;
var opt = this.$options[name];
if (!opt) {
return warn('misspelled option "' + name + '"');
}
if (opt.forwardTo)
return this[opt.forwardTo] && this[opt.forwardTo].setOption(name, value);
if (!opt.handlesSet)
this["$" + name] = value;
if (opt && opt.set)
opt.set.call(this, value);
},
getOption: function(name) {
var opt = this.$options[name];
if (!opt) {
return warn('misspelled option "' + name + '"');
}
if (opt.forwardTo)
return this[opt.forwardTo] && this[opt.forwardTo].getOption(name);
return opt && opt.get ? opt.get.call(this) : this["$" + name];
}
};
function warn(message) {
if (typeof console != "undefined" && console.warn)
console.warn.apply(console, arguments);
}
function reportError(msg, data) {
var e = new Error(msg);
e.data = data;
if (typeof console == "object" && console.error)
console.error(e);
setTimeout(function() { throw e; });
}
var AppConfig = function() {
this.$defaultOptions = {};
};
(function() {
// module loading
oop.implement(this, EventEmitter);
/*
* option {name, value, initialValue, setterName, set, get }
*/
this.defineOptions = function(obj, path, options) {
if (!obj.$options)
this.$defaultOptions[path] = obj.$options = {};
Object.keys(options).forEach(function(key) {
var opt = options[key];
if (typeof opt == "string")
opt = {forwardTo: opt};
opt.name || (opt.name = key);
obj.$options[opt.name] = opt;
if ("initialValue" in opt)
obj["$" + opt.name] = opt.initialValue;
});
// implement option provider interface
oop.implement(obj, optionsProvider);
return this;
};
this.resetOptions = function(obj) {
Object.keys(obj.$options).forEach(function(key) {
var opt = obj.$options[key];
if ("value" in opt)
obj.setOption(key, opt.value);
});
};
this.setDefaultValue = function(path, name, value) {
var opts = this.$defaultOptions[path] || (this.$defaultOptions[path] = {});
if (opts[name]) {
if (opts.forwardTo)
this.setDefaultValue(opts.forwardTo, name, value);
else
opts[name].value = value;
}
};
this.setDefaultValues = function(path, optionHash) {
Object.keys(optionHash).forEach(function(key) {
this.setDefaultValue(path, key, optionHash[key]);
}, this);
};
this.warn = warn;
this.reportError = reportError;
}).call(AppConfig.prototype);
exports.AppConfig = AppConfig;
});
@@ -1,284 +0,0 @@
/* ***** 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 XHTML_NS = "http://www.w3.org/1999/xhtml";
exports.getDocumentHead = function(doc) {
if (!doc)
doc = document;
return doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement;
}
exports.createElement = function(tag, ns) {
return document.createElementNS ?
document.createElementNS(ns || XHTML_NS, tag) :
document.createElement(tag);
};
exports.hasCssClass = function(el, name) {
var classes = (el.className || "").split(/\s+/g);
return classes.indexOf(name) !== -1;
};
/*
* Add a CSS class to the list of classes on the given node
*/
exports.addCssClass = function(el, name) {
if (!exports.hasCssClass(el, name)) {
el.className += " " + name;
}
};
/*
* Remove a CSS class from the list of classes on the given node
*/
exports.removeCssClass = function(el, name) {
var classes = el.className.split(/\s+/g);
while (true) {
var index = classes.indexOf(name);
if (index == -1) {
break;
}
classes.splice(index, 1);
}
el.className = classes.join(" ");
};
exports.toggleCssClass = function(el, name) {
var classes = el.className.split(/\s+/g), add = true;
while (true) {
var index = classes.indexOf(name);
if (index == -1) {
break;
}
add = false;
classes.splice(index, 1);
}
if(add)
classes.push(name);
el.className = classes.join(" ");
return add;
};
/*
* Add or remove a CSS class from the list of classes on the given node
* depending on the value of <tt>include</tt>
*/
exports.setCssClass = function(node, className, include) {
if (include) {
exports.addCssClass(node, className);
} else {
exports.removeCssClass(node, className);
}
};
exports.hasCssString = function(id, doc) {
var index = 0, sheets;
doc = doc || document;
if (doc.createStyleSheet && (sheets = doc.styleSheets)) {
while (index < sheets.length)
if (sheets[index++].owningElement.id === id) return true;
} else if ((sheets = doc.getElementsByTagName("style"))) {
while (index < sheets.length)
if (sheets[index++].id === id) return true;
}
return false;
};
exports.importCssString = function importCssString(cssText, id, doc) {
doc = doc || document;
// If style is already imported return immediately.
if (id && exports.hasCssString(id, doc))
return null;
var style;
if (doc.createStyleSheet) {
style = doc.createStyleSheet();
style.cssText = cssText;
if (id)
style.owningElement.id = id;
} else {
style = doc.createElementNS
? doc.createElementNS(XHTML_NS, "style")
: doc.createElement("style");
style.appendChild(doc.createTextNode(cssText));
if (id)
style.id = id;
exports.getDocumentHead(doc).appendChild(style);
}
};
exports.importCssStylsheet = function(uri, doc) {
if (doc.createStyleSheet) {
doc.createStyleSheet(uri);
} else {
var link = exports.createElement('link');
link.rel = 'stylesheet';
link.href = uri;
exports.getDocumentHead(doc).appendChild(link);
}
};
exports.getInnerWidth = function(element) {
return (
parseInt(exports.computedStyle(element, "paddingLeft"), 10) +
parseInt(exports.computedStyle(element, "paddingRight"), 10) +
element.clientWidth
);
};
exports.getInnerHeight = function(element) {
return (
parseInt(exports.computedStyle(element, "paddingTop"), 10) +
parseInt(exports.computedStyle(element, "paddingBottom"), 10) +
element.clientHeight
);
};
if (typeof document == "undefined")
return;
if (window.pageYOffset !== undefined) {
exports.getPageScrollTop = function() {
return window.pageYOffset;
};
exports.getPageScrollLeft = function() {
return window.pageXOffset;
};
}
else {
exports.getPageScrollTop = function() {
return document.body.scrollTop;
};
exports.getPageScrollLeft = function() {
return document.body.scrollLeft;
};
}
if (window.getComputedStyle)
exports.computedStyle = function(element, style) {
if (style)
return (window.getComputedStyle(element, "") || {})[style] || "";
return window.getComputedStyle(element, "") || {};
};
else
exports.computedStyle = function(element, style) {
if (style)
return element.currentStyle[style];
return element.currentStyle;
};
exports.scrollbarWidth = function(document) {
var inner = exports.createElement("ace_inner");
inner.style.width = "100%";
inner.style.minWidth = "0px";
inner.style.height = "200px";
inner.style.display = "block";
var outer = exports.createElement("ace_outer");
var style = outer.style;
style.position = "absolute";
style.left = "-10000px";
style.overflow = "hidden";
style.width = "200px";
style.minWidth = "0px";
style.height = "150px";
style.display = "block";
outer.appendChild(inner);
var body = document.documentElement;
body.appendChild(outer);
var noScrollbar = inner.offsetWidth;
style.overflow = "scroll";
var withScrollbar = inner.offsetWidth;
if (noScrollbar == withScrollbar) {
withScrollbar = outer.clientWidth;
}
body.removeChild(outer);
return noScrollbar-withScrollbar;
};
/*
* Optimized set innerHTML. This is faster than plain innerHTML if the element
* already contains a lot of child elements.
*
* See http://blog.stevenlevithan.com/archives/faster-than-innerhtml for details
*/
exports.setInnerHtml = function(el, innerHtml) {
var element = el.cloneNode(false);//document.createElement("div");
element.innerHTML = innerHtml;
el.parentNode.replaceChild(element, el);
return element;
};
if ("textContent" in document.documentElement) {
exports.setInnerText = function(el, innerText) {
el.textContent = innerText;
};
exports.getInnerText = function(el) {
return el.textContent;
};
}
else {
exports.setInnerText = function(el, innerText) {
el.innerText = innerText;
};
exports.getInnerText = function(el) {
return el.innerText;
};
}
exports.getParentWindow = function(document) {
return document.defaultView || document.parentWindow;
};
});
File diff suppressed because it is too large Load Diff
@@ -1,367 +0,0 @@
/* ***** 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 keys = require("./keys");
var useragent = require("./useragent");
exports.addListener = function(elem, type, callback) {
if (elem.addEventListener) {
return elem.addEventListener(type, callback, false);
}
if (elem.attachEvent) {
var wrapper = function() {
callback.call(elem, window.event);
};
callback._wrapper = wrapper;
elem.attachEvent("on" + type, wrapper);
}
};
exports.removeListener = function(elem, type, callback) {
if (elem.removeEventListener) {
return elem.removeEventListener(type, callback, false);
}
if (elem.detachEvent) {
elem.detachEvent("on" + type, callback._wrapper || callback);
}
};
/*
* Prevents propagation and clobbers the default action of the passed event
*/
exports.stopEvent = function(e) {
exports.stopPropagation(e);
exports.preventDefault(e);
return false;
};
exports.stopPropagation = function(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
};
exports.preventDefault = function(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
};
/*
* @return {Number} 0 for left button, 1 for middle button, 2 for right button
*/
exports.getButton = function(e) {
if (e.type == "dblclick")
return 0;
if (e.type == "contextmenu" || (useragent.isMac && (e.ctrlKey && !e.altKey && !e.shiftKey)))
return 2;
// DOM Event
if (e.preventDefault) {
return e.button;
}
// old IE
else {
return {1:0, 2:2, 4:1}[e.button];
}
};
exports.capture = function(el, eventHandler, releaseCaptureHandler) {
function onMouseUp(e) {
eventHandler && eventHandler(e);
releaseCaptureHandler && releaseCaptureHandler(e);
exports.removeListener(document, "mousemove", eventHandler, true);
exports.removeListener(document, "mouseup", onMouseUp, true);
exports.removeListener(document, "dragstart", onMouseUp, true);
}
exports.addListener(document, "mousemove", eventHandler, true);
exports.addListener(document, "mouseup", onMouseUp, true);
exports.addListener(document, "dragstart", onMouseUp, true);
return onMouseUp;
};
exports.addMouseWheelListener = function(el, callback) {
if ("onmousewheel" in el) {
exports.addListener(el, "mousewheel", function(e) {
var factor = 8;
if (e.wheelDeltaX !== undefined) {
e.wheelX = -e.wheelDeltaX / factor;
e.wheelY = -e.wheelDeltaY / factor;
} else {
e.wheelX = 0;
e.wheelY = -e.wheelDelta / factor;
}
callback(e);
});
} else if ("onwheel" in el) {
exports.addListener(el, "wheel", function(e) {
var factor = 0.35;
switch (e.deltaMode) {
case e.DOM_DELTA_PIXEL:
e.wheelX = e.deltaX * factor || 0;
e.wheelY = e.deltaY * factor || 0;
break;
case e.DOM_DELTA_LINE:
case e.DOM_DELTA_PAGE:
e.wheelX = (e.deltaX || 0) * 5;
e.wheelY = (e.deltaY || 0) * 5;
break;
}
callback(e);
});
} else {
exports.addListener(el, "DOMMouseScroll", function(e) {
if (e.axis && e.axis == e.HORIZONTAL_AXIS) {
e.wheelX = (e.detail || 0) * 5;
e.wheelY = 0;
} else {
e.wheelX = 0;
e.wheelY = (e.detail || 0) * 5;
}
callback(e);
});
}
};
exports.addMultiMouseDownListener = function(el, timeouts, eventHandler, callbackName) {
var clicks = 0;
var startX, startY, timer;
var eventNames = {
2: "dblclick",
3: "tripleclick",
4: "quadclick"
};
exports.addListener(el, "mousedown", function(e) {
if (exports.getButton(e) !== 0) {
clicks = 0;
} else if (e.detail > 1) {
clicks++;
if (clicks > 4)
clicks = 1;
} else {
clicks = 1;
}
if (useragent.isIE) {
var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5;
if (!timer || isNewClick)
clicks = 1;
if (timer)
clearTimeout(timer);
timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
if (clicks == 1) {
startX = e.clientX;
startY = e.clientY;
}
}
e._clicks = clicks;
eventHandler[callbackName]("mousedown", e);
if (clicks > 4)
clicks = 0;
else if (clicks > 1)
return eventHandler[callbackName](eventNames[clicks], e);
});
if (useragent.isOldIE) {
exports.addListener(el, "dblclick", function(e) {
clicks = 2;
if (timer)
clearTimeout(timer);
timer = setTimeout(function() {timer = null}, timeouts[clicks - 1] || 600);
eventHandler[callbackName]("mousedown", e);
eventHandler[callbackName](eventNames[clicks], e);
});
}
};
var getModifierHash = useragent.isMac && useragent.isOpera && !("KeyboardEvent" in window)
? function(e) {
return 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0);
}
: function(e) {
return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
};
exports.getModifierString = function(e) {
return keys.KEY_MODS[getModifierHash(e)];
};
function normalizeCommandKeys(callback, e, keyCode) {
var hashId = getModifierHash(e);
if (!useragent.isMac && pressedKeys) {
if (pressedKeys[91] || pressedKeys[92])
hashId |= 8;
if (pressedKeys.altGr) {
if ((3 & hashId) != 3)
pressedKeys.altGr = 0;
else
return;
}
if (keyCode === 18 || keyCode === 17) {
var location = "location" in e ? e.location : e.keyLocation;
if (keyCode === 17 && location === 1) {
ts = e.timeStamp;
} else if (keyCode === 18 && hashId === 3 && location === 2) {
var dt = -ts;
ts = e.timeStamp;
dt += ts;
if (dt < 3)
pressedKeys.altGr = true;
}
}
}
if (keyCode in keys.MODIFIER_KEYS) {
keyCode = -1;
}
if (hashId & 8 && (keyCode === 91 || keyCode === 93)) {
keyCode = -1;
}
if (!hashId && keyCode === 13) {
var location = "location" in e ? e.location : e.keyLocation;
if (location === 3) {
callback(e, hashId, -keyCode);
if (e.defaultPrevented)
return;
}
}
if (useragent.isChromeOS && hashId & 8) {
callback(e, hashId, keyCode);
if (e.defaultPrevented)
return;
else
hashId &= ~8;
}
// If there is no hashId and the keyCode is not a function key, then
// we don't call the callback as we don't handle a command key here
// (it's a normal key/character input).
if (!hashId && !(keyCode in keys.FUNCTION_KEYS) && !(keyCode in keys.PRINTABLE_KEYS)) {
return false;
}
return callback(e, hashId, keyCode);
}
var pressedKeys = null;
var ts = 0;
exports.addCommandKeyListener = function(el, callback) {
var addListener = exports.addListener;
if (useragent.isOldGecko || (useragent.isOpera && !("KeyboardEvent" in window))) {
// Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown
// event if the user pressed the key for a longer time. Instead, the
// keydown event was fired once and later on only the keypress event.
// To emulate the 'right' keydown behavior, the keyCode of the initial
// keyDown event is stored and in the following keypress events the
// stores keyCode is used to emulate a keyDown event.
var lastKeyDownKeyCode = null;
addListener(el, "keydown", function(e) {
lastKeyDownKeyCode = e.keyCode;
});
addListener(el, "keypress", function(e) {
return normalizeCommandKeys(callback, e, lastKeyDownKeyCode);
});
} else {
var lastDefaultPrevented = null;
addListener(el, "keydown", function(e) {
pressedKeys[e.keyCode] = true;
var result = normalizeCommandKeys(callback, e, e.keyCode);
lastDefaultPrevented = e.defaultPrevented;
return result;
});
addListener(el, "keypress", function(e) {
if (lastDefaultPrevented && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey)) {
exports.stopEvent(e);
lastDefaultPrevented = null;
}
});
addListener(el, "keyup", function(e) {
pressedKeys[e.keyCode] = null;
});
if (!pressedKeys) {
pressedKeys = Object.create(null);
addListener(window, "focus", function(e) {
pressedKeys = Object.create(null);
});
}
}
};
if (window.postMessage && !useragent.isOldIE) {
var postMessageId = 1;
exports.nextTick = function(callback, win) {
win = win || window;
var messageName = "zero-timeout-message-" + postMessageId;
exports.addListener(win, "message", function listener(e) {
if (e.data == messageName) {
exports.stopPropagation(e);
exports.removeListener(win, "message", listener);
callback();
}
});
win.postMessage(messageName, "*");
};
}
exports.nextFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame;
if (exports.nextFrame)
exports.nextFrame = exports.nextFrame.bind(window);
else
exports.nextFrame = function(callback) {
setTimeout(callback, 17);
};
});
@@ -1,155 +0,0 @@
/* ***** 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 EventEmitter = {};
var stopPropagation = function() { this.propagationStopped = true; };
var preventDefault = function() { this.defaultPrevented = true; };
EventEmitter._emit =
EventEmitter._dispatchEvent = function(eventName, e) {
this._eventRegistry || (this._eventRegistry = {});
this._defaultHandlers || (this._defaultHandlers = {});
var listeners = this._eventRegistry[eventName] || [];
var defaultHandler = this._defaultHandlers[eventName];
if (!listeners.length && !defaultHandler)
return;
if (typeof e != "object" || !e)
e = {};
if (!e.type)
e.type = eventName;
if (!e.stopPropagation)
e.stopPropagation = stopPropagation;
if (!e.preventDefault)
e.preventDefault = preventDefault;
listeners = listeners.slice();
for (var i=0; i<listeners.length; i++) {
listeners[i](e, this);
if (e.propagationStopped)
break;
}
if (defaultHandler && !e.defaultPrevented)
return defaultHandler(e, this);
};
EventEmitter._signal = function(eventName, e) {
var listeners = (this._eventRegistry || {})[eventName];
if (!listeners)
return;
listeners = listeners.slice();
for (var i=0; i<listeners.length; i++)
listeners[i](e, this);
};
EventEmitter.once = function(eventName, callback) {
var _self = this;
callback && this.addEventListener(eventName, function newCallback() {
_self.removeEventListener(eventName, newCallback);
callback.apply(null, arguments);
});
};
EventEmitter.setDefaultHandler = function(eventName, callback) {
var handlers = this._defaultHandlers
if (!handlers)
handlers = this._defaultHandlers = {_disabled_: {}};
if (handlers[eventName]) {
var old = handlers[eventName];
var disabled = handlers._disabled_[eventName];
if (!disabled)
handlers._disabled_[eventName] = disabled = [];
disabled.push(old);
var i = disabled.indexOf(callback);
if (i != -1)
disabled.splice(i, 1);
}
handlers[eventName] = callback;
};
EventEmitter.removeDefaultHandler = function(eventName, callback) {
var handlers = this._defaultHandlers
if (!handlers)
return;
var disabled = handlers._disabled_[eventName];
if (handlers[eventName] == callback) {
var old = handlers[eventName];
if (disabled)
this.setDefaultHandler(eventName, disabled.pop());
} else if (disabled) {
var i = disabled.indexOf(callback);
if (i != -1)
disabled.splice(i, 1);
}
};
EventEmitter.on =
EventEmitter.addEventListener = function(eventName, callback, capturing) {
this._eventRegistry = this._eventRegistry || {};
var listeners = this._eventRegistry[eventName];
if (!listeners)
listeners = this._eventRegistry[eventName] = [];
if (listeners.indexOf(callback) == -1)
listeners[capturing ? "unshift" : "push"](callback);
return callback;
};
EventEmitter.off =
EventEmitter.removeListener =
EventEmitter.removeEventListener = function(eventName, callback) {
this._eventRegistry = this._eventRegistry || {};
var listeners = this._eventRegistry[eventName];
if (!listeners)
return;
var index = listeners.indexOf(callback);
if (index !== -1)
listeners.splice(index, 1);
};
EventEmitter.removeAllListeners = function(eventName) {
if (this._eventRegistry) this._eventRegistry[eventName] = [];
};
exports.EventEmitter = EventEmitter;
});
@@ -1,65 +0,0 @@
/* ***** 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 ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var EventEmitter = require("./event_emitter").EventEmitter;
var assert = require("../test/assertions");
var Emitter = function() {};
oop.implement(Emitter.prototype, EventEmitter);
module.exports = {
"test: dispatch event with no data" : function() {
var emitter = new Emitter();
var called = false;
emitter.addEventListener("juhu", function(e) {
called = true;
assert.equal(e.type, "juhu");
});
emitter._emit("juhu");
assert.ok(called);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}
@@ -1,19 +0,0 @@
// vim:set ts=4 sts=4 sw=4 st:
// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License
// -- tlrobinson Tom Robinson Copyright (C) 2009-2010 MIT License (Narwhal Project)
// -- dantman Daniel Friesen Copyright(C) 2010 XXX No License Specified
// -- fschaefer Florian Schäfer Copyright (C) 2010 MIT License
// -- Irakli Gozalishvili Copyright (C) 2010 MIT License
/*!
Copyright (c) 2009, 280 North Inc. http://280north.com/
MIT License. http://github.com/280north/narwhal/blob/master/README.md
*/
define(function(require, exports, module) {
"use strict";
require("./regexp");
require("./es5-shim");
});
@@ -1,163 +0,0 @@
/*! @license
==========================================================================
SproutCore -- JavaScript Application Framework
copyright 2006-2009, Sprout Systems Inc., Apple Inc. and contributors.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
SproutCore and the SproutCore logo are trademarks of Sprout Systems, Inc.
For more information about SproutCore, visit http://www.sproutcore.com
==========================================================================
@license */
// Most of the following code is taken from SproutCore with a few changes.
define(function(require, exports, module) {
"use strict";
require("./fixoldbrowsers");
var oop = require("./oop");
/*
* Helper functions and hashes for key handling.
*/
var Keys = (function() {
var ret = {
MODIFIER_KEYS: {
16: 'Shift', 17: 'Ctrl', 18: 'Alt', 224: 'Meta'
},
KEY_MODS: {
"ctrl": 1, "alt": 2, "option" : 2, "shift": 4,
"super": 8, "meta": 8, "command": 8, "cmd": 8
},
FUNCTION_KEYS : {
8 : "Backspace",
9 : "Tab",
13 : "Return",
19 : "Pause",
27 : "Esc",
32 : "Space",
33 : "PageUp",
34 : "PageDown",
35 : "End",
36 : "Home",
37 : "Left",
38 : "Up",
39 : "Right",
40 : "Down",
44 : "Print",
45 : "Insert",
46 : "Delete",
96 : "Numpad0",
97 : "Numpad1",
98 : "Numpad2",
99 : "Numpad3",
100: "Numpad4",
101: "Numpad5",
102: "Numpad6",
103: "Numpad7",
104: "Numpad8",
105: "Numpad9",
'-13': "NumpadEnter",
112: "F1",
113: "F2",
114: "F3",
115: "F4",
116: "F5",
117: "F6",
118: "F7",
119: "F8",
120: "F9",
121: "F10",
122: "F11",
123: "F12",
144: "Numlock",
145: "Scrolllock"
},
PRINTABLE_KEYS: {
32: ' ', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5',
54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 61: '=', 65: 'a',
66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h',
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: '\''
}
};
// A reverse map of FUNCTION_KEYS
var name, i;
for (i in ret.FUNCTION_KEYS) {
name = ret.FUNCTION_KEYS[i].toLowerCase();
ret[name] = parseInt(i, 10);
}
// A reverse map of PRINTABLE_KEYS
for (i in ret.PRINTABLE_KEYS) {
name = ret.PRINTABLE_KEYS[i].toLowerCase();
ret[name] = parseInt(i, 10);
}
// Add the MODIFIER_KEYS, FUNCTION_KEYS and PRINTABLE_KEYS to the KEY
// variables as well.
oop.mixin(ret, ret.MODIFIER_KEYS);
oop.mixin(ret, ret.PRINTABLE_KEYS);
oop.mixin(ret, ret.FUNCTION_KEYS);
// aliases
ret.enter = ret["return"];
ret.escape = ret.esc;
ret.del = ret["delete"];
// workaround for firefox bug
ret[173] = '-';
(function() {
var mods = ["cmd", "ctrl", "alt", "shift"];
for (var i = Math.pow(2, mods.length); i--;) {
ret.KEY_MODS[i] = mods.filter(function(x) {
return i & ret.KEY_MODS[x];
}).join("-") + "-";
}
})();
ret.KEY_MODS[0] = "";
ret.KEY_MODS[-1] = "input-";
return ret;
})();
oop.mixin(exports, Keys);
exports.keyCodeToString = function(keyCode) {
// Language-switching keystroke in Chrome/Linux emits keyCode 0.
var keyString = Keys[keyCode];
if (typeof keyString != "string")
keyString = String.fromCharCode(keyCode);
return keyString.toLowerCase();
};
});
@@ -1,221 +0,0 @@
/* ***** 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";
exports.last = function(a) {
return a[a.length - 1];
};
exports.stringReverse = function(string) {
return string.split("").reverse().join("");
};
exports.stringRepeat = function (string, count) {
var result = '';
while (count > 0) {
if (count & 1)
result += string;
if (count >>= 1)
string += string;
}
return result;
};
var trimBeginRegexp = /^\s\s*/;
var trimEndRegexp = /\s\s*$/;
exports.stringTrimLeft = function (string) {
return string.replace(trimBeginRegexp, '');
};
exports.stringTrimRight = function (string) {
return string.replace(trimEndRegexp, '');
};
exports.copyObject = function(obj) {
var copy = {};
for (var key in obj) {
copy[key] = obj[key];
}
return copy;
};
exports.copyArray = function(array){
var copy = [];
for (var i=0, l=array.length; i<l; i++) {
if (array[i] && typeof array[i] == "object")
copy[i] = this.copyObject( array[i] );
else
copy[i] = array[i];
}
return copy;
};
exports.deepCopy = function (obj) {
if (typeof obj !== "object" || !obj)
return obj;
var cons = obj.constructor;
if (cons === RegExp)
return obj;
var copy = cons();
for (var key in obj) {
if (typeof obj[key] === "object") {
copy[key] = exports.deepCopy(obj[key]);
} else {
copy[key] = obj[key];
}
}
return copy;
};
exports.arrayToMap = function(arr) {
var map = {};
for (var i=0; i<arr.length; i++) {
map[arr[i]] = 1;
}
return map;
};
exports.createMap = function(props) {
var map = Object.create(null);
for (var i in props) {
map[i] = props[i];
}
return map;
};
/*
* splice out of 'array' anything that === 'value'
*/
exports.arrayRemove = function(array, value) {
for (var i = 0; i <= array.length; i++) {
if (value === array[i]) {
array.splice(i, 1);
}
}
};
exports.escapeRegExp = function(str) {
return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
};
exports.escapeHTML = function(str) {
return str.replace(/&/g, "&#38;").replace(/"/g, "&#34;").replace(/'/g, "&#39;").replace(/</g, "&#60;");
};
exports.getMatchOffsets = function(string, regExp) {
var matches = [];
string.replace(regExp, function(str) {
matches.push({
offset: arguments[arguments.length-2],
length: str.length
});
});
return matches;
};
/* deprecated */
exports.deferredCall = function(fcn) {
var timer = null;
var callback = function() {
timer = null;
fcn();
};
var deferred = function(timeout) {
deferred.cancel();
timer = setTimeout(callback, timeout || 0);
return deferred;
};
deferred.schedule = deferred;
deferred.call = function() {
this.cancel();
fcn();
return deferred;
};
deferred.cancel = function() {
clearTimeout(timer);
timer = null;
return deferred;
};
deferred.isPending = function() {
return timer;
};
return deferred;
};
exports.delayedCall = function(fcn, defaultTimeout) {
var timer = null;
var callback = function() {
timer = null;
fcn();
};
var _self = function(timeout) {
if (timer == null)
timer = setTimeout(callback, timeout || defaultTimeout);
};
_self.delay = function(timeout) {
timer && clearTimeout(timer);
timer = setTimeout(callback, timeout || defaultTimeout);
};
_self.schedule = _self;
_self.call = function() {
this.cancel();
fcn();
};
_self.cancel = function() {
timer && clearTimeout(timer);
timer = null;
};
_self.isPending = function() {
return timer;
};
return _self;
};
});

Some files were not shown because too many files have changed in this diff Show More