Modules (188)

Document

Description

Dependencies

Functions

oneOrEach

Like _.each(), but if given a single item not in an array, acts as if it were an array containing just that item.

    function oneOrEach(itemOrArr, cb) {
        if (Array.isArray(itemOrArr)) {
            _.each(itemOrArr, cb);
        } else {
            cb(itemOrArr, 0);
        }
    }

Classes

Constructor

Document

Model for the contents of a single file and its current modification state. See DocumentManager documentation for important usage notes.

Document dispatches these events:

change -- When the text of the editor changes (including due to undo/redo).

Passes ({Document}, {ChangeList}), where ChangeList is an array of change record objects. Each change record looks like:

{ from: start of change, expressed as {line: <line number>, ch: <character offset>},
  to: end of change, expressed as {line: <line number>, ch: <chracter offset>},
  text: array of lines of text to replace existing text }

The line and ch offsets are both 0-based.

The ch offset in "from" is inclusive, but the ch offset in "to" is exclusive. For example, an insertion of new content (without replacing existing content) is expressed by a range where from and to are the same.

If "from" and "to" are undefined, then this is a replacement of the entire text content.

IMPORTANT: If you listen for the "change" event, you MUST also addRef() the document (and releaseRef() it whenever you stop listening). You should also listen to the "deleted" event.

deleted -- When the file for this document has been deleted. All views onto the document should be closed. The document will no longer be editable or dispatch "change" events.

languageChanged -- When the value of getLanguage() has changed. 2nd argument is the old value, 3rd argument is the new value.

file File
Need not lie within the project.
initialTimestamp Date
File's timestamp when we read it off disk.
rawText string
Text content of the file.
    function Document(file, initialTimestamp, rawText) {
        this.file = file;
        this.editable = !file.readOnly;
        this._updateLanguage();
        this.refreshText(rawText, initialTimestamp, true);
        // List of full editors which are initialized as master editors for this doc.
        this._associatedFullEditors = [];
    }

    EventDispatcher.makeEventDispatcher(Document.prototype);

Properties

Private

_lineEndings

The content's line-endings style. If a Document is created on empty text, or text with inconsistent line endings, defaults to the current platform's standard endings.

Type
FileUtils.LINE_ENDINGS_CRLF, FileUtils.LINE_ENDINGS_LF
    Document.prototype._lineEndings = null;
Private

_masterEditor

Editor object representing the full-size editor UI for this document. May be null if Document has not yet been modified or been the currentDocument; in that case, our backing model is the string _text.

Type
?Editor
    Document.prototype._masterEditor = null;
Private

_refCount

Number of clients who want this Document to stay alive. The Document is listed in DocumentManager._openDocuments whenever refCount > 0.

    Document.prototype._refCount = 0;
Private

_refreshInProgress

True while refreshText() is in progress and change notifications shouldn't trip the dirty flag.

Type
boolean
    Document.prototype._refreshInProgress = false;
Private

_text

The text contents of the file, or null if our backing model is _masterEditor.

Type
?string
    Document.prototype._text = null;

diskTimestamp

What we expect the file's timestamp to be on disk. If the timestamp differs from this, then it means the file was modified by an app other than Brackets.

Type
!Date
    Document.prototype.diskTimestamp = null;

file

The File for this document. Need not lie within the project. If Document is untitled, this is an InMemoryFile object.

Type
!File
    Document.prototype.file = null;

isDirty

Whether this document has unsaved changes or not. When this changes on any Document, DocumentManager dispatches a "dirtyFlagChange" event.

Type
boolean
    Document.prototype.isDirty = false;

isSaving

Whether this document is currently being saved.

Type
boolean
    Document.prototype.isSaving = false;

keepChangesTime

The timestamp of the document at the point where the user last said to keep changes that conflict with the current disk version. Can also be -1, indicating that the file was deleted on disk at the last point when the user said to keep changes, or null, indicating that the user has not said to keep changes. Note that this is a time as returned by Date.getTime(), not a Date object.

Type
?Number
    Document.prototype.keepChangesTime = null;

language

The Language for this document. Will be resolved by file extension in the constructor

Type
!Language
    Document.prototype.language = null;

Methods

Private

_associateEditor

Aassociates a full editor to this document To be used internally by Editor only when pane marking happens

    Document.prototype._associateEditor = function (editor) {
        // Do a check before processing the request to ensure inline editors are not being handled
        if (this._associatedFullEditors.indexOf(editor) === -1) {
            this._associatedFullEditors.push(editor);
        }
    };
