EditorManager owns the UI for the editor area. This essentially mirrors the 'current document' property maintained by DocumentManager's model.
Note that there is a little bit of unusual overlap between EditorManager and DocumentManager: because the Document state is actually stored in the CodeMirror editor UI, DocumentManager is not a pure headless model. Each Document encapsulates an editor instance, and thus EditorManager must have some knowledge about Document's internal state (we access its _editor property).
This module dispatches the following events:
activeEditorChange -- Fires after the active editor (full or inline).
Doesn't fire when editor temporarily loses focus to a non-editor control (e.g. search toolbar or modal dialog, or window deactivation).
Does fire when focus moves between inline editor and its full-size container.
This event tracks MainViewManagers's
currentFileChange` event and all editor
objects "focus" event.
(e, editorGainingFocus:editor, editorLosingFocus:editor)
The 2nd arg to the listener is which Editor became active; the 3rd arg is
which Editor is deactivated as a result. Either one may be null.
NOTE (#1257): getFocusedEditor()
sometimes lags behind this event. Listeners
should use the arguments or call getActiveEditor()
to reliably see which Editor
just gained focus.
DOM element to house any hidden editors created soley for inline widgets
var _$hiddenEditorsContainer;
Registered inline documentation widget providers sorted descending by priority.
var _inlineDocsProviders = [];
Registered inline-editor widget providers sorted descending by priority.
var _inlineEditProviders = [];
Registered jump-to-definition providers.
var _jumpToDefProviders = [];
Creates a new Editor bound to the given Document. The editor is appended to the given container as a visible child.
function _createEditorForDocument(doc, makeMasterEditor, container, range, editorOptions) {
var editor = new Editor(doc, makeMasterEditor, container, range, editorOptions);
editor.on("focus", function () {
_notifyActiveEditorChanged(editor);
});
editor.on("beforeDestroy", function () {
if (editor.$el.is(":visible")) {
_saveEditorViewState(editor);
}
});
return editor;
}
function _createFullEditorForDocument(document, pane, editorOptions) {
// Create editor; make it initially invisible
var editor = _createEditorForDocument(document, true, pane.$content, undefined, editorOptions);
editor.setVisible(false);
pane.addView(editor);
exports.trigger("_fullEditorCreatedForDocument", document, editor, pane.id);
return editor;
}
Creates a hidden, unattached master editor that is needed when a document is created for the sole purpose of creating an inline editor so operations that require a master editor can be performed Only called from Document._ensureMasterEditor() The editor view is placed in a hidden part of the DOM but can later be moved to a visible pane when the document is opened using pane.addView()
function _createUnattachedMasterEditor(doc) {
// attach to the hidden containers DOM node if necessary
if (!_$hiddenEditorsContainer) {
_$hiddenEditorsContainer = $("#hidden-editors");
}
// Create an editor
var editor = _createEditorForDocument(doc, true, _$hiddenEditorsContainer);
// and hide it
editor.setVisible(false);
}
Asynchronously asks providers to handle jump-to-definition.
function _doJumpToDef() {
var providers = _jumpToDefProviders;
var promise,
i,
result = new $.Deferred();
var editor = getActiveEditor();
if (editor) {
var pos = editor.getCursorPos();
PerfUtils.markStart(PerfUtils.JUMP_TO_DEFINITION);
// Run through providers until one responds
for (i = 0; i < providers.length && !promise; i++) {
var provider = providers[i];
promise = provider(editor, pos);
}
// Will one of them will provide a result?
if (promise) {
promise.done(function () {
PerfUtils.addMeasurement(PerfUtils.JUMP_TO_DEFINITION);
result.resolve();
}).fail(function () {
// terminate timer that was started above
PerfUtils.finalizeMeasurement(PerfUtils.JUMP_TO_DEFINITION);
result.reject();
});
} else {
// terminate timer that was started above
PerfUtils.finalizeMeasurement(PerfUtils.JUMP_TO_DEFINITION);
result.reject();
}
} else {
result.reject();
}
return result.promise();
}
Returns the focused Editor within an inline text editor, or null if something else has focus
function _getFocusedInlineEditor() {
var focusedWidget = getFocusedInlineWidget();
if (focusedWidget instanceof InlineTextEditor) {
return focusedWidget.getFocusedEditor();
}
return null;
}
Current File Changed handler MainViewManager dispatches a "currentFileChange" event whenever the currently viewed file changes. Which could mean that the previously viewed file has been closed or a non-editor view (image) has been given focus. _notifyAcitveEditorChanged is also hooked up to editor.focus to handle focus events for editors which handles changing focus between two editors but, because editormanager maintains a "_lastFocusedEditor" state, we have to "nullify" that state whenever the focus goes to a non-editor or when the current editor is closed
function _handleCurrentFileChange(e, file) {
var doc = file && DocumentManager.getOpenDocumentForPath(file.fullPath);
_notifyActiveEditorChanged(doc && doc._masterEditor);
}
file removed from pane handler.
function _handleRemoveFromPaneView(e, removedFiles) {
var handleFileRemoved = function (file) {
var doc = DocumentManager.getOpenDocumentForPath(file.fullPath);
if (doc) {
MainViewManager._destroyEditorIfNotNeeded(doc);
}
};
// when files are removed from a pane then
// we should destroy any unnecssary views
if ($.isArray(removedFiles)) {
removedFiles.forEach(function (removedFile) {
handleFileRemoved(removedFile);
});
} else {
handleFileRemoved(removedFiles);
}
}
// Set up event dispatching
EventDispatcher.makeEventDispatcher(exports);
// File-based preferences handling
exports.on("activeEditorChange", function (e, current) {
if (current && current.document && current.document.file) {
PreferencesManager._setCurrentFile(current.document.file.fullPath);
}
});
// Initialize: command handlers
CommandManager.register(Strings.CMD_TOGGLE_QUICK_EDIT, Commands.TOGGLE_QUICK_EDIT, function () {
return _toggleInlineWidget(_inlineEditProviders, Strings.ERROR_QUICK_EDIT_PROVIDER_NOT_FOUND);
});
CommandManager.register(Strings.CMD_TOGGLE_QUICK_DOCS, Commands.TOGGLE_QUICK_DOCS, function () {
return _toggleInlineWidget(_inlineDocsProviders, Strings.ERROR_QUICK_DOCS_PROVIDER_NOT_FOUND);
});
CommandManager.register(Strings.CMD_JUMPTO_DEFINITION, Commands.NAVIGATE_JUMPTO_DEFINITION, _doJumpToDef);
// Create PerfUtils measurement
PerfUtils.createPerfMeasurement("JUMP_TO_DEFINITION", "Jump-To-Definiiton");
MainViewManager.on("currentFileChange", _handleCurrentFileChange);
MainViewManager.on("workingSetRemove workingSetRemoveList", _handleRemoveFromPaneView);
// For unit tests and internal use only
exports._createFullEditorForDocument = _createFullEditorForDocument;
exports._notifyActiveEditorChanged = _notifyActiveEditorChanged;
// Internal Use only
exports._saveEditorViewState = _saveEditorViewState;
exports._createUnattachedMasterEditor = _createUnattachedMasterEditor;
// Define public API
exports.createInlineEditorForDocument = createInlineEditorForDocument;
exports.getFocusedInlineWidget = getFocusedInlineWidget;
exports.getInlineEditors = getInlineEditors;
exports.closeInlineWidget = closeInlineWidget;
exports.openDocument = openDocument;
exports.canOpenPath = canOpenPath;
// Convenience Methods
exports.getActiveEditor = getActiveEditor;
exports.getCurrentFullEditor = getCurrentFullEditor;
exports.getFocusedEditor = getFocusedEditor;
exports.registerInlineEditProvider = registerInlineEditProvider;
exports.registerInlineDocsProvider = registerInlineDocsProvider;
exports.registerJumpToDefProvider = registerJumpToDefProvider;
// Deprecated
exports.registerCustomViewer = registerCustomViewer;
exports.resizeEditor = resizeEditor;
exports.focusEditor = focusEditor;
exports.getCurrentlyViewedPath = getCurrentlyViewedPath;
exports.setEditorHolder = setEditorHolder;
});
Inserts a prioritized provider object into the array in sorted (descending) order.
function _insertProviderSorted(array, provider, priority) {
var index,
prioritizedProvider = {
priority: priority,
provider: provider
};
for (index = 0; index < array.length; index++) {
if (array[index].priority < priority) {
break;
}
}
array.splice(index, 0, prioritizedProvider);
}
Editor focus handler to change the currently active editor
function _notifyActiveEditorChanged(current) {
// Skip if the Editor that gained focus was already the most recently focused editor.
// This may happen e.g. if the window loses then regains focus.
if (_lastFocusedEditor === current) {
return;
}
var previous = _lastFocusedEditor;
_lastFocusedEditor = current;
exports.trigger("activeEditorChange", current, previous);
}
function _openInlineWidget(editor, providers, defaultErrorMsg) {
PerfUtils.markStart(PerfUtils.INLINE_WIDGET_OPEN);
// Run through inline-editor providers until one responds
var pos = editor.getCursorPos(),
inlinePromise,
i,
result = new $.Deferred(),
errorMsg,
providerRet;
// Query each provider in priority order. Provider may return:
// 1. `null` to indicate it does not apply to current cursor position
// 2. promise that should resolve to an InlineWidget
// 3. string which indicates provider does apply to current cursor position,
// but reason it could not create InlineWidget
//
// Keep looping until a provider is found. If a provider is not found,
// display the highest priority error message that was found, otherwise display
// default error message
for (i = 0; i < providers.length && !inlinePromise; i++) {
var provider = providers[i].provider;
providerRet = provider(editor, pos);
if (providerRet) {
if (providerRet.hasOwnProperty("done")) {
inlinePromise = providerRet;
} else if (!errorMsg && typeof (providerRet) === "string") {
errorMsg = providerRet;
}
}
}
// Use default error message if none other provided
errorMsg = errorMsg || defaultErrorMsg;
// If one of them will provide a widget, show it inline once ready
if (inlinePromise) {
inlinePromise.done(function (inlineWidget) {
editor.addInlineWidget(pos, inlineWidget).done(function () {
PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
result.resolve();
});
}).fail(function () {
// terminate timer that was started above
PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
editor.displayErrorMessageAtCursor(errorMsg);
result.reject();
});
} else {
// terminate timer that was started above
PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
editor.displayErrorMessageAtCursor(errorMsg);
result.reject();
}
return result.promise();
}
Updates _viewStateCache from the given editor's actual current state
function _restoreEditorViewState(editor) {
// We want to ignore the current state of the editor, so don't call __getViewState()
var viewState = ViewStateManager.getViewState(editor.document.file);
if (viewState) {
editor.restoreViewState(viewState);
}
}
Updates _viewStateCache from the given editor's actual current state
function _saveEditorViewState(editor) {
ViewStateManager.updateViewState(editor);
}
Create and/or show the editor for the specified document
function _showEditor(document, pane, editorOptions) {
// Ensure a main editor exists for this document to show in the UI
var createdNewEditor = false,
editor = document._masterEditor;
// Check if a master editor is not set already or the current master editor doesn't belong
// to the pane container requested - to support creation of multiple full editors
// This check is required as _masterEditor is the active full editor for the document
// and there can be existing full editor created for other panes
if (editor && editor._paneId && editor._paneId !== pane.id) {
editor = document._checkAssociatedEditorForPane(pane.id);
}
if (!editor) {
// Performance (see #4757) Chrome wastes time messing with selection
// that will just be changed at end, so clear it for now
if (window.getSelection && window.getSelection().empty) { // Chrome
window.getSelection().empty();
}
// Editor doesn't exist: populate a new Editor with the text
editor = _createFullEditorForDocument(document, pane, editorOptions);
createdNewEditor = true;
} else if (editor.$el.parent()[0] !== pane.$content[0]) {
// editor does exist but is not a child of the pane so add it to the
// pane (which will switch the view's container as well)
pane.addView(editor);
}
// show the view
pane.showView(editor);
if (MainViewManager.getActivePaneId() === pane.id) {
// give it focus
editor.focus();
}
if (createdNewEditor) {
_restoreEditorViewState(editor);
}
}
Closes any focused inline widget. Else, asynchronously asks providers to create one.
function _toggleInlineWidget(providers, errorMsg) {
var result = new $.Deferred();
var currentEditor = getCurrentFullEditor();
if (currentEditor) {
var inlineWidget = currentEditor.getFocusedInlineWidget();
if (inlineWidget) {
// an inline widget's editor has focus, so close it
PerfUtils.markStart(PerfUtils.INLINE_WIDGET_CLOSE);
inlineWidget.close().done(function () {
PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_CLOSE);
// return a resolved promise to CommandManager
result.resolve(false);
});
} else {
// main editor has focus, so create an inline editor
_openInlineWidget(currentEditor, providers, errorMsg).done(function () {
result.resolve(true);
}).fail(function () {
result.reject();
});
}
} else {
// Can not open an inline editor without a host editor
result.reject();
}
return result.promise();
}
Determines if the file can be opened in an editor
function canOpenPath(fullPath) {
return !LanguageManager.getLanguageForPath(fullPath).isBinary();
}
Removes the given widget UI from the given hostEditor (agnostic of what the widget's content is). The widget's onClosed() callback will be run as a result.
function closeInlineWidget(hostEditor, inlineWidget) {
// If widget has focus, return it to the hostEditor & move the cursor to where the inline used to be
if (inlineWidget.hasFocus()) {
// Place cursor back on the line just above the inline (the line from which it was opened)
// If cursor's already on that line, leave it be to preserve column position
var widgetLine = hostEditor._codeMirror.getLineNumber(inlineWidget.info.line);
var cursorLine = hostEditor.getCursorPos().line;
if (cursorLine !== widgetLine) {
hostEditor.setCursorPos({ line: widgetLine, pos: 0 });
}
hostEditor.focus();
}
return hostEditor.removeInlineWidget(inlineWidget);
}
Creates a new inline Editor instance for the given Document. The editor is not yet visible or attached to a host editor.
function createInlineEditorForDocument(doc, range, inlineContent) {
// Hide the container for the editor before creating it so that CodeMirror doesn't do extra work
// when initializing the document. When we construct the editor, we have to set its text and then
// set the (small) visible range that we show in the editor. If the editor is visible, CM has to
// render a large portion of the document before setting the visible range. By hiding the editor
// first and showing it after the visible range is set, we avoid that initial render.
$(inlineContent).hide();
var inlineEditor = _createEditorForDocument(doc, false, inlineContent, range);
inlineEditor._hostEditor = getCurrentFullEditor();
$(inlineContent).show();
return { content: inlineContent, editor: inlineEditor };
}
Returns focus to the last visible editor that had focus. If no editor visible, does nothing. This function should be called to restore editor focus after it has been temporarily removed. For example, after a dialog with editable text is closed.
function focusEditor() {
DeprecationWarning.deprecationWarning("Use MainViewManager.focusActivePane() instead of EditorManager.focusEditor().", true);
MainViewManager.focusActivePane();
}
Returns the current active editor (full-sized OR inline editor). This editor may not have focus at the moment, but it is visible and was the last editor that was given focus. Returns null if no editors are active.
function getActiveEditor() {
return _lastFocusedEditor;
}
Retrieves the visible full-size Editor for the currently opened file in the ACTIVE_PANE
function getCurrentFullEditor() {
var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE),
doc = currentPath && DocumentManager.getOpenDocumentForPath(currentPath);
return doc && doc._masterEditor;
}
function getCurrentlyViewedPath() {
DeprecationWarning.deprecationWarning("Use MainViewManager.getCurrentlyViewedFile() instead of EditorManager.getCurrentlyViewedPath().", true);
// We only want to return a path of a document object
// not other things like images, etc...
var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE),
doc;
if (currentPath) {
doc = DocumentManager.getOpenDocumentForPath(currentPath);
}
if (doc) {
return currentPath;
}
return null;
}
Returns the currently focused editor instance (full-sized OR inline editor). This function is similar to getActiveEditor(), with one main difference: this function will only return editors that currently have focus, whereas getActiveEditor() will return the last visible editor that was given focus (but may not currently have focus because, for example, a dialog with editable text is open).
function getFocusedEditor() {
var currentEditor = getCurrentFullEditor();
if (currentEditor) {
// See if any inlines have focus
var focusedInline = _getFocusedInlineEditor();
if (focusedInline) {
return focusedInline;
}
// otherwise, see if full-sized editor has focus
if (currentEditor.hasFocus()) {
return currentEditor;
}
}
return null;
}
Returns the currently focused inline widget, if any.
function getFocusedInlineWidget() {
var currentEditor = getCurrentFullEditor();
if (currentEditor) {
return currentEditor.getFocusedInlineWidget();
}
return null;
}
function getInlineEditors(hostEditor) {
var inlineEditors = [];
if (hostEditor) {
hostEditor.getInlineWidgets().forEach(function (widget) {
if (widget instanceof InlineTextEditor && widget.editor) {
inlineEditors.push(widget.editor);
}
});
}
return inlineEditors;
}
Opens the specified document in the given pane
function openDocument(doc, pane, editorOptions) {
var perfTimerName = PerfUtils.markStart("EditorManager.openDocument():\t" + (!doc || doc.file.fullPath));
if (doc && pane) {
_showEditor(doc, pane, editorOptions);
}
PerfUtils.addMeasurement(perfTimerName);
}
function registerCustomViewer() {
throw new Error("EditorManager.registerCustomViewer() has been removed.");
}
Registers a new inline docs provider. When Quick Docs is invoked each registered provider is asked if it wants to provide inline docs given the current editor and cursor location. An optional priority parameter is used to give providers with higher priority an opportunity to provide an inline editor before providers with lower priority.
function registerInlineDocsProvider(provider, priority) {
if (priority === undefined) {
priority = 0;
}
_insertProviderSorted(_inlineDocsProviders, provider, priority);
}
Registers a new inline editor provider. When Quick Edit is invoked each registered provider is asked if it wants to provide an inline editor given the current editor and cursor location. An optional priority parameter is used to give providers with higher priority an opportunity to provide an inline editor before providers with lower priority.
function registerInlineEditProvider(provider, priority) {
if (priority === undefined) {
priority = 0;
}
_insertProviderSorted(_inlineEditProviders, provider, priority);
}
Registers a new jump-to-definition provider. When jump-to-definition is invoked each registered provider is asked if it wants to provide jump-to-definition results, given the current editor and cursor location.
function registerJumpToDefProvider(provider) {
_jumpToDefProviders.push(provider);
}