diff --git a/demo/resize.html b/demo/resize.html index 69eae2c39ba3930dbe87f86c07657c3db44bed13..1c1ef390ab3e658ac6c75136b7eb27f86bce33b6 100644 --- a/demo/resize.html +++ b/demo/resize.html @@ -12,10 +12,6 @@ border: 1px solid #eee; height: auto; } - .CodeMirror-scroll { - overflow-y: hidden; - overflow-x: auto; - } </style> <div id=nav> <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> @@ -37,13 +33,10 @@ border: 1px solid #eee; height: auto; } -.CodeMirror-scroll { - overflow-y: hidden; - overflow-x: auto; -} </textarea></form> -<p>By setting a few CSS properties, and giving +<p>By setting an editor's <code>height</code> style +to <code>auto</code> and giving the <a href="../doc/manual.html#option_viewportMargin"><code>viewportMargin</code></a> a value of <code>Infinity</code>, CodeMirror can be made to automatically resize to fit its content.</p> diff --git a/doc/manual.html b/doc/manual.html index bf2536da34e3ffaf3f3276f3cd6a0bcc263f798b..42fc03ebbf73eb413da50c980632c8fc8b2a4182 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -998,16 +998,12 @@ editor.setOption("extraKeys", { <dd>The outer element of the editor. This should be used for the editor width, height, borders and positioning. Can also be used to set styles that should hold for everything inside the editor - (such as font and font size), or to set a background.</dd> - - <dt id="class_CodeMirror_scroll"><code><strong>CodeMirror-scroll</strong></code></dt> - <dd>Whether the editor scrolls (<code>overflow: auto</code> + - fixed height). By default, it does. Setting - the <code>CodeMirror</code> class to have <code>height: - auto</code> and giving this class <code>overflow-x: auto; - overflow-y: hidden;</code> will cause the editor - to <a href="../demo/resize.html">resize to fit its - content</a>.</dd> + (such as font and font size), or to set a background. Setting + this class' <code>height</code> style to <code>auto</code> will + make the editor <a href="../demo/resize.html">resize to fit its + content</a> (it is recommended to also set + the <a href="#option_viewportMargin"><code>viewportMargin</code> + option</a> to <code>Infinity</code> when doing this.</dd> <dt id="class_CodeMirror_focused"><code><strong>CodeMirror-focused</strong></code></dt> <dd>Whenever the editor is focused, the top element gets this diff --git a/lib/codemirror.css b/lib/codemirror.css index e2d4ec2048c43df68c9c4d3a1685e997dfcf73ec..5c18db34b40d6c8f68cc6a72e856d4a80558d65b 100644 --- a/lib/codemirror.css +++ b/lib/codemirror.css @@ -5,10 +5,6 @@ font-family: monospace; height: 300px; } -.CodeMirror-scroll { - /* Set scrolling behaviour here */ - overflow: auto; -} /* PADDING */ @@ -151,6 +147,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } .CodeMirror-scroll { + overflow: scroll; /* 30px is the magic margin used to hide the element's real scrollbars */ /* See overflow: hidden in .CodeMirror */ margin-bottom: -30px; margin-right: -30px; @@ -195,7 +192,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-gutters { position: absolute; left: 0; top: 0; - padding-bottom: 30px; z-index: 3; } .CodeMirror-gutter { @@ -203,9 +199,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} height: 100%; -moz-box-sizing: content-box; box-sizing: content-box; - padding-bottom: 30px; - margin-bottom: -32px; display: inline-block; + margin-bottom: -30px; /* Hack to make IE7 behave */ *zoom:1; *display:inline; diff --git a/lib/codemirror.js b/lib/codemirror.js index a1470ac5c2b6c5a86d9c0bb92d9bfb280a7e9760..04192ba95c96f497e2e3ccf76bb7e26fc42ef254 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -164,7 +164,7 @@ // Behavior of elts with overflow: auto and padding is // inconsistent across browsers. This is used to ensure the // scrollable area is big enough. - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); // Will contain the gutters, if any. d.gutters = elt("div", null, "CodeMirror-gutters"); d.lineGutter = null; @@ -202,6 +202,9 @@ d.lastWrapHeight = d.lastWrapWidth = 0; d.updateLineNumbers = null; + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + // Used to only resize the line number gutter when necessary (when // the amount of lines crosses a boundary that makes its width change) d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; @@ -389,10 +392,6 @@ // SCROLLBARS - function hScrollbarTakesSpace(cm) { - return cm.display.scroller.clientHeight - cm.display.wrapper.clientHeight < scrollerCutOff - 3; - } - // Prepare DOM reads needed to update the scrollbars. Done in one // shot to minimize update/measure roundtrips. function measureForScrollbars(cm) { @@ -401,22 +400,27 @@ clientHeight: scroll.clientHeight, barHeight: cm.display.scrollbarV.clientHeight, scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth, - hScrollbarTakesSpace: hScrollbarTakesSpace(cm), barWidth: cm.display.scrollbarH.clientWidth, docHeight: Math.round(cm.doc.height + paddingVert(cm.display)) }; } - // Re-synchronize the fake scrollbars with the actual size of the - // content. function updateScrollbars(cm, measure) { if (!measure) measure = measureForScrollbars(cm); + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + // FIXME optimize by somehow detecting when the situation is okay? + // (Revisit after implementing custom scrollbars) + if (startWidth != cm.display.barWidth || startHeight != cm.display.barHeight) + updateScrollbarsInner(cm, measureForScrollbars(cm)); + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { var d = cm.display, sWidth = scrollbarWidth(d.measure); - var scrollHeight = measure.docHeight + scrollerCutOff; var needsH = measure.scrollWidth > measure.clientWidth; - if (needsH && measure.scrollWidth <= measure.clientWidth + 1 && - sWidth > 0 && !measure.hScrollbarTakesSpace) - needsH = false; // (Issue #2562) + var scrollHeight = measure.docHeight + scrollGap(cm) + d.barHeight; var needsV = scrollHeight > measure.clientHeight; if (needsV) { @@ -429,6 +433,8 @@ d.scrollbarV.style.display = ""; d.scrollbarV.firstChild.style.height = "0"; } + d.sizer.style.paddingRight = (d.barWidth = needsV ? sWidth : 0) + "px"; + if (needsH) { d.scrollbarH.style.display = "block"; d.scrollbarH.style.right = needsV ? sWidth + "px" : "0"; @@ -438,6 +444,8 @@ d.scrollbarH.style.display = ""; d.scrollbarH.firstChild.style.width = "0"; } + d.sizer.style.paddingBottom = (d.barHeight = needsH ? sWidth : 0) + "px"; + if (needsH && needsV) { d.scrollbarFiller.style.display = "block"; d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = sWidth + "px"; @@ -550,16 +558,28 @@ this.wrapperHeight = display.wrapper.clientHeight; this.wrapperWidth = display.wrapper.clientWidth; this.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo; - this.oldScrollerWidth = display.scroller.clientWidth; + this.oldDisplayWidth = displayWidth(cm); this.force = force; this.dims = getDimensions(cm); } + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + // Does the actual updating of the line display. Bails out // (returning false) when there is nothing to be done and forced is // false. function updateDisplayIfNeeded(cm, update) { var display = cm.display, doc = cm.doc; + if (update.editorIsHidden) { resetView(cm); return false; @@ -612,9 +632,10 @@ if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); // Prevent selection and cursors from interfering with the scroll - // width. + // width and height. removeChildren(display.cursorDiv); removeChildren(display.selectionDiv); + display.heightForcer.style.top = display.gutters.style.height = 0; if (different) { display.lastWrapHeight = update.wrapperHeight; @@ -630,14 +651,13 @@ function postUpdateDisplay(cm, update) { var force = update.force, viewport = update.viewport; for (var first = true;; first = false) { - if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) { + if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) { force = true; } else { force = false; // Clip forced viewport to actual scrollable area. if (viewport && viewport.top != null) - viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - scrollerCutOff - - cm.display.scroller.clientHeight, viewport.top)}; + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; // Updated line heights might result in the drawn area not // actually covering the viewport. Keep looping until it does. update.visible = visibleLines(cm.display, cm.doc, viewport); @@ -671,16 +691,7 @@ function setDocumentHeight(cm, measure) { cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px"; - cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px"; - } - - function checkForWebkitWidthBug(cm, measure) { - // Work around Webkit bug where it sometimes reserves space for a - // non-existing phantom scrollbar in the scroller (Issue #2420) - if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) { - cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px"; - cm.display.gutters.style.height = measure.docHeight + "px"; - } + cm.display.gutters.style.height = Math.max(measure.docHeight + scrollGap(cm), measure.clientHeight) + "px"; } // Read the actual heights of the rendered lines, and update their @@ -1498,13 +1509,21 @@ return data; } + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; + } + // Ensure the lineView.wrapping.heights array is populated. This is // an array of bottom offsets for the lines that make up a drawn // line. When lineWrapping is on, there might be more than one // height. function ensureLineHeights(cm, lineView, rect) { var wrapping = cm.options.lineWrapping; - var curWidth = wrapping && cm.display.scroller.clientWidth; + var curWidth = wrapping && displayWidth(cm); if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { var heights = lineView.measure.heights = []; if (wrapping) { @@ -2022,6 +2041,7 @@ function endOperation_R1(op) { var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); if (op.updateMaxLine) findMaxLine(cm); op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || @@ -2047,8 +2067,9 @@ // updateDisplay_W2 will use these properties to do the actual resizing if (display.maxLineChanged && !cm.options.lineWrapping) { op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; - op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + - scrollerCutOff - display.scroller.clientWidth); + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm)); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); } if (op.updatedDisplay || op.selectionChanged) @@ -2081,9 +2102,6 @@ function endOperation_finish(op) { var cm = op.cm, display = cm.display, doc = cm.doc; - if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1) - updateScrollbars(cm); - if (op.updatedDisplay) postUpdateDisplay(cm, op.update); // Abort mouse wheel delta measurement, when scrolling explicitly @@ -2096,7 +2114,7 @@ display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top; } if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { - var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); + var left = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft)); display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left; alignHorizontally(cm); } @@ -2118,16 +2136,6 @@ if (display.wrapper.offsetHeight) doc.scrollTop = cm.display.scroller.scrollTop; - // Apply workaround for two webkit bugs - if (op.updatedDisplay && webkit) { - if (cm.options.lineWrapping) - checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420) - if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth && - op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 && - !hScrollbarTakesSpace(cm)) - updateScrollbars(cm); // (Issue #2562) - } - // Fire change events, and delayed event handlers if (op.changeObjs) signal(cm, "changes", cm, op.changeObjs); @@ -3721,7 +3729,7 @@ if (doScroll != null && !phantom) { var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + - (coords.bottom - coords.top + scrollerCutOff) + "px; left: " + + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + coords.left + "px; width: 2px;"); cm.display.lineSpace.appendChild(scrollNode); scrollNode.scrollIntoView(doScroll); @@ -3770,7 +3778,7 @@ var display = cm.display, snapMargin = textHeight(cm.display); if (y1 < 0) y1 = 0; var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; - var screen = display.scroller.clientHeight - scrollerCutOff, result = {}; + var screen = displayHeight(cm), result = {}; if (y2 - y1 > screen) y2 = y1 + screen; var docBottom = cm.doc.height + paddingVert(display); var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; @@ -3782,7 +3790,7 @@ } var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; - var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); var tooWide = x2 - x1 > screenw; if (tooWide) x2 = x1 + screenw; if (x1 < 10) @@ -3791,7 +3799,6 @@ result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); else if (x2 > screenw + screenleft - 3) result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; - return result; } @@ -4380,10 +4387,11 @@ if (y != null) this.curOp.scrollTop = y; }), getScrollInfo: function() { - var scroller = this.display.scroller, co = scrollerCutOff; + var scroller = this.display.scroller; return {left: scroller.scrollLeft, top: scroller.scrollTop, - height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, - clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; }, scrollIntoView: methodOp(function(range, margin) { @@ -7282,7 +7290,7 @@ // MISC UTILITIES // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerCutOff = 30; + var scrollerGap = 30; // Returned or thrown by various protocols to signal 'I'm not // handling this'.