Private

_checkAssociatedEditorForPane

Checks and returns if a full editor exists for the provided pane attached to this document

paneId String
Returns: Editor
Attached editor bound to the provided pane id
    Document.prototype._checkAssociatedEditorForPane = function (paneId) {
        var editorCount, editorForPane;
        for (editorCount = 0; editorCount < this._associatedFullEditors.length; ++editorCount) {
            if (this._associatedFullEditors[editorCount]._paneId === paneId) {
                editorForPane = this._associatedFullEditors[editorCount];
                break;
            }
        }

        return editorForPane;
    };
Private

_disassociateEditor

Disassociates an editor from this document if present in the associated editor list To be used internally by Editor only when destroyed and not the current master editor for the document

    Document.prototype._disassociateEditor = function (editor) {
        // Do a check before processing the request to ensure inline editors are not being handled
        if (this._associatedFullEditors.indexOf(editor) >= 0) {
            this._associatedFullEditors.splice(this._associatedFullEditors.indexOf(editor), 1);
        }
    };
Private

_ensureMasterEditor

Guarantees that _masterEditor is non-null. If needed, asks EditorManager to create a new master editor bound to this Document (which in turn causes Document._makeEditable() to be called). Should ONLY be called by Editor and Document.

    Document.prototype._ensureMasterEditor = function () {
        if (!this._masterEditor) {
            EditorManager._createUnattachedMasterEditor(this);
        }
    };
Private

_handleEditorChange

Handles changes from the master backing Editor. Changes are triggered either by direct edits to that Editor's UI, OR by our setText()/refreshText() methods.

    Document.prototype._handleEditorChange = function (event, editor, changeList) {
        // Handle editor change event only when it is originated from the master editor for this doc
        if (this._masterEditor !== editor) {
            return;
        }

        // TODO: This needs to be kept in sync with SpecRunnerUtils.createMockActiveDocument(). In the
        // future, we should fix things so that we either don't need mock documents or that this
        // is factored so it will just run in both.
        if (!this._refreshInProgress) {
            // Sync isDirty from CodeMirror state
            var wasDirty = this.isDirty;
            this.isDirty = !editor._codeMirror.isClean();

            // Notify if isDirty just changed (this also auto-adds us to working set if needed)
            if (wasDirty !== this.isDirty) {
                exports.trigger("_dirtyFlagChange", this);
            }
        }

        // Notify that Document's text has changed
        this._notifyDocumentChange(changeList);
    };
Private

_makeEditable

Attach a backing Editor to the Document, enabling setText() to be called. Assumes Editor has already been initialized with the value of getText(). ONLY Editor should call this (and only when EditorManager has told it to act as the master editor).

masterEditor non-nullable Editor
    Document.prototype._makeEditable = function (masterEditor) {

        this._text = null;
        this._masterEditor = masterEditor;

        masterEditor.on("change", this._handleEditorChange.bind(this));
    };
Private

_makeNonEditable

Detach the backing Editor from the Document, disallowing setText(). The text content is stored back onto _text so other Document clients continue to have read-only access. ONLY Editor.destroy() should call this.

    Document.prototype._makeNonEditable = function () {
        if (!this._masterEditor) {
            console.error("Document is already non-editable");
        } else {
            // _text represents the raw text, so fetch without normalized line endings
            this._text = this.getText(true);
            this._associatedFullEditors.splice(this._associatedFullEditors.indexOf(this._masterEditor), 1);

            // Identify the most recently created full editor before this and set that as new master editor
            if (this._associatedFullEditors.length > 0) {
                this._masterEditor = this._associatedFullEditors[this._associatedFullEditors.length - 1];
            } else {
                this._masterEditor = null;
            }
        }
    };
Private

_markClean

    Document.prototype._markClean = function () {
        this.isDirty = false;
        if (this._masterEditor) {
            this._masterEditor._codeMirror.markClean();
        }
        exports.trigger("_dirtyFlagChange", this);
    };
Private

_notifyDocumentChange

changeList Object
Changelist in CodeMirror format
    Document.prototype._notifyDocumentChange = function (changeList) {
        this.trigger("change", this, changeList);
        exports.trigger("documentChange", this, changeList);
    };
Private

_notifyFilePathChanged

