Respond to dirty flag change event. If the dirty flag is associated with an inline editor, show (or hide) the dirty indicator.
function _dirtyFlagChangeHandler(event, doc) {
var $dirtyIndicators = $(".inline-text-editor .dirty-indicator"),
$indicator;
$dirtyIndicators.each(function (index, indicator) {
$indicator = $(this);
if ($indicator.data("fullPath") === doc.file.fullPath) {
_showDirtyIndicator($indicator, doc.isDirty);
}
});
}
Shows or hides the dirty indicator
function _showDirtyIndicator($indicatorDiv, isDirty) {
// Show or hide the dirty indicator by adjusting
// the width of the div.
$indicatorDiv.css("width", isDirty ? 16 : 0);
}
Given a host editor and its inline editors, find the widest gutter and make all the others match
function _syncGutterWidths(hostEditor) {
var allHostedEditors = EditorManager.getInlineEditors(hostEditor);
// add the host itself to the list too
allHostedEditors.push(hostEditor);
var maxWidth = 0;
allHostedEditors.forEach(function (editor) {
var $gutter = $(editor._codeMirror.getGutterElement()).find(".CodeMirror-linenumbers");
$gutter.css("min-width", "");
var curWidth = $gutter.width();
if (curWidth > maxWidth) {
maxWidth = curWidth;
}
});
if (allHostedEditors.length === 1) {
//There's only the host, just refresh the gutter
allHostedEditors[0]._codeMirror.setOption("gutters", allHostedEditors[0]._codeMirror.getOption("gutters"));
return;
}
maxWidth = maxWidth + "px";
allHostedEditors.forEach(function (editor) {
$(editor._codeMirror.getGutterElement()).find(".CodeMirror-linenumbers").css("min-width", maxWidth);
// Force CodeMirror to refresh the gutter
editor._codeMirror.setOption("gutters", editor._codeMirror.getOption("gutters"));
});
}
function InlineTextEditor() {
InlineWidget.call(this);
this.editor = null;
// We need to set this as a capture handler so CodeMirror doesn't handle Esc before we see it.
this.handleKeyDown = this.handleKeyDown.bind(this);
this.htmlContent.addEventListener("keydown", this.handleKeyDown, true);
}
InlineTextEditor.prototype = Object.create(InlineWidget.prototype);
InlineTextEditor.prototype.constructor = InlineTextEditor;
InlineTextEditor.prototype.parentClass = InlineWidget.prototype;
InlineTextEditor.prototype.$wrapper = null;
If Document's file is deleted, or Editor loses sync with Document, just close
InlineTextEditor.prototype._onLostContent = function () {
// Note: this closes the entire inline widget if any one Editor loses sync. This seems
// better than leaving it open but suddenly removing one rule from the result list.
this.close();
};
// Consolidate all dirty document updates
// Due to circular dependencies, not safe to call on() directly
EventDispatcher.on_duringInit(DocumentManager, "dirtyFlagChange", _dirtyFlagChangeHandler);
exports.InlineTextEditor = InlineTextEditor;
});
Updates start line display.
InlineTextEditor.prototype._updateLineRange = function (editor) {
this._startLine = editor.getFirstVisibleLine();
this._endLine = editor.getLastVisibleLine();
this._lineCount = this._endLine - this._startLine;
this.$lineNumber.text(this._startLine + 1);
};
InlineTextEditor.prototype.getFocusedEditor = function () {
if (this.editor && this.editor.hasFocus()) {
return this.editor;
}
return null;
};
InlineTextEditor.prototype.handleKeyDown = function (e) {
if (e.keyCode === KeyEvent.DOM_VK_ESCAPE && this.editor && this.editor.getSelections().length > 1) {
CodeMirror.commands.singleSelection(this.editor._codeMirror);
e.stopImmediatePropagation();
}
};
InlineTextEditor.prototype.load = function (hostEditor) {
InlineTextEditor.prototype.parentClass.load.apply(this, arguments);
// We don't create the actual editor here--that will happen the first time
// setInlineContent() is called.
this.$wrapper = $("<div/>").addClass("inline-text-editor").appendTo(this.$htmlContent);
this.$header = $("<div/>").addClass("inline-editor-header").appendTo(this.$wrapper);
this.$filename = $("<a/>").addClass("filename").appendTo(this.$header);
this.$editorHolder = $("<div/>").addClass("inline-editor-holder").appendTo(this.$wrapper);
};
Some tasks have to wait until we've been parented into the outer editor
InlineTextEditor.prototype.onAdded = function () {
var self = this;
InlineTextEditor.prototype.parentClass.onAdded.apply(this, arguments);
if (this.editor) {
this.editor.refresh();
}
// Update display of inline editors when the hostEditor signals a redraw
CodeMirror.on(this.info, "redraw", function () {
// At the point where we get the redraw, CodeMirror might not yet have actually
// re-added the widget to the DOM. This is filed as https://github.com/codemirror/CodeMirror/issues/1226.
// For now, we can work around it by doing the refresh on a setTimeout().
window.setTimeout(function () {
if (self.editor) {
self.editor.refresh();
}
}, 0);
});
_syncGutterWidths(this.hostEditor);
if (this.editor) {
this.editor.focus();
}
};
Called any time inline was closed, whether manually (via close()) or automatically
InlineTextEditor.prototype.onClosed = function () {
InlineTextEditor.prototype.parentClass.onClosed.apply(this, arguments);
_syncGutterWidths(this.hostEditor);
// Destroy the inline editor.
this.setInlineContent(null);
this.htmlContent.removeEventListener("keydown", this.handleKeyDown, true);
};
Called when the editor containing the inline is made visible.
InlineTextEditor.prototype.onParentShown = function () {
InlineTextEditor.prototype.parentClass.onParentShown.apply(this, arguments);
// Refresh line number display and codemirror line number gutter
if (this.editor) {
this._updateLineRange(this.editor);
this.editor.refresh();
}
// We need to call this explicitly whenever the host editor is reshown
this.sizeInlineWidgetToContents();
};
Sets the document and range to show in the inline editor, or null to destroy the current editor and leave the content blank.
InlineTextEditor.prototype.setInlineContent = function (doc, startLine, endLine) {
var self = this;
// Destroy the previous editor if we had one and clear out the filename info.
if (this.editor) {
this.editor.off(".InlineTextEditor");
this.editor.destroy(); // remove from DOM and release ref on Document
this.editor = null;
this.$filename.off(".InlineTextEditor")
.removeAttr("title");
this.$filename.html("");
}
if (!doc) {
return;
}
var range = {
startLine: startLine,
endLine: endLine
};
// dirty indicator, with file path stored on it
var $dirtyIndicatorDiv = $("<div/>")
.addClass("dirty-indicator")
.html("•")
.width(0); // initialize indicator as hidden
$dirtyIndicatorDiv.data("fullPath", doc.file.fullPath);
this.$lineNumber = $("<span class='line-number'/>");
// update contents of filename link
this.$filename.append($dirtyIndicatorDiv)
.append(doc.file.name + " : ")
.append(this.$lineNumber)
.attr("title", doc.file.fullPath);
// clicking filename jumps to full editor view
this.$filename.on("click.InlineTextEditor", function () {
CommandManager.execute(Commands.FILE_OPEN, { fullPath: doc.file.fullPath })
.done(function () {
EditorManager.getCurrentFullEditor().setCursorPos(startLine, 0, true);
});
});
var inlineInfo = EditorManager.createInlineEditorForDocument(doc, range, this.$editorHolder.get(0));
this.editor = inlineInfo.editor;
// Init line number display
this._updateLineRange(inlineInfo.editor);
// Always update the widget height when an inline editor completes a
// display update
this.editor.on("update.InlineTextEditor", function (event, editor) {
self.sizeInlineWidgetToContents();
});
// Size editor to content whenever text changes (via edits here or any
// other view of the doc: Editor fires "change" any time its text
// changes, regardless of origin)
this.editor.on("change.InlineTextEditor", function (event, editor) {
if (self.hostEditor.isFullyVisible()) {
self.sizeInlineWidgetToContents();
self._updateLineRange(editor);
}
});
// If Document's file is deleted, or Editor loses sync with Document, delegate to this._onLostContent()
this.editor.on("lostContent.InlineTextEditor", function () {
self._onLostContent.apply(self, arguments);
});
// set dirty indicator state
_showDirtyIndicator($dirtyIndicatorDiv, doc.isDirty);
};
Update the inline editor's height when the number of lines change. The base implementation of this method does nothing.
InlineTextEditor.prototype.sizeInlineWidgetToContents = function () {
// brackets_codemirror_overrides.css adds height:auto to CodeMirror
// Inline editors themselves do not need to be sized, but layouts like
// the one used in CSSInlineEditor do need some manual layout.
};