13

In my HTML source code, I have a code block like the following (I use showdown and highlight.js on this page):

<pre><code class="cpp">
double myNumber = (double)4;
</code></pre>

My problem is that the first linebreak remains part of the "code" block. It's probably because of the enclosing "pre" block, but I need that there because highlight.js expects it (also apparently the HTML5 standard recommends it). The code is rendered like this (note the leading line break):

enter image description here

So my question is, using css, javascript or jquery, how can I remove leading or trailing linebreaks from "code" blocks like this one?

Boinst
  • 3,365
  • 2
  • 38
  • 60
  • 1
    Try `code{display:block;}` – Musa Feb 18 '13 at 23:05
  • Does the plugin render an actual line break tag? e.g.
    – Christopher Marshall Feb 18 '13 at 23:05
  • Thanks @Musa, that doesn't seem to do the trick. – Boinst Feb 18 '13 at 23:06
  • 3
    jQuery.trim the contents instead? – andytuba Feb 18 '13 at 23:07
  • @ChristopherMarshall, no, just a blank line per the screenshot. – Boinst Feb 18 '13 at 23:07
  • Is an actual
    rendered out? I doubt it... but also try removing any spacing/etc in the actual code of the
     tags, then inspect to see if there are any style coming through on the final tag.
    – RomoCode Feb 18 '13 at 23:07
  • @Romanulus, no, an actual
    is not rendered out. Removing the actual linebreak manually within the code block does fix the problem as you'd expect, but makes the html less readable. There is nothing within the pre block aside from the code block itself.
    – Boinst Feb 18 '13 at 23:13
  • why was that first answer deleted? Was just looking at the code, and it looked pretty good to me, no? – Zach Lysobey Feb 18 '13 at 23:29
  • @ZachL, I have no idea. I have a notification that says "3 answers on question", but I don't see any answers – Boinst Feb 18 '13 at 23:51
  • 1
    I deleted (then undeleted) my answer because it didn't work for the stated scenario. My updated answer will work in Chrome and FF, but I can't get IE9 to function as intended with the `code` element. Still, there is now some value in my answer, so I'll leave it. – Tim M. Feb 19 '13 at 00:13
  • HTML 5 says `
    ` can be followed by a newline. In your example, the newline is after `` though.
    – robinst Oct 19 '15 at 07:37

6 Answers6

16

You could use this hack:

pre:first-line {
    line-height: 0;
}
junvar
  • 11,151
  • 2
  • 30
  • 46
jonasnas
  • 3,540
  • 1
  • 23
  • 32
4

Here's another approach using Javascript that also solves the problem:

<script>
    window.onload = function (){
        // remove leading linebreaks from code blocks.
        var pre = document.getElementsByTagName("code");
        for (var i = 0, len = pre.length; i < len; i++) {
            var text = pre[i].firstChild.nodeValue;
            pre[i].firstChild.nodeValue = text.replace(/^\n+|\n+$/g, "");
        }
    }
</script>

Someone else posted this, then deleted their answer, but I thought it was worth preserving.

Oleh Prypin
  • 33,184
  • 10
  • 89
  • 99
Boinst
  • 3,365
  • 2
  • 38
  • 60
  • 2
    With LiveScript, jQuery: `$ 'code:first-child' .each -> @inner-HTML .= replace(/^\n+|\n+$/g, "")` (sorry, couldn't withstand the elegance). – Oleh Prypin Nov 16 '14 at 14:58
3

That's how pre works by default: it honors line breaks and whitespace. If you don't want the newline to render, then you have to remove it. Either outright remove it from the source or comment it out if you care how the source looks.

http://jsfiddle.net/VL8tG/

<pre><code class="cpp"><!--
-->double myNumber = (double)4;<!--
--></code></pre>
cimmanon
  • 67,211
  • 17
  • 165
  • 171
3

I just remove them manually before the highlighter starts.

const trimLine = node => 
  node.firstChild.nodeValue = node.firstChild.nodeValue.replace(/^\n+|\n+$/g, "");



window.onload = function () {

  Array.prototype.slice
      .call(document.getElementsByTagName("code"), 0)
      .map(trimLine);

  hljs.initHighlightingOnLoad();

}
John Haugeland
  • 9,230
  • 3
  • 37
  • 40
  • Had to put a `null` check in the function to avoid hitting the few uses of inline `` that did not yet have contents, unlike the ready to go `
    ` blocks where the leading and trailing newlines were bugging me.
    – Kevin Apr 30 '21 at 20:07
  • Needed changed to `/(^\n+)|(\n+\s*$)/g` for me for trailing line being indented. – Kevin Apr 30 '21 at 20:32
2

Demo: http://jsfiddle.net/WjVVs/4/.

Tested with Chrome and FF on the PC. It does not work in IE 9 when the plugin is applied to a code element (it appears to work fine when applied to a pre element). I can't find a suitable workaround, but feel free to comment/update.

This is a modified version from another answer. This plugin attempts to remove extra indentation caused by the natural flow of the document. I've modified it to be smarter about leading whitespace.

If it works correctly, you should see something like:

enter image description here

Usage

$("code").prettyPre(); // any selector here

HTML with leading whitespace and extra indentation

<div>
        <pre><code class="cpp">
           double myNumber = (double)4;

           // another line

           // another line

                // this is purposely indented further
                for( var i = 0; i < 100; i++ ){

                }            

        </code></pre>
</div>

Plugin

(function( $ ) {
    $.fn.prettyPre = function( method ) {

        var defaults = {
            ignoreExpression: /\s/ // what should be ignored?
        };

        var methods = {
            init: function( options ) {
                this.each( function() {
                    var context = $.extend( {}, defaults, options );
                    var $obj = $( this );
                    var usingInnerText = true;
                    var text = $obj.get( 0 ).innerText;

                    // some browsers support innerText...some don't...some ONLY work with innerText.
                    if ( typeof text == "undefined" ) {
                        text = $obj.html();
                        usingInnerText = false;
                    }

                    // use the first line as a baseline for how many unwanted leading whitespace characters are present
                    var superfluousSpaceCount = 0;
                    var pos = 0;
                    var currentChar = text.substring( 0, 1 );

                    while ( context.ignoreExpression.test( currentChar ) ) {
                        if(currentChar !== "\n"){
                            superfluousSpaceCount++;
                        }else{
                            superfluousSpaceCount = 0;
                        }

                        currentChar = text.substring( ++pos, pos + 1 );
                    }

                    // split
                    var parts = text.split( "\n" );
                    var reformattedText = "";

                    // reconstruct
                    var length = parts.length;
                    for ( var i = 0; i < length; i++ ) {

                        // remove leading whitespace (represented by an empty string)
                        if(i === 0 && parts[0]=== ""){
                            continue;   
                        }

                        // cleanup, and don't append a trailing newline if we are on the last line
                        reformattedText += parts[i].substring( superfluousSpaceCount ) + ( i == length - 1 ? "" : "\n" );
                    }

                    // modify original
                    if ( usingInnerText ) {
                        $obj.get( 0 ).innerText = reformattedText;
                    }
                    else {
                        // This does not appear to execute code in any browser but the onus is on the developer to not 
                        // put raw input from a user anywhere on a page, even if it doesn't execute!
                        $obj.html( reformattedText );
                    }
                } );
            }
        }

        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ) );
        }
        else if ( typeof method === "object" || !method ) {
            return methods.init.apply( this, arguments );
        }
        else {
            $.error( "Method " + method + " does not exist on jQuery.prettyPre." );
        }
    }
} )( jQuery );
Community
  • 1
  • 1
