kopia lustrzana https://github.com/c9/core
612 wiersze
26 KiB
JavaScript
612 wiersze
26 KiB
JavaScript
define(function(require, module, exports) {
|
|
main.consumes = ["Plugin", "UndoManager", "util", "error_handler"];
|
|
main.provides = ["Document"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var Plugin = imports.Plugin;
|
|
var util = imports.util;
|
|
var UndoManager = imports.UndoManager;
|
|
var reportError = imports.error_handler.reportError;
|
|
|
|
function Document(options) {
|
|
var plugin = new Plugin("Ajax.org", main.consumes);
|
|
var undoManager;
|
|
var emit = plugin.getEmitter();
|
|
|
|
if (options && options.undoManager instanceof Plugin) {
|
|
undoManager = options.undoManager;
|
|
delete options.undoManager;
|
|
}
|
|
else
|
|
undoManager = new UndoManager();
|
|
|
|
var sessions = {};
|
|
var value = options && options.value || "";
|
|
var changed = false;
|
|
var meta = {};
|
|
var hasValue = options && (typeof options.value === "string");
|
|
|
|
var tab, lastState, title, tooltip, editor, recentValue, ready;
|
|
|
|
plugin.on("newListener", function(type, listener) {
|
|
if (type == "state.set" && lastState) {
|
|
listener({
|
|
doc: plugin,
|
|
state: lastState
|
|
});
|
|
}
|
|
});
|
|
|
|
// Listen to changes and detect when the value of the editor
|
|
// is different from what is on disk
|
|
function initUndo(){
|
|
undoManager.on("change", function(e) {
|
|
var c = !undoManager.isAtBookmark();
|
|
if (changed !== c) {
|
|
changed = c;
|
|
emit("changed", { changed: c });
|
|
}
|
|
});
|
|
}
|
|
initUndo();
|
|
|
|
/***** Methods *****/
|
|
|
|
function setBookmarkedValue(value, cleansed) {
|
|
if (plugin.value == value) {
|
|
recentValue = value;
|
|
undoManager.bookmark();
|
|
return;
|
|
}
|
|
var state = getState();
|
|
|
|
// Record value (which should add an undo stack item)
|
|
plugin.value = value;
|
|
|
|
// Bookmark the undo manager
|
|
undoManager.bookmark();
|
|
|
|
// Update state
|
|
delete state.changed;
|
|
delete state.value;
|
|
delete state.meta;
|
|
state.undoManager = undoManager.getState();
|
|
|
|
if (cleansed && editor && state[editor.type])
|
|
state[editor.type].cleansed = true;
|
|
|
|
// Set new state (preserving original state)
|
|
if (emit("mergeState") !== false)
|
|
setState(state);
|
|
}
|
|
|
|
function getState(filter) {
|
|
// Editor is not inited yet, so we keep the state set until
|
|
// editor is loaded
|
|
var state = !editor && lastState
|
|
? state = util.extend({}, lastState)
|
|
: {};
|
|
|
|
state.changed = changed;
|
|
state.meta = {};
|
|
state.filter = filter || false;
|
|
|
|
for (var prop in meta) {
|
|
if (prop.charAt(0) != "$")
|
|
state.meta[prop] = meta[prop];
|
|
}
|
|
|
|
if (title)
|
|
state.title = title;
|
|
if (tooltip)
|
|
state.tooltip = tooltip;
|
|
|
|
if (!filter) {
|
|
state.value = plugin.value;
|
|
state.undoManager = undoManager.getState();
|
|
}
|
|
else {
|
|
delete state.value;
|
|
delete state.undoManager;
|
|
}
|
|
|
|
if (editor)
|
|
state[editor.type] = editor.getState(plugin, filter);
|
|
|
|
var event = { doc: plugin, state: state };
|
|
emit("getState", event);
|
|
|
|
lastState = event.state;
|
|
|
|
return event.state;
|
|
}
|
|
|
|
function setState(state) {
|
|
if (state.meta) {
|
|
for (var prop in meta) {
|
|
if (prop.charAt(0) == "$")
|
|
state.meta[prop] = meta[prop];
|
|
}
|
|
meta = state.meta;
|
|
}
|
|
if (state.title) plugin.title = state.title;
|
|
if (state.tooltip) plugin.tooltip = state.tooltip;
|
|
|
|
if (typeof state.value == "string")
|
|
plugin.value = state.value;
|
|
|
|
if (state.undoManager && !(state.undoManager instanceof Plugin))
|
|
undoManager.setState(state.undoManager);
|
|
|
|
if (state.changed && state.changed !== changed) {
|
|
changed = state.changed;
|
|
emit("changed", { changed: changed });
|
|
}
|
|
|
|
if (editor && state[editor.type])
|
|
editor.setState(plugin, state[editor.type]);
|
|
|
|
var event = { doc: plugin, state: state };
|
|
emit("setState", event);
|
|
|
|
lastState = event.state;
|
|
}
|
|
|
|
function getSession(type) {
|
|
var ed = editor || tab && tab.editor;
|
|
if (ed && !type) type = ed.type;
|
|
if (!type) return;
|
|
|
|
if (sessions[type])
|
|
return sessions[type];
|
|
|
|
var session = sessions[type] = new Plugin();
|
|
session.load(null, "session");
|
|
return session;
|
|
}
|
|
|
|
function progress(options) {
|
|
emit("progress", options);
|
|
}
|
|
|
|
function clone() {
|
|
var state = this.getState();
|
|
state.undoManager = undoManager;
|
|
var newdoc = new Document(state);
|
|
emit("clone", { doc: newdoc });
|
|
newdoc.meta.cloned = true;
|
|
return newdoc;
|
|
}
|
|
|
|
function canUnload() {
|
|
return emit("canUnload");
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("unload", function(){
|
|
changed = false;
|
|
|
|
// Unload the undo manager
|
|
undoManager.unload();
|
|
|
|
// Unload the sessions
|
|
for (var type in sessions)
|
|
sessions[type].unload();
|
|
});
|
|
|
|
/**
|
|
* Session Class for Cloud9 Editors. Each document that is loaded into an
|
|
* editor has one session object per editor. A session object is retrieved by
|
|
* calling {@link Document#getSession} and is specifically tied to an editor
|
|
* type. The session is used to store state and functions on by the editor.
|
|
*
|
|
* The session relates to other objects as such:
|
|
*
|
|
* * {@link Pane} - Represent a single pane, housing multiple tabs
|
|
* * {@link Tab} - A single tab (button) in a pane
|
|
* * {@link Editor} - The editor responsible for displaying the file in the tab
|
|
* * {@link Document} - The representation of a file in the tab
|
|
* * **Session - The session information of the editor**
|
|
* * {@link UndoManager} - The object that manages the undo stack for this document
|
|
*
|
|
* Panes can live in certain areas of Cloud9. By default these areas are:
|
|
*
|
|
* * {@link panes} - The main area where editor panes are displayed
|
|
* * {@link console} - The console in the bottom of the screen
|
|
*
|
|
* Tabs are managed by the {@link tabManager}. The default way to
|
|
* open a new file in an editor uses the tabManager:
|
|
*
|
|
* tabManager.openFile("/file.js", true, function(err, tab) {
|
|
* var session = tab.document.getSession();
|
|
* var aceSession = session.session;
|
|
* });
|
|
*
|
|
* Generally the state and functions stored on the session object are private
|
|
* to the editor and do not concern other plugins. However one can imagine that
|
|
* plugins that work on editors make use of these properties. For instance the
|
|
* {@link ace.status.Statusbar} plugin uses the information on the state object from the
|
|
* ace editor to display state information like the line number and language
|
|
* mode.
|
|
*
|
|
* When a tab is moved from one pane to another, the document is unloaded from
|
|
* one editor and then loaded into another one. It is up to the editor to
|
|
* decide how to handle the stored state on the session object. Often the
|
|
* editor will remove only some of that state and reuse other state in the
|
|
* new editor. For instance, the {@link ace.Ace Ace} editor stores the ace session
|
|
* object on the Cloud9 session object.
|
|
* It will only do this once and will use the same ace session object in
|
|
* different ace editors, throughout the lifetime of the session object.
|
|
*
|
|
* This example shows how an editor could deal with a session. For more
|
|
* information see the source code of the {@link texteditor.TextEditor TextEditor} plugin.
|
|
*
|
|
* plugin.on("documentLoad", function(e) {
|
|
* var doc = e.doc;
|
|
* var session = doc.getSession();
|
|
*
|
|
* if (!session.id) {
|
|
* session.id = Math.random();
|
|
* session.iframe = document.createElement("iframe");
|
|
* }
|
|
*
|
|
* // Container would be a div created in the {@link Editor#draw} event.
|
|
* container.appendChild(session.iframe);
|
|
* });
|
|
*
|
|
* @class Session
|
|
* @extends Plugin
|
|
*/
|
|
/**
|
|
* Document Class for Cloud9 Editors. Each file that is opened
|
|
* in an editor has a document object that contains it's value and
|
|
* state.
|
|
*
|
|
* The document relates to other objects as such:
|
|
*
|
|
* * {@link Pane} - Represent a single pane, housing multiple tabs
|
|
* * {@link Tab} - A single tab (button) in a pane
|
|
* * {@link Editor} - The editor responsible for displaying the file in the tab
|
|
* * **Document - The representation of a file in the tab**
|
|
* * {@link Session} - The session information of the editor
|
|
* * {@link UndoManager} - The object that manages the undo stack for this document
|
|
*
|
|
* Panes can live in certain areas of Cloud9. By default these areas are:
|
|
*
|
|
* * {@link panes} - The main area where editor panes are displayed
|
|
* * {@link console} - The console in the bottom of the screen
|
|
*
|
|
* Tabs are managed by the {@link tabManager}. The default way to
|
|
* open a new file in an editor uses the tabManager:
|
|
*
|
|
* tabManager.openFile("/file.js", true, function(err, tab) {
|
|
* var doc = tab.document;
|
|
* console.log("The value is: ", doc.value);
|
|
* });
|
|
*
|
|
* For editors that do not allow a user to edit files
|
|
* (e.g. terminal), a document object will still be created.
|
|
*/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
* the tab this document is attached to
|
|
* @property {Tab} tab
|
|
*/
|
|
get tab(){ return tab; },
|
|
set tab(v){ tab = v; },
|
|
|
|
/**
|
|
* the meta information for this document. Use this object to
|
|
* store any additional information you'd like to store. If you
|
|
* are storing state information that should not be kept between
|
|
* reloads of the IDE, prefix the items with a "$" character.
|
|
*
|
|
* Example:
|
|
*
|
|
* // This document has a black bird
|
|
* var doc = tabs.focussedTab.document;
|
|
* doc.meta.blackBird = true;
|
|
*
|
|
* Example (state is not preserved):
|
|
*
|
|
* // This document is being processed
|
|
* var doc = tabs.focussedTab.document;
|
|
* doc.meta.$processing = true;
|
|
*
|
|
* @property meta
|
|
*/
|
|
get meta(){ return meta; },
|
|
|
|
/**
|
|
* The last state that was set into the document.
|
|
* @property lastState
|
|
* @private
|
|
*/
|
|
get lastState(){ return lastState; },
|
|
|
|
/**
|
|
* the editor this document is attached to
|
|
* @property {Editor} editor
|
|
*/
|
|
get editor(){ return editor; },
|
|
set editor(v) {
|
|
editor = v;
|
|
emit("setEditor", { editor: v });
|
|
},
|
|
/**
|
|
* Whether the document is fully loaded
|
|
* @property {Boolean} ready
|
|
*/
|
|
get ready(){ return ready; },
|
|
set ready(v) {
|
|
// try to find out why is this called twice
|
|
var e = new Error("Setting ready on ready document");
|
|
if (ready)
|
|
reportError(e, {ready: ready});
|
|
ready = e.stack || true;
|
|
emit.sticky("ready", { doc: plugin });
|
|
},
|
|
/**
|
|
* The tooltip displayed when hovering over the tab button
|
|
* @property {String} tooltip
|
|
*/
|
|
get tooltip(){ return tooltip; },
|
|
set tooltip(v) {
|
|
tooltip = v;
|
|
emit("setTooltip", { tooltip: v });
|
|
},
|
|
/**
|
|
* The title of the document (displayed as caption of the tab button)
|
|
* @property {String} title
|
|
*/
|
|
get title(){ return title; },
|
|
set title(v) {
|
|
title = v;
|
|
emit("setTitle", { title: v });
|
|
},
|
|
/**
|
|
* Sets or retrieves the serialized value of this document.
|
|
* Setting this property will not change the undo stack. Set
|
|
* this property only to initialize the document or to reset
|
|
* the value of this document. Requesting the value of this
|
|
* document will cause it to serialize it's full state.
|
|
* @property {String} value
|
|
*/
|
|
get value(){
|
|
var calculated = emit("getValue", { value: recentValue || value });
|
|
if (typeof calculated != "string")
|
|
calculated = value;
|
|
|
|
if (undoManager.isAtBookmark())
|
|
recentValue = calculated;
|
|
|
|
return calculated;
|
|
},
|
|
set value(v) {
|
|
value = recentValue = v;
|
|
emit("setValue", { value: v });
|
|
hasValue = true;
|
|
},
|
|
/**
|
|
* The last serialized value that was either set or retrieved.
|
|
* The `recentValue` is only updated during a get when the
|
|
* document is saved (undoManager.isAtBookmark() == true).
|
|
* @property {String} recentValue
|
|
* @readonly
|
|
*/
|
|
get recentValue(){ return recentValue; },
|
|
/**
|
|
* Specifies whether the document on this tab is changed
|
|
* @property {Boolean} changed
|
|
*/
|
|
get changed(){ return changed; },
|
|
/**
|
|
* The object managing all the changes to the document
|
|
* @property {UndoManager} undoManager
|
|
*/
|
|
get undoManager(){ return undoManager; },
|
|
set undoManager(newUndo){
|
|
undoManager.unload();
|
|
undoManager = newUndo;
|
|
initUndo();
|
|
},
|
|
|
|
_events: [
|
|
/**
|
|
* Fires when the state is retrieved
|
|
* @event getState
|
|
* @param {Object} e
|
|
* @param {Document} e.doc the document for which the state is retrieved
|
|
* @param {Object} e.state the state object to extend with information
|
|
*/
|
|
"getState",
|
|
/**
|
|
* Fires when the state is set
|
|
* @event setState
|
|
* @param {Object} e
|
|
* @param {Document} e.doc the document for which the state is set
|
|
* @param {Object} e.state the state object
|
|
*/
|
|
"setState",
|
|
/**
|
|
* Fires when the value of this document is retrieved.
|
|
* When implementing an editor, use this event to return
|
|
* the current value of this document in the event handler.
|
|
*
|
|
* Example:
|
|
*
|
|
* doc.on("getValue", function(e) {
|
|
* return "The current value of this editor"
|
|
* }, doc.getSession());
|
|
*
|
|
* @event getValue
|
|
* @param {Object} e
|
|
* @param {String} e.value the last cached value
|
|
*/
|
|
"getValue",
|
|
/**
|
|
* Fires when the value of this document is set
|
|
* When implementing an editor, use this event to set
|
|
* the new value of this document in the event handler.
|
|
*
|
|
* Example:
|
|
*
|
|
* doc.on("setValue", function(e) {
|
|
* myDiv.innerHTML = e.value
|
|
* }, doc.getSession());
|
|
*
|
|
* @event setValue
|
|
* @param {Object} e
|
|
* @param {String} e.value the value that is being set
|
|
*/
|
|
"setValue",
|
|
/**
|
|
* @ignore
|
|
*/
|
|
"mergeState",
|
|
/**
|
|
* Fires when the title of this document is set
|
|
* @event setTitle
|
|
* @param {Object} e
|
|
* @param {String} e.value the title that is being set
|
|
*/
|
|
"setTitle",
|
|
/**
|
|
* Fires when the tooltip of this document is set
|
|
* @event setTooltip
|
|
* @param {Object} e
|
|
* @param {String} e.value the tooltip that is being set
|
|
*/
|
|
"setTooltip",
|
|
/**
|
|
* Fires when the editor of this document is set
|
|
* @event setEditor
|
|
* @param {Object} e
|
|
* @param {String} e.value the editor that is being set
|
|
*/
|
|
"setEditor",
|
|
/**
|
|
* Fires when the change state of the document has changed
|
|
* @event changed
|
|
* @param {Object} e
|
|
* @param {String} e.changed whether the value is changed
|
|
*/
|
|
"changed",
|
|
/**
|
|
* Fires when there is progress information related to this
|
|
* file. This can be either downloading or uploading.
|
|
* @event progress
|
|
* @param {Object} e
|
|
* @param {Number} e.loaded the number of bytes that have been downloaded/uploaded.
|
|
* @param {Number} e.total the total number of bytes for this file.
|
|
* @param {Boolean} e.complete whether the download has completed.
|
|
* @param {Boolean} e.upload whether this is an upload (instead of a download).
|
|
**/
|
|
"progress",
|
|
/**
|
|
* Fires when this document is cloned
|
|
* @event cone
|
|
* @param {Object} e
|
|
* @param {Document} e.doc the cloned document
|
|
**/
|
|
"clone",
|
|
/**
|
|
* @event canUnload
|
|
*/
|
|
"canUnload"
|
|
],
|
|
|
|
/**
|
|
* Retrieves the session object where editors can store state
|
|
*/
|
|
getSession: getSession,
|
|
|
|
/**
|
|
* Sets the value of the document and bookmarks it in the
|
|
* undoManager. This method also preserves the complete state
|
|
* of the editor while transitioning to the new value.
|
|
* @param {String} value The new value of the document
|
|
*/
|
|
setBookmarkedValue: setBookmarkedValue,
|
|
|
|
/**
|
|
* Retrieves the state of the document. The state object will
|
|
* have a section for each editor that it is loaded in. These
|
|
* sections have the name of the editor. For instance if the
|
|
* editor is ace, the returned object will look something like
|
|
* this:
|
|
*
|
|
* {
|
|
* ace : {
|
|
* scrollTop : 100,
|
|
* ...
|
|
* }
|
|
* }
|
|
*
|
|
* @return {Object}
|
|
* @return {Boolean} return.changed Specifying whether the document state has been saved.
|
|
* @return {Object} return.meta Any metadata set on this document (except items starting with a "$").
|
|
* @return {String} return.title The title displayed in the tab, displaying this document.
|
|
* @return {String} return.tooltip The tooltip displayed in the tab, displaying this document
|
|
* @return {String} return.value The value of the document.
|
|
* @return {Object} return.undoManager The result of {@link UndoManager#getState}.
|
|
*/
|
|
getState: getState,
|
|
|
|
/**
|
|
* Sets the state of the document
|
|
* @param {Object} state the state of the document. This can also include sections for editors. See {@link Document#method-getState}.
|
|
* @param {Boolean} state.changed Specifying whether the document state has been saved.
|
|
* @param {Object} state.meta Any metadata set on this document (except items starting with a "$").
|
|
* @param {String} state.title The title displayed in the tab, displaying this document.
|
|
* @param {String} state.tooltip The tooltip displayed in the tab, displaying this document
|
|
* @param {String} state.value The value of the document.
|
|
* @param {Object} state.undoManager The result of {@link UndoManager#getState}.
|
|
*/
|
|
setState: setState,
|
|
|
|
/**
|
|
* Set the progress indicator for this document
|
|
* @param {Object} options
|
|
* @param {Number} options.loaded the number of bytes that have been downloaded/uploaded.
|
|
* @param {Number} options.total the total number of bytes for this file.
|
|
* @param {Boolean} options.complete whether the download has completed.
|
|
* @param {Boolean} options.upload whether this is an upload (instead of a download).
|
|
*/
|
|
progress: progress,
|
|
|
|
/**
|
|
* Clones this document
|
|
*/
|
|
clone: clone,
|
|
|
|
/**
|
|
* Whether this document is ready to be unloaded
|
|
*/
|
|
canUnload: canUnload,
|
|
|
|
/**
|
|
* Returns whether the document has it's value set.
|
|
* @return {Boolean}
|
|
*/
|
|
hasValue: function() { return !!hasValue; },
|
|
});
|
|
|
|
if (options)
|
|
setState(options);
|
|
|
|
plugin.load(null, "document");
|
|
|
|
return plugin;
|
|
}
|
|
|
|
/***** Register and define API *****/
|
|
|
|
register(null, {
|
|
Document: Document
|
|
});
|
|
}
|
|
});
|