fix inserting snippets when multiple selections are present

This commit is contained in:
nightwing 2014-05-04 17:29:56 +04:00
commit feb500c86e
7 changed files with 159 additions and 70 deletions

View file

@ -51,7 +51,7 @@ var Autocomplete = function() {
this.changeTimer = lang.delayedCall(function() { this.changeTimer = lang.delayedCall(function() {
this.updateCompletions(true); this.updateCompletions(true);
}.bind(this)) }.bind(this));
}; };
(function() { (function() {
@ -270,7 +270,7 @@ var Autocomplete = function() {
var _id = this.gatherCompletionsId; var _id = this.gatherCompletionsId;
this.gatherCompletions(this.editor, function(err, results) { this.gatherCompletions(this.editor, function(err, results) {
// Only detach if result gathering is finished // Only detach if result gathering is finished
var doDetach = function() { var detachIfFinished = function() {
if (!results.finished) return; if (!results.finished) return;
return this.detach(); return this.detach();
}.bind(this); }.bind(this);
@ -279,10 +279,10 @@ var Autocomplete = function() {
var matches = results && results.matches; var matches = results && results.matches;
if (!matches || !matches.length) if (!matches || !matches.length)
return doDetach(); return detachIfFinished();
// Wrong prefix or wrong session -> ignore // Wrong prefix or wrong session -> ignore
if (prefix.indexOf(results.prefix) != 0 || _id != this.gatherCompletionsId) if (prefix.indexOf(results.prefix) !== 0 || _id != this.gatherCompletionsId)
return; return;
this.completions = new FilteredList(matches); this.completions = new FilteredList(matches);
@ -291,11 +291,11 @@ var Autocomplete = function() {
// No results // No results
if (!filtered.length) if (!filtered.length)
return doDetach(); return detachIfFinished();
// One result equals to the prefix // One result equals to the prefix
if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet) if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet)
return doDetach(); return detachIfFinished();
// Autoinsert if one result // Autoinsert if one result
if (this.autoInsert && filtered.length == 1) if (this.autoInsert && filtered.length == 1)

View file

@ -141,11 +141,15 @@ exports.commands = [{
name: "findnext", name: "findnext",
bindKey: bindKey("Ctrl-K", "Command-G"), bindKey: bindKey("Ctrl-K", "Command-G"),
exec: function(editor) { editor.findNext(); }, exec: function(editor) { editor.findNext(); },
multiSelectAction: "forEach",
scrollIntoView: "center",
readOnly: true readOnly: true
}, { }, {
name: "findprevious", name: "findprevious",
bindKey: bindKey("Ctrl-Shift-K", "Command-Shift-G"), bindKey: bindKey("Ctrl-Shift-K", "Command-Shift-G"),
exec: function(editor) { editor.findPrevious(); }, exec: function(editor) { editor.findPrevious(); },
multiSelectAction: "forEach",
scrollIntoView: "center",
readOnly: true readOnly: true
}, { }, {
name: "selectOrFindNext", name: "selectOrFindNext",

View file

@ -2363,8 +2363,9 @@ var Editor = function(renderer, session) {
var cursorLayer = this.renderer.$cursorLayer; var cursorLayer = this.renderer.$cursorLayer;
if (!cursorLayer) if (!cursorLayer)
return; return;
cursorLayer.setSmoothBlinking(style == "smooth"); cursorLayer.setSmoothBlinking(/smooth/.test(style));
cursorLayer.isBlinking = !this.$readOnly && style != "wide"; cursorLayer.isBlinking = !this.$readOnly && style != "wide";
dom.setCssClass(cursorLayer.element, "ace_slim-cursors", /slim/.test(style));
}; };
}).call(Editor.prototype); }).call(Editor.prototype);

View file

@ -143,15 +143,6 @@ var Keys = (function() {
} }
})(); })();
(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("-") + "-";
}
})();
return ret; return ret;
})(); })();
oop.mixin(exports, Keys); oop.mixin(exports, Keys);

View file