Tim M.
  • 53,671
  • 14
  • 120
  • 163
1

These functions use a class (added to the pre) to remove leading and trailing whitespace

function removeWhitespace(indent) {
  // Get a list of all elements that need whitespace removed by indent value (will have class `indent-X`)
  // List may be 0 long - loop simply doesn't run
  var preElements = document.getElementsByClassName('indent-'+indent);
  for (i = 0; i < preElements.length; i++) {
    preElements[i].innerHTML = preElements[i].innerHTML.split('\n'+' '.repeat(indent)).join('\n').split('\n'+' '.repeat(indent-2)+'</code>').join('</code>').split("\n").slice(1,-1).join("\n");
    //split('\n'+' '.repeat(indent)).join('\n') -- Split at every newline followed by X spaces. Then join together with the newlines.
    // .split('\n'+' '.repeat(indent-2)+'</code>').join('</code>') -- The lastline will have 2 less spaces, so remove those, and the newline at the end. Add the tag back in.
    //.split("\n").slice(1,-2).join("\n"); -- Remove the first and last lines.
  }
}

function removeWhitespaces() {
  // Loop over all indents, 2 to 40
  for (indent = 2; indent <= 40; indent+=2) {
    removeWhitespace(indent);
  }
}

Simply add the class indent-X where X is the amount of whitespace you want to remove to the pre.

JSFiddle

function removeWhitespace(indent) {
  // Get a list of all elements that need indent removed by indent value (will have class `indent-X`)
  // List may be 0 long - loop simply doesn't run
  var preElements = document.getElementsByClassName('indent-' + indent);

  for (i = 0; i < preElements.length; i++) {
    preElements[i].innerHTML = preElements[i].innerHTML.split('\n' + ' '.repeat(indent)).join('\n').split('\n' + ' '.repeat(indent - 2) + '</code>').join('</code>').split("\n").slice(1, -2).join("\n");
    //split('\n'+' '.repeat(indent)).join('\n') -- Split at every newline followed by X spaces. Then join together with the newlines.
    // .split('\n'+' '.repeat(indent-2)+'</code>').join('</code>') -- The lastline will have 2 less spaces, so remove those, and the newline at the end. Add the tag back in.
    //.split("\n").slice(1,-1).join("\n"); -- Remove the first and last lines.

    // Remove the clickme element.
    document.getElementById('clickme').innerHTML = '';
  }
}

function removeWhitespaces() {
  // Loop over all indents, 2 to 40
  for (indent = 2; indent <= 40; indent += 2) {
    removeWhitespace(indent);
  }
}
.indent-14 {
  background-color: #ccc;
}
<body>
  <div id="clickme" onclick="removeWhitespaces()">
    Click Me
  </div>
  <pre class="indent-14">
    <code>
              function createCORSRequest(method, url) {
                var request = new XMLHttpRequest();
                if ('withCredentials' in request) {
                  request.open(method, url, true);
                } else if (typeof XDomainRequest != 'undefined') {
                  request = new XDomainRequest();
                  request.open(method, url);
                } else {
                  request = null;
                }
                return request;
              }
    </code>
  </pre>
</body>
Tim
  • 2,563
  • 1
  • 23
  • 31