Called when Document.file has been modified (due to a rename)

    Document.prototype._notifyFilePathChanged = function () {
        // File extension may have changed
        this._updateLanguage();
    };
Private

_toggleMasterEditor

Toggles the master editor which has gained focus from a pool of full editors To be used internally by Editor only

    Document.prototype._toggleMasterEditor = function (masterEditor) {
        // Do a check before processing the request to ensure inline editors are not being set as master editor
        if (this.file === masterEditor.document.file && this._associatedFullEditors.indexOf(masterEditor) >= 0) {
            this._masterEditor = masterEditor;
        }
    };
Private

_updateLanguage

Updates the language to match the current mapping given by LanguageManager

    Document.prototype._updateLanguage = function () {
        var oldLanguage = this.language;
        this.language = LanguageManager.getLanguageForPath(this.file.fullPath);
        if (oldLanguage && oldLanguage !== this.language) {
            this.trigger("languageChanged", oldLanguage, this.language);
        }
    };
Private

_updateTimestamp

    Document.prototype._updateTimestamp = function (timestamp) {
        this.diskTimestamp = timestamp;
        // Clear the "keep changes" timestamp since it's no longer relevant.
        this.keepChangesTime = null;
    };

addRef

Add a ref to keep this Document alive

    Document.prototype.addRef = function () {
        //console.log("+++REF+++ "+this);

        if (this._refCount === 0) {
            //console.log("+++ adding to open list");
            if (exports.trigger("_afterDocumentCreate", this)) {
                return;
            }
        }
        this._refCount++;
    };

adjustPosForChange

Adjusts a given position taking a given replaceRange-type edit into account. If the position is within the original edit range (start and end inclusive), it gets pushed to the end of the content that replaced the range. Otherwise, if it's after the edit, it gets adjusted so it refers to the same character it did before the edit.

pos non-nullable {line:number, ch: number}
The position to adjust.
textLines non-nullable Array.<string>
The text of the change, split into an array of lines.
start non-nullable {line: number, ch: number}
The start of the edit.
end non-nullable {line: number, ch: number}
The end of the edit.
Returns: {line: number,ch: number}
The adjusted position.
    Document.prototype.adjustPosForChange = function (pos, textLines, start, end) {
        // Same as CodeMirror.adjustForChange(), but that's a private function
        // and Marijn would rather not expose it publicly.
        var change = { text: textLines, from: start, to: end };

        if (CodeMirror.cmpPos(pos, start) < 0) {
            return pos;
        }
        if (CodeMirror.cmpPos(pos, end) <= 0) {
            return CodeMirror.changeEnd(change);
        }

        var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1,
            ch = pos.ch;
        if (pos.line === change.to.line) {
            ch += CodeMirror.changeEnd(change).ch - change.to.ch;
        }
        return {line: line, ch: ch};
    };

batchOperation

Batches a series of related Document changes. Repeated calls to replaceRange() should be wrapped in a batch for efficiency. Begins the batch, calls doOperation(), ends the batch, and then returns.

doOperation function()
    Document.prototype.batchOperation = function (doOperation) {
        this._ensureMasterEditor();

        var self = this;
        self._masterEditor._codeMirror.operation(doOperation);
    };

doMultipleEdits

