summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn McLear <john@mclear.co.uk>2012-04-06 17:07:49 -0700
committerJohn McLear <john@mclear.co.uk>2012-04-06 17:07:49 -0700
commit57e137f65bd5152c0c2dc8508c796674d3289545 (patch)
tree99ed138fe0686e5304228331e9f3cda1b1687070
parentae21eb30ddda46f535d5024bd192145383d4be5d (diff)
parent7418f08dc788b2f8a405fc20d73b088ee4413e85 (diff)
downloadetherpad-lite-57e137f65bd5152c0c2dc8508c796674d3289545.zip
Merge pull request #599 from fourplusone/ace2_refactoring
ACE2 refactoring
-rw-r--r--src/node/utils/tar.json2
-rw-r--r--src/static/js/AttributeManager.js158
-rw-r--r--src/static/js/AttributePool.js6
-rw-r--r--src/static/js/ChangesetUtils.js60
-rw-r--r--src/static/js/ace2_inner.js280
5 files changed, 298 insertions, 208 deletions
diff --git a/src/node/utils/tar.json b/src/node/utils/tar.json
index 14c93f5c..5c1abac5 100644
--- a/src/node/utils/tar.json
+++ b/src/node/utils/tar.json
@@ -56,6 +56,7 @@
, "rjquery.js"
, "AttributePool.js"
, "Changeset.js"
+ , "ChangesetUtils.js"
, "security.js"
, "skiplist.js"
, "virtual_lines.js"
@@ -66,6 +67,7 @@
, "changesettracker.js"
, "linestylefilter.js"
, "domline.js"
+ , "AttributeManager.js"
, "ace2_inner.js"
]
}
diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js
new file mode 100644
index 00000000..c6351e86
--- /dev/null
+++ b/src/static/js/AttributeManager.js
@@ -0,0 +1,158 @@
+var Changeset = require('./Changeset');
+var ChangesetUtils = require('./ChangesetUtils');
+var _ = require('./underscore');
+
+var lineMarkerAttribute = 'lmkr';
+
+// If one of these attributes are set to the first character of a
+// line it is considered as a line attribute marker i.e. attributes
+// set on this marker are applied to the whole line.
+// The list attribute is only maintained for compatibility reasons
+var lineAttributes = [lineMarkerAttribute,'list'];
+
+/*
+ The Attribute manager builds changesets based on a document
+ representation for setting and removing range or line-based attributes.
+
+ @param rep the document representation to be used
+ @param applyChangesetCallback this callback will be called
+ once a changeset has been built.
+*/
+
+var AttributeManager = function(rep, applyChangesetCallback)
+{
+ this.rep = rep;
+ this.applyChangesetCallback = applyChangesetCallback;
+ this.author = '';
+
+ // If the first char in a line has one of the following attributes
+ // it will be considered as a line marker
+};
+
+AttributeManager.prototype = _(AttributeManager.prototype).extend({
+
+ applyChangeset: function(changeset){
+ if(!this.applyChangesetCallback) return changeset;
+
+ var cs = changeset.toString();
+ if (!Changeset.isIdentity(cs))
+ {
+ this.applyChangesetCallback(cs);
+ }
+
+ return changeset;
+ },
+
+ /*
+ Sets attributes on a range
+ @param start [row, col] tuple pointing to the start of the range
+ @param end [row, col] tuple pointing to the end of the range
+ @param attribute: an array of attributes
+ */
+ setAttributesOnRange: function(start, end, attribs)
+ {
+ var builder = Changeset.builder(this.rep.lines.totalWidth());
+ ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, start);
+ ChangesetUtils.buildKeepRange(this.rep, builder, start, end, attribs, this.rep.apool);
+ return this.applyChangeset(builder);
+ },
+
+ /*
+ Returns if the line already has a line marker
+ @param lineNum: the number of the line
+ */
+ lineHasMarker: function(lineNum){
+ var that = this;
+
+ return _.find(lineAttributes, function(attribute){
+ return that.getAttributeOnLine(lineNum, attribute) != '';
+ }) !== undefined;
+ },
+
+ /*
+ Gets a specified attribute on a line
+ @param lineNum: the number of the line to set the attribute for
+ @param attributeKey: the name of the attribute to get, e.g. list
+ */
+ getAttributeOnLine: function(lineNum, attributeName){
+ // get `attributeName` attribute of first char of line
+ var aline = this.rep.alines[lineNum];
+ if (aline)
+ {
+ var opIter = Changeset.opIterator(aline);
+ if (opIter.hasNext())
+ {
+ return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
+ }
+ }
+ return '';
+ },
+
+ /*
+ Sets a specified attribute on a line
+ @param lineNum: the number of the line to set the attribute for
+ @param attributeKey: the name of the attribute to set, e.g. list
+ @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
+
+ */
+ setAttributeOnLine: function(lineNum, attributeName, attributeValue){
+ var loc = [0,0];
+ var builder = Changeset.builder(this.rep.lines.totalWidth());
+ var hasMarker = this.lineHasMarker(lineNum);
+
+ ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
+
+ if(hasMarker){
+ ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
+ [attributeName, attributeValue]
+ ], this.rep.apool);
+ }else{
+ // add a line marker
+ builder.insert('*', [
+ ['author', this.author],
+ ['insertorder', 'first'],
+ [lineMarkerAttribute, '1'],
+ [attributeName, attributeValue]
+ ], this.rep.apool);
+ }
+
+ return this.applyChangeset(builder);
+ },
+
+ /*
+ Removes a specified attribute on a line
+ @param lineNum: the number of the affected line
+ @param attributeKey: the name of the attribute to remove, e.g. list
+
+ */
+ removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
+
+ var loc = [0,0];
+ var builder = Changeset.builder(this.rep.lines.totalWidth());
+ var hasMarker = this.lineHasMarker(lineNum);
+
+ if(hasMarker){
+ ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]), [
+ [attributeName, attributeValue]
+ ], this.rep.apool);
+ ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1]));
+ }
+
+ return this.applyChangeset(builder);
+ },
+
+ /*
+ Sets a specified attribute on a line
+ @param lineNum: the number of the line to set the attribute for
+ @param attributeKey: the name of the attribute to set, e.g. list
+ @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
+ */
+ toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
+ return this.getAttributeOnLine(attributeName) ?
+ this.removeAttributeOnLine(lineNum, attributeName) :
+ this.setAttributeOnLine(lineNum, attributeName, attributeValue);
+
+ }
+});
+
+module.exports = AttributeManager; \ No newline at end of file
diff --git a/src/static/js/AttributePool.js b/src/static/js/AttributePool.js
index a9245daf..f5990c07 100644
--- a/src/static/js/AttributePool.js
+++ b/src/static/js/AttributePool.js
@@ -22,6 +22,12 @@
* limitations under the License.
*/
+/*
+ An AttributePool maintains a mapping from [key,value] Pairs called
+ Attributes to Numbers (unsigened integers) and vice versa. These numbers are
+ used to reference Attributes in Changesets.
+*/
+
var AttributePool = function () {
this.numToAttrib = {}; // e.g. {0: ['foo','bar']}
this.attribToNum = {}; // e.g. {'foo,bar': 0}
diff --git a/src/static/js/ChangesetUtils.js b/src/static/js/ChangesetUtils.js
new file mode 100644
index 00000000..e0b67881
--- /dev/null
+++ b/src/static/js/ChangesetUtils.js
@@ -0,0 +1,60 @@
+/**
+ * This module contains several helper Functions to build Changesets
+ * based on a SkipList
+ */
+
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+exports.buildRemoveRange = function(rep, builder, start, end)
+{
+ var startLineOffset = rep.lines.offsetOfIndex(start[0]);
+ var endLineOffset = rep.lines.offsetOfIndex(end[0]);
+
+ if (end[0] > start[0])
+ {
+ builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
+ builder.remove(end[1]);
+ }
+ else
+ {
+ builder.remove(end[1] - start[1]);
+ }
+}
+
+exports.buildKeepRange = function(rep, builder, start, end, attribs, pool)
+{
+ var startLineOffset = rep.lines.offsetOfIndex(start[0]);
+ var endLineOffset = rep.lines.offsetOfIndex(end[0]);
+
+ if (end[0] > start[0])
+ {
+ builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
+ builder.keep(end[1], 0, attribs, pool);
+ }
+ else
+ {
+ builder.keep(end[1] - start[1], 0, attribs, pool);
+ }
+}
+
+exports.buildKeepToStartOfRange = function(rep, builder, start)
+{
+ var startLineOffset = rep.lines.offsetOfIndex(start[0]);
+
+ builder.keep(startLineOffset, start[0]);
+ builder.keep(start[1]);
+}
+
diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js
index f8393d0b..99bbbb4f 100644
--- a/src/static/js/ace2_inner.js
+++ b/src/static/js/ace2_inner.js
@@ -35,6 +35,8 @@ var isNodeText = Ace2Common.isNodeText,
binarySearchInfinite = Ace2Common.binarySearchInfinite,
htmlPrettyEscape = Ace2Common.htmlPrettyEscape,
noop = Ace2Common.noop;
+ var hooks = require('./pluginfw/hooks');
+
function Ace2Inner(){
@@ -45,10 +47,12 @@ function Ace2Inner(){
var domline = require('./domline').domline;
var AttribPool = require('./AttributePool');
var Changeset = require('./Changeset');
+ var ChangesetUtils = require('./ChangesetUtils');
var linestylefilter = require('./linestylefilter').linestylefilter;
var SkipList = require('./skiplist');
var undoModule = require('./undomodule').undoModule;
var makeVirtualLineView = require('./virtual_lines').makeVirtualLineView;
+ var AttributeManager = require('./AttributeManager');
var DEBUG = false; //$$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;"
// changed to false
@@ -78,14 +82,11 @@ function Ace2Inner(){
var overlaysdiv = lineMetricsDiv.nextSibling;
initLineNumbers();
- var outsideKeyDown = function(evt)
- {};
- var outsideKeyPress = function(evt)
- {
- return true;
- };
- var outsideNotifyDirty = function()
- {};
+ var outsideKeyDown = noop;
+
+ var outsideKeyPress = function(){return true;};
+
+ var outsideNotifyDirty = noop;
// selFocusAtStart -- determines whether the selection extends "backwards", so that the focus
// point (controlled with the arrow keys) is at the beginning; not supported in IE, though
@@ -100,6 +101,7 @@ function Ace2Inner(){
alines: [],
apool: new AttribPool()
};
+
// lines, alltext, alines, and DOM are set up in setup()
if (undoModule.enabled)
{
@@ -119,6 +121,7 @@ function Ace2Inner(){
iframePadRight = 0;
var console = (DEBUG && window.console);
+ var documentAttributeManager;
if (!window.console)
{
@@ -155,6 +158,7 @@ function Ace2Inner(){
var textFace = 'monospace';
var textSize = 12;
+
function textLineHeight()
{
@@ -929,7 +933,10 @@ function Ace2Inner(){
},
grayedout: setClassPresenceNamed(outerWin.document.body, "grayedout"),
dmesg: function(){ dmesg = window.dmesg = value; },
- userauthor: function(value){ thisAuthor = String(value); },
+ userauthor: function(value){
+ thisAuthor = String(value);
+ documentAttributeManager.author = thisAuthor;
+ },
styled: setStyled,
textface: setTextFace,
textsize: setTextSize,
@@ -1864,55 +1871,6 @@ function Ace2Inner(){
}
}
-
- function setupMozillaCaretHack(lineNum)
- {
- // This is really ugly, but by god, it works!
- // Fixes annoying Firefox caret artifact (observed in 2.0.0.12
- // and unfixed in Firefox 2 as of now) where mutating the DOM
- // and then moving the caret to the beginning of a line causes
- // an image of the caret to be XORed at the top of the iframe.
- // The previous solution involved remembering to set the selection
- // later, in response to the next event in the queue, which was hugely
- // annoying.
- // This solution: add a space character (0x20) to the beginning of the line.
- // After setting the selection, remove the space.
- var lineNode = rep.lines.atIndex(lineNum).lineNode;
-
- var fc = lineNode.firstChild;
- while (isBlockElement(fc) && fc.firstChild)
- {
- fc = fc.firstChild;
- }
- var textNode;
- if (isNodeText(fc))
- {
- fc.nodeValue = " " + fc.nodeValue;
- textNode = fc;
- }
- else
- {
- textNode = doc.createTextNode(" ");
- fc.parentNode.insertBefore(textNode, fc);
- }
- markNodeClean(lineNode);
- return {
- unhack: function()
- {
- if (textNode.nodeValue == " ")
- {
- textNode.parentNode.removeChild(textNode);
- }
- else
- {
- textNode.nodeValue = textNode.nodeValue.substring(1);
- }
- markNodeClean(lineNode);
- }
- };
- }
-
-
function getPointForLineAndChar(lineAndChar)
{
var line = lineAndChar[0];
@@ -2247,6 +2205,9 @@ function Ace2Inner(){
}
+ /*
+ Converts the position of a char (index in String) into a [row, col] tuple
+ */
function lineAndColumnFromChar(x)
{
var lineEntry = rep.lines.atOffset(x);
@@ -2301,8 +2262,8 @@ function Ace2Inner(){
// CCCC\n
// end[0]: <CCC end[1] CCC>-------\n
var builder = Changeset.builder(rep.lines.totalWidth());
- buildKeepToStartOfRange(builder, start);
- buildRemoveRange(builder, start, end);
+ ChangesetUtils.buildKeepToStartOfRange(rep, builder, start);
+ ChangesetUtils.buildRemoveRange(rep, builder, start, end);
builder.insert(newText, [
['author', thisAuthor]
], rep.apool);
@@ -2313,69 +2274,17 @@ function Ace2Inner(){
function performDocumentApplyAttributesToCharRange(start, end, attribs)
{
- if (end >= rep.alltext.length)
- {
- end = rep.alltext.length - 1;
- }
- performDocumentApplyAttributesToRange(lineAndColumnFromChar(start), lineAndColumnFromChar(end), attribs);
+ end = Math.min(end, rep.alltext.length - 1);
+ documentAttributeManager.setAttributesOnRange(lineAndColumnFromChar(start), lineAndColumnFromChar(end), attribs);
}
editorInfo.ace_performDocumentApplyAttributesToCharRange = performDocumentApplyAttributesToCharRange;
-
- function performDocumentApplyAttributesToRange(start, end, attribs)
- {
- var builder = Changeset.builder(rep.lines.totalWidth());
- buildKeepToStartOfRange(builder, start);
- buildKeepRange(builder, start, end, attribs, rep.apool);
- var cs = builder.toString();
- performDocumentApplyChangeset(cs);
- }
- editorInfo.ace_performDocumentApplyAttributesToRange = performDocumentApplyAttributesToRange;
-
- function buildKeepToStartOfRange(builder, start)
- {
- var startLineOffset = rep.lines.offsetOfIndex(start[0]);
-
- builder.keep(startLineOffset, start[0]);
- builder.keep(start[1]);
- }
-
- function buildRemoveRange(builder, start, end)
- {
- var startLineOffset = rep.lines.offsetOfIndex(start[0]);
- var endLineOffset = rep.lines.offsetOfIndex(end[0]);
-
- if (end[0] > start[0])
- {
- builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
- builder.remove(end[1]);
- }
- else
- {
- builder.remove(end[1] - start[1]);
- }
- }
-
- function buildKeepRange(builder, start, end, attribs, pool)
- {
- var startLineOffset = rep.lines.offsetOfIndex(start[0]);
- var endLineOffset = rep.lines.offsetOfIndex(end[0]);
-
- if (end[0] > start[0])
- {
- builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
- builder.keep(end[1], 0, attribs, pool);
- }
- else
- {
- builder.keep(end[1] - start[1], 0, attribs, pool);
- }
- }
-
+
+
function setAttributeOnSelection(attributeName, attributeValue)
{
if (!(rep.selStart && rep.selEnd)) return;
- performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, [
+ documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
[attributeName, attributeValue]
]);
}
@@ -2436,13 +2345,13 @@ function Ace2Inner(){
if (selectionAllHasIt)
{
- performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, [
+ documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
[attributeName, '']
]);
}
else
{
- performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, [
+ documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
[attributeName, 'true']
]);
}
@@ -2964,6 +2873,10 @@ function Ace2Inner(){
"ul": 1
};
+ _.each(hooks.callAll('aceRegisterBlockElements'), function(element){
+ _blockElems[element] = 1;
+ })
+
function isBlockElement(n)
{
return !!_blockElems[(n.tagName || "").toLowerCase()];
@@ -3328,6 +3241,7 @@ function Ace2Inner(){
{
return;
}
+
var lineNum = rep.selStart[0];
var listType = getLineListType(lineNum);
@@ -3399,11 +3313,9 @@ function Ace2Inner(){
}
}
- if (mods.length > 0)
- {
- setLineListTypes(mods);
- }
-
+ _.each(mods, function(mod){
+ setLineListType(mod[0], mod[1]);
+ });
return true;
}
editorInfo.ace_doIndentOutdent = doIndentOutdent;
@@ -3818,26 +3730,17 @@ function Ace2Inner(){
return;
}
- var mozillaCaretHack = (false && browser.mozilla && selStart && selEnd && selStart[0] == selEnd[0] && selStart[1] == rep.lines.atIndex(selStart[0]).lineMarker && selEnd[1] == rep.lines.atIndex(selEnd[0]).lineMarker && setupMozillaCaretHack(selStart[0]));
-
var selection = {};
var ss = [selStart[0], selStart[1]];
- if (mozillaCaretHack) ss[1] += 1;
selection.startPoint = getPointForLineAndChar(ss);
var se = [selEnd[0], selEnd[1]];
- if (mozillaCaretHack) se[1] += 1;
selection.endPoint = getPointForLineAndChar(se);
selection.focusAtStart = !! rep.selFocusAtStart;
setSelection(selection);
-
- if (mozillaCaretHack)
- {
- mozillaCaretHack.unhack();
- }
}
function getRepHTML()
@@ -4926,27 +4829,30 @@ function Ace2Inner(){
}
}
}
-
+
+ var listAttributeName = 'list';
+
function getLineListType(lineNum)
{
- // get "list" attribute of first char of line
- var aline = rep.alines[lineNum];
- if (aline)
- {
- var opIter = Changeset.opIterator(aline);
- if (opIter.hasNext())
- {
- return Changeset.opAttributeValue(opIter.next(), 'list', rep.apool) || '';
- }
- }
- return '';
+ return documentAttributeManager.getAttributeOnLine(lineNum, listAttributeName)
}
function setLineListType(lineNum, listType)
{
- setLineListTypes([
- [lineNum, listType]
- ]);
+ if(listType == ''){
+ documentAttributeManager.removeAttributeOnLine(lineNum, listAttributeName);
+ }else{
+ documentAttributeManager.setAttributeOnLine(lineNum, listAttributeName, listType);
+ }
+
+ //if the list has been removed, it is necessary to renumber
+ //starting from the *next* line because the list may have been
+ //separated. If it returns null, it means that the list was not cut, try
+ //from the current one.
+ if(renumberList(lineNum+1)==null)
+ {
+ renumberList(lineNum);
+ }
}
function renumberList(lineNum){
@@ -4993,8 +4899,8 @@ function Ace2Inner(){
}
else if(curLevel == level)
{
- buildKeepRange(builder, loc, (loc = [line, 0]));
- buildKeepRange(builder, loc, (loc = [line, 1]), [
+ ChangesetUtils.buildKeepRange(rep, builder, loc, (loc = [line, 0]));
+ ChangesetUtils.buildKeepRange(rep, builder, loc, (loc = [line, 1]), [
['start', position]
], rep.apool);
@@ -5025,62 +4931,6 @@ function Ace2Inner(){
}
- function setLineListTypes(lineNumTypePairsInOrder)
- {
- var loc = [0, 0];
- var builder = Changeset.builder(rep.lines.totalWidth());
- for (var i = 0; i < lineNumTypePairsInOrder.length; i++)
- {
- var pair = lineNumTypePairsInOrder[i];
- var lineNum = pair[0];
- var listType = pair[1];
- buildKeepRange(builder, loc, (loc = [lineNum, 0]));
- if (getLineListType(lineNum))
- {
- // already a line marker
- if (listType)
- {
- // make different list type
- buildKeepRange(builder, loc, (loc = [lineNum, 1]), [
- ['list', listType]
- ], rep.apool);
- }
- else
- {
- // remove list marker
- buildRemoveRange(builder, loc, (loc = [lineNum, 1]));
- }
- }
- else
- {
- // currently no line marker
- if (listType)
- {
- // add a line marker
- builder.insert('*', [
- ['author', thisAuthor],
- ['insertorder', 'first'],
- ['list', listType]
- ], rep.apool);
- }
- }
- }
-
- var cs = builder.toString();
- if (!Changeset.isIdentity(cs))
- {
- performDocumentApplyChangeset(cs);
- }
-
- //if the list has been removed, it is necessary to renumber
- //starting from the *next* line because the list may have been
- //separated. If it returns null, it means that the list was not cut, try
- //from the current one.
- if(renumberList(lineNum+1)==null)
- {
- renumberList(lineNum);
- }
- }
function doInsertList(type)
{
@@ -5118,7 +4968,10 @@ function Ace2Inner(){
var t = getLineListType(n);
mods.push([n, allLinesAreList ? 'indent' + level : (t ? type + level : type + '1')]);
}
- setLineListTypes(mods);
+
+ _.each(mods, function(mod){
+ setLineListType(mod[0], mod[1]);
+ });
}
function doInsertUnorderedList(){
@@ -5538,6 +5391,11 @@ function Ace2Inner(){
}
}
}
+
+
+ // Init documentAttributeManager
+ documentAttributeManager = new AttributeManager(rep, performDocumentApplyChangeset);
+ editorInfo.ace_performDocumentApplyAttributesToRange = documentAttributeManager.setAttributesOnRange;
$(document).ready(function(){
doc = document; // defined as a var in scope outside
@@ -5577,7 +5435,13 @@ function Ace2Inner(){
bindTheEventHandlers();
});
-
+
+ hooks.callAll('aceInitialized', {
+ editorInfo: editorInfo,
+ rep: rep,
+ documentAttributeManager: documentAttributeManager
+ });
+
scheduler.setTimeout(function()
{
parent.readyFunc(); // defined in code that sets up the inner iframe