diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index 9a86c66c6a7..1a73d509bac 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -55,6 +55,7 @@ define(function (require, exports, module) { // Bind event handlers this._updateRelatedContainer = this._updateRelatedContainer.bind(this); + this._ensureCursorVisible = this._ensureCursorVisible.bind(this); this._onClick = this._onClick.bind(this); // Create DOM to hold editors and related list @@ -156,6 +157,9 @@ define(function (require, exports, module) { // but in this case we're specifically concerned with changes in the given // editor, not general document changes. $(this.editors[0]).on("change", this._updateRelatedContainer); + + // Cursor activity in the inline editor may cause us to horizontally scroll. + $(this.editors[0]).on("cursorActivity", this._ensureCursorVisible); this.sizeInlineWidgetToContents(true); this._updateRelatedContainer(); @@ -197,6 +201,7 @@ define(function (require, exports, module) { // remove resize handlers for relatedContainer $(this.hostEditor).off("change", this._updateRelatedContainer); $(this.editors[0]).off("change", this._updateRelatedContainer); + $(this.editors[0]).off("cursorActivity", this._ensureCursorVisible); $(this).off("offsetTopChanged", this._updateRelatedContainer); $(window).off("resize", this._updateRelatedContainer); }; @@ -239,7 +244,7 @@ define(function (require, exports, module) { // Because we're using position: fixed, we need to explicitly clip the rule list if it crosses // out of the top or bottom of the scroller area. - var hostScroller = this.hostEditor._codeMirror.getScrollerElement(), + var hostScroller = this.hostEditor.getScrollerElement(), rcTop = this.$relatedContainer.offset().top, rcHeight = this.$relatedContainer.outerHeight(), rcBottom = rcTop + rcHeight, @@ -269,6 +274,50 @@ define(function (require, exports, module) { // Position immediately to the left of the main editor's scrollbar. this.$relatedContainer.css("right", rightOffset + "px"); + + // Add extra padding to the right edge of the widget to account for the rule list. + this.$htmlContent.css("padding-right", this.$relatedContainer.outerWidth() + "px"); + + // Set the minimum width of the widget (which doesn't include the padding) to the width + // of CodeMirror's linespace, so that the total width will be at least as large as the + // width of the host editor's code plus the padding for the rule list. We need to do this + // rather than just setting min-width to 100% because adding padding for the rule list + // actually pushes out the width of the container, so we would end up continuously + // growing the overall width. + // This is a bit of a hack since it relies on knowing some detail about the innards of CodeMirror. + var lineSpace = this.hostEditor._getLineSpaceElement(), + minWidth = $(lineSpace).offset().left - this.$htmlContent.offset().left + $(lineSpace).width(); + this.$htmlContent.css("min-width", minWidth + "px"); + }; + + /** + * Based on the position of the cursor in the inline editor, determine whether we need to change the + * scroll position of the host editor to ensure that the cursor is visible. + */ + CSSInlineEditor.prototype._ensureCursorVisible = function () { + if ($.contains(this.editors[0].getRootElement(), document.activeElement)) { + var cursorCoords = this.editors[0]._codeMirror.cursorCoords(), + lineSpaceOffset = $(this.editors[0]._getLineSpaceElement()).offset(), + ruleListOffset = this.$relatedContainer.offset(); + // If we're off the left-hand side, we just want to scroll it into view normally. But + // if we're underneath the rule list on the right, we want to ask the host editor to + // scroll far enough that the current cursor position is visible to the left of the rule + // list. (Because we always add extra padding for the rule list, this is always possible.) + if (cursorCoords.x > ruleListOffset.left) { + cursorCoords.x += this.$relatedContainer.outerWidth(); + } + + // Vertically, we want to set the scroll position relative to the overall host editor, not + // the lineSpace of the widget itself. Also, we can't use the lineSpace here, because its top + // position just corresponds to whatever CodeMirror happens to have rendered at the top. So + // we need to figure out our position relative to the top of the virtual scroll area, which is + // the top of the actual scroller minus the scroll position. + var scrollerTop = $(this.hostEditor.getScrollerElement()).offset().top - this.hostEditor.getScrollPos().y; + this.hostEditor._codeMirror.scrollIntoView(cursorCoords.x - lineSpaceOffset.left, + cursorCoords.y - scrollerTop, + cursorCoords.x - lineSpaceOffset.left, + cursorCoords.yBot - scrollerTop); + } }; /** diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 720a7521109..dfab768738d 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -333,7 +333,7 @@ define(function (require, exports, module) { Editor.prototype.destroy = function () { // CodeMirror docs for getWrapperElement() say all you have to do is "Remove this from your // tree to delete an editor instance." - $(this._codeMirror.getWrapperElement()).remove(); + $(this.getRootElement()).remove(); // Disconnect from Document this.document.releaseRef(); @@ -716,6 +716,25 @@ define(function (require, exports, module) { return this._codeMirror.getWrapperElement(); }; + /** + * Gets the lineSpace element within the editor (the container around the individual lines of code). + * FUTURE: This is fairly CodeMirror-specific. Logic that depends on this may break if we switch + * editors. + * @returns {Object} The editor's lineSpace element. + */ + Editor.prototype._getLineSpaceElement = function () { + var lineSpaceParent = $(".CodeMirror-lines", this.getScrollerElement()).get(0); + return $(lineSpaceParent).children().get(0); + }; + + /** + * Returns the current scroll position of the editor. + * @returns {{x:number, y:number}} The x,y scroll position. + */ + Editor.prototype.getScrollPos = function () { + return this._codeMirror.scrollPos(); + }; + /** * Adds an inline widget below the given line. If any inline widget was already open for that * line, it is closed without warning. @@ -775,7 +794,7 @@ define(function (require, exports, module) { /** * Sets the height of an inline widget in this editor. * @param {!InlineWidget} inlineWidget The widget whose height should be set. - * @param {!height} height The height of the widget. + * @param {!number} height The height of the widget. * @param {boolean} ensureVisible Whether to scroll the entire widget into view. */ Editor.prototype.setInlineWidgetHeight = function (inlineWidget, height, ensureVisible) { @@ -829,7 +848,7 @@ define(function (require, exports, module) { /** Returns true if the editor has focus */ Editor.prototype.hasFocus = function () { // The CodeMirror instance wrapper has a "CodeMirror-focused" class set when focused - return $(this._codeMirror.getWrapperElement()).hasClass("CodeMirror-focused"); + return $(this.getRootElement()).hasClass("CodeMirror-focused"); }; /** @@ -845,7 +864,7 @@ define(function (require, exports, module) { * @param {boolean} show true to show the editor, false to hide it */ Editor.prototype.setVisible = function (show) { - $(this._codeMirror.getWrapperElement()).css("display", (show ? "" : "none")); + $(this.getRootElement()).css("display", (show ? "" : "none")); this._codeMirror.refresh(); if (show) { this._inlineWidgets.forEach(function (inlineWidget) { @@ -859,7 +878,7 @@ define(function (require, exports, module) { * visible, and has a non-zero width/height. */ Editor.prototype.isFullyVisible = function () { - return $(this._codeMirror.getWrapperElement()).is(":visible"); + return $(this.getRootElement()).is(":visible"); }; /** diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 7a9e1a688eb..65bc975bb41 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -321,7 +321,6 @@ a, img { .InlineWidget { border-top: 1px solid #C0C0C0; border-bottom: 1px solid #C0C0C0; - min-width: 100%; background-color: #eaeaea; .filename {