Helper function for edit operations that operate on multiple selections. Takes an "edit list" that specifies a list of replaceRanges that should occur, but where all the positions are with respect to the document state before all the edits (i.e., you don't have to figure out how to fix up the selections after each sub-edit). Edits must be non-overlapping (in original-document terms). All the edits are done in a single batch.

If your edits are structured in such a way that each individual edit would cause its associated selection to be properly updated, then all you need to specify are the edits themselves, and the selections will automatically be updated as the edits are performed. However, for some kinds of edits, you need to fix up the selection afterwards. In that case, you can specify one or more selections to be associated with each edit. Those selections are assumed to be in terms of the document state after the edit, as if that edit were the only one being performed (i.e., you don't have to worry about adjusting for the effect of other edits). If you supply these selections, then this function will adjust them as necessary for the effects of other edits, and then return a flat list of all the selections, suitable for passing to setSelections().

| non-nullable Array.<{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}
Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, selection: ?{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>} | ?Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}>} edits Specifies the list of edits to perform in a manner similar to CodeMirror's `replaceRange`. This array will be mutated. `edit` is the edit to perform: `text` will replace the current contents of the range between `start` and `end`. If `end` is unspecified, the text is inserted at `start`. `start` and `end` should be positions relative to the document *ignoring* all other edit descriptions (i.e., as if you were only performing this one edit on the document). If any of the edits overlap, an error will be thrown. If `selection` is specified, it should be a selection associated with this edit. If `isBeforeEdit` is set on the selection, the selection will be fixed up for this edit. If not, it won't be fixed up for this edit, meaning it should be expressed in terms of the document state after this individual edit is performed (ignoring any other edits). Note that if you were planning on just specifying `isBeforeEdit` for every selection, you can accomplish the same thing by simply not passing any selections and letting the editor update the existing selections automatically. Note that `edit` and `selection` can each be either an individual edit/selection, or a group of edits/selections to apply in order. This can be useful if you need to perform multiple edits in a row and then specify a resulting selection that shouldn't be fixed up for any of those edits (but should be fixed up for edits related to other selections). It can also be useful if you have several selections that should ignore the effects of a given edit because you've fixed them up already (this commonly happens with line-oriented edits where multiple cursors on the same line should be ignored, but still tracked). Within an edit group, edit positions must be specified relative to previous edits within that group. Also, the total bounds of edit groups must not overlap (e.g. edits in one group can't surround an edit from another group).
origin nullable string
An optional edit origin that's passed through to each replaceRange().
Returns: Array<{start:{line:number,ch:number},end:{line:number,ch:number},primary:boolean,reversed: boolean}>
The list of passed selections adjusted for the performed edits, if any.
    Document.prototype.doMultipleEdits = function (edits, origin) {
        var self = this;

        // Sort the edits backwards, so we don't have to adjust the edit positions as we go along
        // (though we do have to adjust the selection positions).
        edits.sort(function (editDesc1, editDesc2) {
            var edit1 = (Array.isArray(editDesc1.edit) ? editDesc1.edit[0] : editDesc1.edit),
                edit2 = (Array.isArray(editDesc2.edit) ? editDesc2.edit[0] : editDesc2.edit);
            // Treat all no-op edits as if they should happen before all other edits (the order
            // doesn't really matter, as long as they sort out of the way of the real edits).
            if (!edit1) {
                return -1;
            } else if (!edit2) {
                return 1;
            } else {
                return CodeMirror.cmpPos(edit2.start, edit1.start);
            }
        });

        // Pull out the selections, in the same order as the edits.
        var result = _.cloneDeep(_.pluck(edits, "selection"));

        // Preflight the edits to specify "end" if unspecified and make sure they don't overlap.
        // (We don't want to do it during the actual edits, since we don't want to apply some of
        // the edits before we find out.)
        _.each(edits, function (editDesc, index) {
            oneOrEach(editDesc.edit, function (edit) {
                if (edit) {
                    if (!edit.end) {
                        edit.end = edit.start;
                    }
                    if (index > 0) {
                        var prevEditGroup = edits[index - 1].edit;
                        // The edits are in reverse order, so we want to make sure this edit ends
                        // before any of the previous ones start.
                        oneOrEach(prevEditGroup, function (prevEdit) {
                            if (CodeMirror.cmpPos(edit.end, prevEdit.start) > 0) {
                                throw new Error("Document.doMultipleEdits(): Overlapping edits specified");
                            }
                        });
                    }
                }
            });
        });

        // Perform the edits.
        this.batchOperation(function () {
            _.each(edits, function (editDesc, index) {
                // Perform this group of edits. The edit positions are guaranteed to be okay
                // since all the previous edits we've done have been later in the document. However,
                // we have to fix up any selections that overlap or come after the edit.
                oneOrEach(editDesc.edit, function (edit) {
                    if (edit) {
                        self.replaceRange(edit.text, edit.start, edit.end, origin);

                        // Fix up all the selections *except* the one(s) related to this edit list that
                        // are not "before-edit" selections.
                        var textLines = edit.text.split("\n");
                        _.each(result, function (selections, selIndex) {
                            if (selections) {
                                oneOrEach(selections, function (sel) {
                                    if (sel.isBeforeEdit || selIndex !== index) {
                                        sel.start = self.adjustPosForChange(sel.start, textLines, edit.start, edit.end);
                                        sel.end = self.adjustPosForChange(sel.end, textLines, edit.start, edit.end);
                                    }
                                });
                            }
                        });
                    }
                });
            });
        });

        result = _.chain(result)
            .filter(function (item) {
                return item !== undefined;
            })
            .flatten()
            .sort(function (sel1, sel2) {
                return CodeMirror.cmpPos(sel1.start, sel2.start);
            })
            .value();
        _.each(result, function (item) {
            delete item.isBeforeEdit;
        });
        return result;
    };

getLanguage

Returns the language this document is written in. The language returned is based on the file extension.

Returns: Language
An object describing the language used in this document
    Document.prototype.getLanguage = function () {
        return this.language;
    };

getLine

Returns the text of the given line (excluding any line ending characters)

Zero-based number
line number
Returns: !string
    Document.prototype.getLine = function (lineNum) {
        this._ensureMasterEditor();
        return this._masterEditor._codeMirror.getLine(lineNum);
    };

getRange

Returns the characters in the given range. Line endings are normalized to '\n'.

start non-nullable {line:number, ch:number}
Start of range, inclusive
end non-nullable {line:number, ch:number}
End of range, exclusive
Returns: !string
    Document.prototype.getRange = function (start, end) {
        this._ensureMasterEditor();
        return this._masterEditor._codeMirror.getRange(start, end);
    };

getText

Returns the document's current contents; may not be saved to disk yet. Whenever this value changes, the Document dispatches a "change" event.

useOriginalLineEndings optional boolean
If true, line endings in the result depend on the Document's line endings setting (based on OS & the original text loaded from disk). If false, line endings are always \n (like all the other Document text getter methods).
Returns: string
    Document.prototype.getText = function (useOriginalLineEndings) {
        if (this._masterEditor) {
            // CodeMirror.getValue() always returns text with LF line endings; fix up to match line
            // endings preferred by the document, if necessary
            var codeMirrorText = this._masterEditor._codeMirror.getValue();
            if (useOriginalLineEndings) {
                if (this._lineEndings === FileUtils.LINE_ENDINGS_CRLF) {
                    return codeMirrorText.replace(/\n/g, "\r\n");
                }
            }
            return codeMirrorText;

        } else {
            // Optimized path that doesn't require creating master editor
            if (useOriginalLineEndings) {
                return this._text;
            } else {
                return Document.normalizeText(this._text);
            }
        }
    };

isUntitled

Is this an untitled document?

Returns: boolean
- whether or not the document is untitled
    Document.prototype.isUntitled = function () {
        return this.file instanceof InMemoryFile;
    };

normalizeText STATIC

Normalizes line endings the same way CodeMirror would

    Document.normalizeText = function (text) {
        return text.replace(/\r\n/g, "\n");
    };

notifySaved

Called when the document is saved (which currently happens in DocumentCommandHandlers). Marks the document not dirty and notifies listeners of the save.

    Document.prototype.notifySaved = function () {
        if (!this._masterEditor) {
            console.log("### Warning: saving a Document that is not modifiable!");
        }

        this._markClean();

        // TODO: (issue #295) fetching timestamp async creates race conditions (albeit unlikely ones)
        var thisDoc = this;
        this.file.stat(function (err, stat) {
            if (!err) {
                thisDoc._updateTimestamp(stat.mtime);
            } else {
                console.log("Error updating timestamp after saving file: " + thisDoc.file.fullPath);
            }
            exports.trigger("_documentSaved", thisDoc);
        });
    };

refreshText

Sets the contents of the document. Treated as reloading the document from disk: the document will be marked clean with a new timestamp, the undo/redo history is cleared, and we re-check the text's line-ending style. CAN be called even if there is no backing editor.

text non-nullable string
The text to replace the contents of the document with.
newTimestamp non-nullable Date
Timestamp of file at the time we read its new contents from disk.
initial boolean
True if this is the initial load of the document. In that case, we don't send change events.
    Document.prototype.refreshText = function (text, newTimestamp, initial) {
        var perfTimerName = PerfUtils.markStart("refreshText:\t" + (!this.file || this.file.fullPath));

        // If clean, don't transiently mark dirty during refresh
        // (we'll still send change events though, of course)
        this._refreshInProgress = true;

        if (this._masterEditor) {
            this._masterEditor._resetText(text);  // clears undo history too
            // _handleEditorChange() triggers "change" event for us
        } else {
            this._text = text;

            if (!initial) {
                // We fake a change record here that looks like CodeMirror's text change records, but
                // omits "from" and "to", by which we mean the entire text has changed.
                // TODO: Dumb to split it here just to join it again in the change handler, but this is
                // the CodeMirror change format. Should we document our change format to allow this to
                // either be an array of lines or a single string?
                this._notifyDocumentChange([{text: text.split(/\r?\n/)}]);
            }
        }
        this._updateTimestamp(newTimestamp);

        // If Doc was dirty before refresh, reset it to clean now (don't always call, to avoid no-op dirtyFlagChange events) Since
        // _resetText() above already ensures Editor state is clean, it's safe to skip _markClean() as long as our own state is already clean too.
        if (this.isDirty) {
            this._markClean();
        }
        this._refreshInProgress = false;

        // Sniff line-ending style
        this._lineEndings = FileUtils.sniffLineEndings(text);
        if (!this._lineEndings) {
            this._lineEndings = FileUtils.getPlatformLineEndings();
        }

        exports.trigger("_documentRefreshed", this);

        PerfUtils.addMeasurement(perfTimerName);
    };

releaseRef

Remove a ref that was keeping this Document alive

    Document.prototype.releaseRef = function () {
        //console.log("---REF--- "+this);

        this._refCount--;
        if (this._refCount < 0) {
            console.error("Document ref count has fallen below zero!");
            return;
        }
        if (this._refCount === 0) {
            //console.log("--- removing from open list");
            if (exports.trigger("_beforeDocumentDelete", this)) {
                return;
            }
        }
    };

reload

Reloads the document from FileSystem @return {promise} - to check if reload was successful or not

    Document.prototype.reload = function () {
        var $deferred = $.Deferred();
        var self = this;
        FileUtils.readAsText(this.file)
            .done(function (text, readTimestamp) {
                self.refreshText(text, readTimestamp);
                $deferred.resolve();
            })
            .fail(function (error) {
                console.log("Error reloading contents of " + self.file.fullPath, error);
                $deferred.reject();
            });
        return $deferred.promise();
    };

    // We dispatch events from the module level, and the instance level. Instance events are wired up
    // in the Document constructor.
    EventDispatcher.makeEventDispatcher(exports);

    // Define public API
    exports.Document = Document;
});