@ -464,36 +464,41 @@ var Editor = require("./editor").Editor;
/** /**
* Executes a command for each selection range. * Executes a command for each selection range.
* @param {String} cmd The command to execute * @param {Object} cmd The command to execute
* @param {String} args Any arguments for the command * @param {String} args Any arguments for the command
* @method Editor.forEachSelection * @method Editor.forEachSelection
**/ **/
this.forEachSelection = function(cmd, args, $byLines) { this.forEachSelection = function(cmd, args, options) {
if (this.inVirtualSelectionMode) if (this.inVirtualSelectionMode)
return; return;
var keepOrder = options && options.keepOrder;
var $byLines = options == true || options && options.$byLines
var session = this.session; var session = this.session;
var selection = this.selection; var selection = this.selection;
var rangeList = selection.rangeList; var rangeList = selection.rangeList;
var ranges = (keepOrder ? selection : rangeList).ranges;
var result; var result;
if (!ranges.length)
return cmd.exec ? cmd.exec(this, args || {}) : cmd(this, args || {});
var reg = selection._eventRegistry; var reg = selection._eventRegistry;
selection._eventRegistry = {}; selection._eventRegistry = {};
var tmpSel = new Selection(session); var tmpSel = new Selection(session);
this.inVirtualSelectionMode = true; this.inVirtualSelectionMode = true;
for (var i = rangeList.ranges.length; i--;) { for (var i = ranges.length; i--;) {
if ($byLines) { if ($byLines) {
while (i > 0 && rangeList.ranges[i].start.row == rangeList.ranges[i - 1].end.row) while (i > 0 && ranges[i].start.row == ranges[i - 1].end.row)
i--; i--;
} }
tmpSel.fromOrientedRange(rangeList.ranges[i]); tmpSel.fromOrientedRange(ranges[i]);
tmpSel.id = rangeList.ranges[i].marker; tmpSel.index = i;
this.selection = session.selection = tmpSel; this.selection = session.selection = tmpSel;
var cmdResult = cmd.exec(this, args || {}); var cmdResult = cmd.exec ? cmd.exec(this, args || {}) : cmd(this, args || {});
if (result !== undefined) if (!result && cmdResult !== undefined)
result = cmdResult; result = cmdResult;
tmpSel.toOrientedRange(rangeList.ranges[i]); tmpSel.toOrientedRange(ranges[i]);
} }
tmpSel.detach(); tmpSel.detach();
@ -599,6 +604,10 @@ var Editor = require("./editor").Editor;
for (var i = ranges.length; i--; ) for (var i = ranges.length; i--; )
selection.addRange(ranges[i], true); selection.addRange(ranges[i], true);
// keep old selection as primary if possible
if (range && selection.rangeList.rangeAtPoint(range.start))
selection.addRange(range, true);
this.$blockScrolling -= 1; this.$blockScrolling -= 1;
return ranges.length; return ranges.length;

View file

@ -30,8 +30,11 @@
define(function(require, exports, module) { define(function(require, exports, module) {
"use strict"; "use strict";
var lang = require("./lib/lang") var oop = require("./lib/oop");
var Range = require("./range").Range var EventEmitter = require("./lib/event_emitter").EventEmitter;
var lang = require("./lib/lang");
var Range = require("./range").Range;
var Anchor = require("./anchor").Anchor;
var HashHandler = require("./keyboard/hash_handler").HashHandler; var HashHandler = require("./keyboard/hash_handler").HashHandler;
var Tokenizer = require("./tokenizer").Tokenizer; var Tokenizer = require("./tokenizer").Tokenizer;
var comparePoints = Range.comparePoints; var comparePoints = Range.comparePoints;
@ -42,12 +45,14 @@ var SnippetManager = function() {
}; };
(function() { (function() {
oop.implement(this, EventEmitter);
this.getTokenizer = function() { this.getTokenizer = function() {
function TabstopToken(str, _, stack) { function TabstopToken(str, _, stack) {
str = str.substr(1); str = str.substr(1);
if (/^\d+$/.test(str) && !stack.inFormatString) if (/^\d+$/.test(str) && !stack.inFormatString)
return [{tabstopId: parseInt(str, 10)}]; return [{tabstopId: parseInt(str, 10)}];
return [{text: str}] return [{text: str}];
} }
function escape(ch) { function escape(ch) {
return "(?:[^\\\\" + ch + "]|\\\\.)"; return "(?:[^\\\\" + ch + "]|\\\\.)";
@ -125,7 +130,7 @@ var SnippetManager = function() {
}); });
SnippetManager.prototype.getTokenizer = function() { SnippetManager.prototype.getTokenizer = function() {
return SnippetManager.$tokenizer; return SnippetManager.$tokenizer;
} };
return SnippetManager.$tokenizer; return SnippetManager.$tokenizer;
}; };
@ -151,6 +156,7 @@ var SnippetManager = function() {
switch(name) { switch(name) {
case "CURRENT_WORD": case "CURRENT_WORD":
var r = s.getWordRange(); var r = s.getWordRange();
/* falls through */
case "SELECTION": case "SELECTION":
case "SELECTED_TEXT": case "SELECTED_TEXT":
return s.getTextRange(r); return s.getTextRange(r);
@ -166,7 +172,7 @@ var SnippetManager = function() {
return s.getUseSoftTabs() ? "YES" : "NO"; return s.getUseSoftTabs() ? "YES" : "NO";
case "TAB_SIZE": case "TAB_SIZE":
return s.getTabSize(); return s.getTabSize();
// defult but can't fill :( // default but can't fill :(
case "FILENAME": case "FILENAME":
case "FILEPATH": case "FILEPATH":
return ""; return "";
@ -262,7 +268,7 @@ var SnippetManager = function() {
return result; return result;
}; };
this.insertSnippet = function(editor, snippetText) { this.insertSnippetForSelection = function(editor, snippetText) {
var cursor = editor.getCursorPosition(); var cursor = editor.getCursorPosition();
var line = editor.session.getLine(cursor.row); var line = editor.session.getLine(cursor.row);
var tabString = editor.session.getTabString(); var tabString = editor.session.getTabString();
@ -313,7 +319,7 @@ var SnippetManager = function() {
tabstops.forEach(function(ts) {ts.length = 0}); tabstops.forEach(function(ts) {ts.length = 0});
var expanding = {}; var expanding = {};
function copyValue(val) { function copyValue(val) {
var copy = [] var copy = [];
for (var i = 0; i < val.length; i++) { for (var i = 0; i < val.length; i++) {
var p = val[i]; var p = val[i];
if (typeof p == "object") { if (typeof p == "object") {
@ -349,7 +355,7 @@ var SnippetManager = function() {
if (ts.indexOf(p) === -1) if (ts.indexOf(p) === -1)
ts.push(p); ts.push(p);
}; }
// convert to plain text // convert to plain text
var row = 0, column = 0; var row = 0, column = 0;
@ -373,8 +379,21 @@ var SnippetManager = function() {
var end = editor.session.replace(range, text); var end = editor.session.replace(range, text);
var tabstopManager = new TabstopManager(editor); var tabstopManager = new TabstopManager(editor);
tabstopManager.addTabstops(tabstops, range.start, end); var selectionId = editor.inVirtualSelectionMode && editor.selection.index;
tabstopManager.tabNext(); tabstopManager.addTabstops(tabstops, range.start, end, selectionId);
};
this.insertSnippet = function(editor, snippetText) {
var self = this;
if (editor.inVirtualSelectionMode)
return self.insertSnippetForSelection(editor, snippetText);
editor.forEachSelection(function() {
self.insertSnippetForSelection(editor, snippetText);
}, null, {keepOrder: true});
if (editor.tabstopManager)
editor.tabstopManager.tabNext();
}; };
this.$getScope = function(editor) { this.$getScope = function(editor) {
@ -384,7 +403,7 @@ var SnippetManager = function() {
// PHP is actually HTML // PHP is actually HTML
if (scope === "php" && !editor.session.$mode.inlinePhp) if (scope === "php" && !editor.session.$mode.inlinePhp)
scope = "html"; scope = "html";
var c = editor.getCursorPosition() var c = editor.getCursorPosition();
var state = editor.session.getState(c.row); var state = editor.session.getState(c.row);
if (typeof state === "object") { if (typeof state === "object") {
state = state[0]; state = state[0];
@ -413,7 +432,17 @@ var SnippetManager = function() {
return scopes; return scopes;
}; };
this.expandWithTab = function(editor) { this.expandWithTab = function(editor, options) {
var self = this;
var result = editor.forEachSelection(function() {
return self.expandSnippetForSelection(editor, options);
}, null, {keepOrder: true});
if (result && editor.tabstopManager)
editor.tabstopManager.tabNext();
return result;
};
this.expandSnippetForSelection = function(editor, options) {
var cursor = editor.getCursorPosition(); var cursor = editor.getCursorPosition();
var line = editor.session.getLine(cursor.row); var line = editor.session.getLine(cursor.row);
var before = line.substring(0, cursor.column); var before = line.substring(0, cursor.column);
@ -429,7 +458,8 @@ var SnippetManager = function() {
}, this); }, this);
if (!snippet) if (!snippet)
return false; return false;
if (options && options.dryRun)
return true;
editor.session.doc.removeInLine(cursor.row, editor.session.doc.removeInLine(cursor.row,
cursor.column - snippet.replaceBefore.length, cursor.column - snippet.replaceBefore.length,
cursor.column + snippet.replaceAfter.length cursor.column + snippet.replaceAfter.length
@ -437,7 +467,7 @@ var SnippetManager = function() {
this.variables.M__ = snippet.matchBefore; this.variables.M__ = snippet.matchBefore;
this.variables.T__ = snippet.matchAfter; this.variables.T__ = snippet.matchAfter;
this.insertSnippet(editor, snippet.content); this.insertSnippetForSelection(editor, snippet.content);
this.variables.M__ = this.variables.T__ = null; this.variables.M__ = this.variables.T__ = null;
return true; return true;
@ -469,7 +499,7 @@ var SnippetManager = function() {
var self = this; var self = this;
function wrapRegexp(src) { function wrapRegexp(src) {
if (src && !/^\^?\(.*\)\$?$|^\\b$/.test(src)) if (src && !/^\^?\(.*\)\$?$|^\\b$/.test(src))
src = "(?:" + src + ")" src = "(?:" + src + ")";
return src || ""; return src || "";
} }
@ -491,7 +521,7 @@ var SnippetManager = function() {
function addSnippet(s) { function addSnippet(s) {
if (!s.scope) if (!s.scope)
s.scope = scope || "_"; s.scope = scope || "_";
scope = s.scope scope = s.scope;
if (!snippetMap[scope]) { if (!snippetMap[scope]) {
snippetMap[scope] = []; snippetMap[scope] = [];
snippetNameMap[scope] = {}; snippetNameMap[scope] = {};
@ -517,12 +547,14 @@ var SnippetManager = function() {
s.endRe = guardedRegexp(s.endTrigger, s.endGuard, true); s.endRe = guardedRegexp(s.endTrigger, s.endGuard, true);
s.endTriggerRe = new RegExp(s.endTrigger, "", true); s.endTriggerRe = new RegExp(s.endTrigger, "", true);
}; }
if (snippets.content) if (snippets.content)
addSnippet(snippets); addSnippet(snippets);
else if (Array.isArray(snippets)) else if (Array.isArray(snippets))
snippets.forEach(addSnippet); snippets.forEach(addSnippet);
this._signal("registerSnippets", {scope: scope});
}; };
this.unregister = function(snippets, scope) { this.unregister = function(snippets, scope) {
var snippetMap = this.snippetMap; var snippetMap = this.snippetMap;
@ -551,7 +583,7 @@ var SnippetManager = function() {
while (m = re.exec(str)) { while (m = re.exec(str)) {
if (m[1]) { if (m[1]) {
try { try {
snippet = JSON.parse(m[1]) snippet = JSON.parse(m[1]);
list.push(snippet); list.push(snippet);
} catch (e) {} } catch (e) {}
} if (m[4]) { } if (m[4]) {
@ -604,9 +636,10 @@ var TabstopManager = function(editor) {
}; };
(function() { (function() {
this.attach = function(editor) { this.attach = function(editor) {
this.index = -1; this.index = 0;
this.ranges = []; this.ranges = [];
this.tabstops = []; this.tabstops = [];
this.$openTabstops = null;
this.selectedTabstop = null; this.selectedTabstop = null;
this.editor = editor; this.editor = editor;
@ -646,7 +679,7 @@ var TabstopManager = function(editor) {
} }
if (!this.$inChange && isRemove) { if (!this.$inChange && isRemove) {
var ts = this.selectedTabstop; var ts = this.selectedTabstop;
var changedOutside = !ts.some(function(r) { var changedOutside = ts && !ts.some(function(r) {
return comparePoints(r.start, start) <= 0 && comparePoints(r.end, end) >= 0; return comparePoints(r.start, start) <= 0 && comparePoints(r.end, end) >= 0;
}); });
if (changedOutside) if (changedOutside)
@ -658,7 +691,7 @@ var TabstopManager = function(editor) {
if (r.end.row < start.row) if (r.end.row < start.row)
continue; continue;
if (comparePoints(start, r.start) < 0 && comparePoints(end, r.end) > 0) { if (isRemove && comparePoints(start, r.start) < 0 && comparePoints(end, r.end) > 0) {
this.removeRange(r); this.removeRange(r);
i--; i--;
continue; continue;
@ -681,7 +714,7 @@ var TabstopManager = function(editor) {
}; };
this.updateLinkedFields = function() { this.updateLinkedFields = function() {
var ts = this.selectedTabstop; var ts = this.selectedTabstop;
if (!ts.hasLinkedRanges) if (!ts || !ts.hasLinkedRanges)
return; return;
this.$inChange = true; this.$inChange = true;
var session = this.editor.session; var session = this.editor.session;
@ -690,7 +723,7 @@ var TabstopManager = function(editor) {
var range = ts[i]; var range = ts[i];
if (!range.linked) if (!range.linked)
continue; continue;
var fmt = exports.snippetManager.tmStrFormat(text, range.original) var fmt = exports.snippetManager.tmStrFormat(text, range.original);
session.replace(range, fmt); session.replace(range, fmt);
} }
this.$inChange = false; this.$inChange = false;
@ -701,7 +734,7 @@ var TabstopManager = function(editor) {
}; };
this.onChangeSelection = function() { this.onChangeSelection = function() {
if (!this.editor) if (!this.editor)
return return;
var lead = this.editor.selection.lead; var lead = this.editor.selection.lead;
var anchor = this.editor.selection.anchor; var anchor = this.editor.selection.anchor;
var isEmpty = this.editor.selection.isEmpty(); var isEmpty = this.editor.selection.isEmpty();
@ -719,14 +752,17 @@ var TabstopManager = function(editor) {
this.detach(); this.detach();
}; };
this.tabNext = function(dir) { this.tabNext = function(dir) {
var max = this.tabstops.length - 1; var max = this.tabstops.length;
var index = this.index + (dir || 1); var index = this.index + (dir || 1);
index = Math.min(Math.max(index, 0), max); index = Math.min(Math.max(index, 1), max);
this.selectTabstop(index);
if (index == max) if (index == max)
index = 0;
this.selectTabstop(index);
if (index === 0)
this.detach(); this.detach();
}; };
this.selectTabstop = function(index) { this.selectTabstop = function(index) {
this.$openTabstops = null;
var ts = this.tabstops[this.index]; var ts = this.tabstops[this.index];
if (ts) if (ts)
this.addTabstopMarkers(ts); this.addTabstopMarkers(ts);
@ -744,6 +780,9 @@ var TabstopManager = function(editor) {
continue; continue;
sel.addRange(ts[i].clone(), true); sel.addRange(ts[i].clone(), true);
} }
// todo investigate why is this needed
if (sel.ranges[0])
sel.addRange(sel.ranges[0].clone());
} else { } else {
this.editor.selection.setRange(ts.firstNonLinked); this.editor.selection.setRange(ts.firstNonLinked);
} }
@ -751,6 +790,8 @@ var TabstopManager = function(editor) {
this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler); this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
}; };
this.addTabstops = function(tabstops, start, end) { this.addTabstops = function(tabstops, start, end) {
if (!this.$openTabstops)
this.$openTabstops = [];
// add final tabstop if missing // add final tabstop if missing
if (!tabstops[0]) { if (!tabstops[0]) {
var p = Range.fromPoints(end, end); var p = Range.fromPoints(end, end);
@ -761,33 +802,44 @@ var TabstopManager = function(editor) {
} }
var i = this.index; var i = this.index;
var arg = [i, 0]; var arg = [i + 1, 0];
var ranges = this.ranges; var ranges = this.ranges;
var editor = this.editor; tabstops.forEach(function(ts, index) {
tabstops.forEach(function(ts) { var dest = this.$openTabstops[index] || ts;
for (var i = ts.length; i--;) { for (var i = ts.length; i--;) {
var p = ts[i]; var p = ts[i];
var range = Range.fromPoints(p.start, p.end || p.start); var range = Range.fromPoints(p.start, p.end || p.start);
movePoint(range.start, start); movePoint(range.start, start);
movePoint(range.end, start); movePoint(range.end, start);
range.original = p; range.original = p;
range.tabstop = ts; range.tabstop = dest;
ranges.push(range); ranges.push(range);
ts[i] = range; if (dest != ts)
dest.unshift(range);
else
dest[i] = range;
if (p.fmtString) { if (p.fmtString) {
range.linked = true; range.linked = true;
ts.hasLinkedRanges = true; dest.hasLinkedRanges = true;
} else if (!ts.firstNonLinked) } else if (!dest.firstNonLinked)
ts.firstNonLinked = range; dest.firstNonLinked = range;
} }
if (!ts.firstNonLinked) if (!dest.firstNonLinked)
ts.hasLinkedRanges = false; dest.hasLinkedRanges = false;
arg.push(ts); if (dest === ts) {
this.addTabstopMarkers(ts); arg.push(dest);
this.$openTabstops[index] = dest;
}
this.addTabstopMarkers(dest);
}, this); }, this);
// tabstop 0 is the last one
if (arg.length > 2) {
// when adding new snippet inside existing one, make sure 0 tabstop is at the end
if (this.tabstops.length)
arg.push(arg.splice(2, 1)[0]); arg.push(arg.splice(2, 1)[0]);
this.tabstops.splice.apply(this.tabstops, arg); this.tabstops.splice.apply(this.tabstops, arg);
}
}; };
this.addTabstopMarkers = function(ts) { this.addTabstopMarkers = function(ts) {
@ -810,6 +862,13 @@ var TabstopManager = function(editor) {
i = this.ranges.indexOf(range); i = this.ranges.indexOf(range);
this.ranges.splice(i, 1); this.ranges.splice(i, 1);
this.editor.session.removeMarker(range.markerId); this.editor.session.removeMarker(range.markerId);
if (!range.tabstop.length) {
i = this.tabstops.indexOf(range.tabstop);
if (i != -1)
this.tabstops.splice(i, 1);
if (!this.tabstops.length)
this.detach();
}
}; };
this.keyboardHandler = new HashHandler(); this.keyboardHandler = new HashHandler();
@ -835,6 +894,19 @@ var TabstopManager = function(editor) {
}).call(TabstopManager.prototype); }).call(TabstopManager.prototype);
var changeTracker = {};
changeTracker.onChange = Anchor.prototype.onChange;
changeTracker.setPosition = function(row, column) {
this.pos.row = row;
this.pos.column = column;
};
changeTracker.update = function(pos, delta, $insertRight) {
this.$insertRight = $insertRight;
this.pos = pos;
this.onChange(delta);
};
var movePoint = function(point, diff) { var movePoint = function(point, diff) {
if (point.row == 0) if (point.row == 0)
point.column += diff.column; point.column += diff.column;
@ -860,4 +932,14 @@ require("./lib/dom").importCssString("\
exports.snippetManager = new SnippetManager(); exports.snippetManager = new SnippetManager();
var Editor = require("./editor").Editor;
(function() {
this.insertSnippet = function(content, options) {
return exports.snippetManager.insertSnippet(this, content, options);
};
this.expandSnippet = function(options) {
return exports.snippetManager.expandWithTab(this, options);
};
}).call(Editor.prototype);
}); });

View file

@ -149,7 +149,9 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) {
// TODO: cleanup event // TODO: cleanup event
this.$worker.postMessage({event: event, data: {data: data.data}}); this.$worker.postMessage({event: event, data: {data: data.data}});
} }
catch(ex) {} catch(ex) {
console.error(ex.stack);
}
}; };
this.attachToDocument = function(doc) { this.attachToDocument = function(doc) {