Adding copy buttons to code blocks in Sphinx#

NOTE: This is now a sphinx extension! Thanks to some friendly suggestions, I’ve written this up as a super tiny sphinx extension. Check it out here: https://github.com/choldgraf/sphinx-copybutton

Sphinx is a fantastic way to build documentation for your Python package. On the Jupyter project, we use it for almost all of our repositories.

A common use for Sphinx is to step people through a chunk of code. For example, in the Zero to JupyterHub for Kubernetes guide we step users through a number of installation and configuration steps.

A common annoyance is that there is a lot of copy/pasting involved. Sometimes you accidentally miss a character or some whitespace. So, I spent a bit of time figuring out how to automatically embed a copy button into code blocks. It turns out this is pretty easy!

Here’s what the final result will look like (just hover the code block below)

wow = this_text
is_so = much*more*copyable

Adding a copy button to your Sphinx code blocks#

To accomplish this we’ll use the excellent clipboard.js which provides the machinery for copying the contents of an HTML element as well as jquery for modifying our built documentation on-demand.

The result will be a Sphinx site with code blocks that display a copy button when you hover over them. You can see how it looks on this very page, which uses a similar method (but is built with Jekyll).

Here’s what you should do:

  1. Add the clipboard.js javascript. Create a javascript script called doc/_static/custom.js. In the file, put the following code (see comments for explanation):

    function addCopyButtonToCode(){
    // get all code elements
    var allCodeBlocksElements = $( "div.highlight pre" );
    
    // For each element, do the following steps
    allCodeBlocksElements.each(function(ii) {
    // define a unique id for this element and add it
    var currentId = "codeblock" + (ii + 1);
    $(this).attr('id', currentId);
    
    // create a button that's configured for clipboard.js
    // point it to the text that's in this code block
    // add the button just after the text in the code block w/ jquery
    var clipButton = '<button class="btn copybtn" data-clipboard-target="#' + currentId + '"><img src="https://clipboardjs.com/assets/images/clippy.svg" width="13" alt="Copy to clipboard"></button>';
       $(this).after(clipButton);
    });
    
    // tell clipboard.js to look for clicks that match this query
    new Clipboard('.btn');
    }
    
    $(document).ready(function () {
    // Once the DOM is loaded for the page, attach clipboard buttons
    addCopyButtonToCode();
    });
    
  2. Add some CSS to make it pretty. Create a custom CSS file called doc/_static/custom.css (or add to one you’ve already got). In the file, put these lines:

    /* Copy buttons */
    button.copybtn {
      webkit-transition: opacity .3s ease-in-out;
      -o-transition: opacity .3s ease-in-out;
      transition: opacity .3s ease-in-out;
      opacity: 0;
      padding: 2px 6px;
      position: absolute;
      right: 4px;
      top: 4px;
    }
    div.highlight:hover .copybtn, div.highlight .copybtn:focus {
        opacity: .3;
    }
    div.highlight .copybtn:hover {
        opacity: 1;
    }
    div.highlight {
        position: relative;
    }
    

3. **Link these scripts in your configuration.** You need to link your
 custom JS and CSS scripts, as well as the clipboard.js script so it ships with
 your site. In your `conf.py` file, add the following function/lines (or add to one you've already
 got defined).

 ```python
 def setup(app):
     app.add_stylesheet('custom.css')
     app.add_javascript("custom.js")
     app.add_javascript("https://cdn.jsdelivr.net/npm/clipboard@1/dist/clipboard.min.js")

And that’s it! Once you clear your Sphinx cache and re-build your site, you should now have buttons that appear when you hover over them, and that copy the text inside when you click them.

Thanks#

Many thanks to this StackOverflow post for the majority of the code that led to this hack!