replaceRange

Adds, replaces, or removes text. If a range is given, the text at that range is replaced with the given new text; if text == "", then the entire range is effectively deleted. If 'end' is omitted, then the new text is inserted at that point and all existing text is preserved. Line endings will be rewritten to match the document's current line-ending style.

IMPORTANT NOTE: Because of #1688, do not use this in cases where you might be operating on a linked document (like the main document for an inline editor) during an outer CodeMirror operation (like a key event that's handled by the editor itself). A common case of this is code hints in inline editors. In such cases, use editor._codeMirror.replaceRange() instead. This should be fixed when we migrate to use CodeMirror's native document-linking functionality.

text non-nullable string
Text to insert or replace the range with
start non-nullable {line:number, ch:number}
Start of range, inclusive (if 'to' specified) or insertion point (if not)
end nullable {line:number, ch:number}
End of range, exclusive; optional
origin nullable string
Optional string used to batch consecutive edits for undo. If origin starts with "+", then consecutive edits with the same origin will be batched for undo if they are close enough together in time. If origin starts with "*", then all consecutive edit with the same origin will be batched for undo. Edits with origins starting with other characters will not be batched. (Note that this is a higher level of batching than batchOperation(), which already batches all edits within it for undo. Origin batching works across operations.)
    Document.prototype.replaceRange = function (text, start, end, origin) {
        this._ensureMasterEditor();
        this._masterEditor._codeMirror.replaceRange(text, start, end, origin);
        // _handleEditorChange() triggers "change" event
    };

setText

Sets the contents of the document. Treated as an edit. Line endings will be rewritten to match the document's current line-ending style.

text non-nullable string
The text to replace the contents of the document with.
    Document.prototype.setText = function (text) {
        this._ensureMasterEditor();
        this._masterEditor._codeMirror.setValue(text);
        // _handleEditorChange() triggers "change" event
    };

toString

(pretty toString(), to aid debugging)

    Document.prototype.toString = function () {
        var dirtyInfo = (this.isDirty ? " (dirty!)" : " (clean)");
        var editorInfo = (this._masterEditor ? " (Editable)" : " (Non-editable)");
        var refInfo = " refs:" + this._refCount;
        return "[Document " + this.file.fullPath + dirtyInfo + editorInfo + refInfo + "]";
    };