diff --git a/doc/_static/test/TEST_COPYBUTTON.png b/doc/_static/test/TEST_COPYBUTTON.png new file mode 100644 index 0000000..2787393 Binary files /dev/null and b/doc/_static/test/TEST_COPYBUTTON.png differ diff --git a/doc/conf.py b/doc/conf.py index 75d20c3..ba87009 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -97,6 +97,11 @@ # # html_sidebars = {} +# CopyButton configuration +# copybutton_prompt_text = ">>> " +# copybutton_only_copy_prompt_lines = False +# copybutton_remove_prompts = False +# copybutton_image_path = "test/TEST_COPYBUTTON.png" # -- Options for HTMLHelp output --------------------------------------------- diff --git a/doc/index.rst b/doc/index.rst index e43d0a6..263750f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -13,7 +13,7 @@ Sphinx-copybutton Sphinx-copybutton does one thing: add little "copy" button to the right of your code blocks. That's it! It is a lightweight wrapper around the excellent (and also lightweight) Javascript library -`ClipboardJS `. +`ClipboardJS `_. **Here's an example** @@ -27,13 +27,20 @@ And here's a code block, note the copy button to the right! copy me! By default, ``sphinx-copybutton`` will remove Python prompts from -each line that begins with them. For example, try copying the text +each line that begins with them. If it finds lines that start with the +prompt text, all *other* lines will not be copied. +For example, try copying the text below: .. code-block:: python >>> a = 2 >>> print(a) + 2 + + >>> b = 'wow' + >>> print(b) + wow The text that ``sphinx-copybutton`` uses can be configured as well. See :ref:`configure_copy_text` for more information. @@ -70,8 +77,8 @@ extensions list. E.g.: ... ] -When you build your site, your code blocks should now have little copy buttons to their -right. Clicking the button will copy the code inside! +When you build your site, your code blocks should now have little copy buttons +to their right. Clicking the button will copy the code inside! Customization ============= @@ -92,52 +99,78 @@ overwrite sphinx-copybutton's behavior. .. _configure_copy_text: -Customize the text that is removed during copying -------------------------------------------------- +Strip and configure input prompts for code cells +------------------------------------------------ -By default, ``sphinx-copybutton`` will remove Python prompts (">>> ") from -the beginning of each line. To change the text that is removed (or to remove -no text at all), add the following configuration to your ``conf.py`` file: +By default, ``sphinx-copybutton`` will copy the entire contents of a code +block when the button is clicked. For many languages, it is common to +include **input prompts** with your examples, along with the outputs from +running the code. -.. code:: python +``sphinx-copybutton`` provides functionality to both +strip input prompts, as well as *only* select lines that begin with a prompt. +This allows users to click the button and *only* copy the input text, +excluding the prompts and outputs. - copybutton_skip_text = "sometexttoskip" +To define the prompt text that you'd like removed from copied text in your code +blocks, use the following configuration value in your ``conf.py`` file: -Note that this text will only be removed from lines that *begin* with the text. +.. code-block:: -Use a different copy button image ---------------------------------- + copybutton_prompt_text = "myinputprompt" + +When this variable is set, ``sphinx-copybutton`` will remove the prompt from +the beginning of any lines that start with the text you specify. In +addition, *only* the lines that contain prompts will be copied if any are +discovered. If no lines with prompts are found, then the full contents of +the cell will be copied. + +For example, to exclude traditional Python prompts from your copied code, +use the following configuration: + +.. code-block:: + + copybutton_prompt_text = ">>> " -To use a different image for your copy buttons, the easiest thing to do is -to add a small bit of javascript to your Sphinx build that points the image -to something new. Follow these steps: +Configure whether *only* lines with prompts are copied +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -1. Create a new javascript file in your site's static folder (e.g., `_static/js/custom.js`). - In it, put the following code: +By default, if sphinx-copybutton detects lines that begin with code prompts, +it will *only* copy the text in those lines (after stripping the prompts). +This assumes that the rest of the code block contains outputs that shouldn't +be copied. - .. code-block:: javascript +To disable this behavior, use the following configuration in ``conf.py``: - const updateCopyButtonImages = () => { - const copybuttonimages = document.querySelectorAll('a.copybtn img') - copybuttonimages.forEach((img, index) => { - img.setAttribute('src', 'path-to-new-image.svg') - }) - } +.. code-block:: python + + copybutton_only_copy_prompt_lines = False - runWhenDOMLoaded(updateCopyButtonImages) +In this case, all lines of the code blocks will be copied after the prompts +are stripped. +Configure whether the input prompts should be stripped +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -2. Add this javascript file to your `conf.py` configuration like so: +By default, sphinx-copybutton will remove the prompt text from lines +according to the value of ``copybutton_prompt_text``. - .. code-block:: python +To disable this behavior and copy the full text of lines with prompts +(for example, if you'd like to copy *only* the lines with prompts, but not +strip the prompts), use the following configuration in ``conf.py``: + +.. code-block:: python - def setup(app): - app.add_javascript('js/custom.js'); + copybutton_remove_prompts = False + +Use a different copy button image +--------------------------------- -This will replace the copybutton images each time the page loads! +To use a different image for your copy buttons, do the following: -**If you know of a better way to do this with sphinx, please don't hesitate to -recommend something!** +1. Place the image in the ``_static/`` folder of your site. +2. Set the ``copybutton_image_path`` variable in your ``conf.py`` to be the + path to your image file, **relative to** ``_static/``. Development =========== diff --git a/setup.py b/setup.py index 128aa47..9797bc9 100644 --- a/setup.py +++ b/setup.py @@ -33,5 +33,8 @@ '_static/copybutton.js', '_static/copy-button.svg', '_static/clipboard.min.js']}, - classifiers=["License :: OSI Approved :: MIT License"] + classifiers=["License :: OSI Approved :: MIT License"], + install_requires=[ + "sphinx>=1.8" + ] ) diff --git a/sphinx_copybutton/__init__.py b/sphinx_copybutton/__init__.py index baa7fde..168aabc 100644 --- a/sphinx_copybutton/__init__.py +++ b/sphinx_copybutton/__init__.py @@ -7,18 +7,26 @@ def scb_static_path(app): static_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '_static')) app.config.html_static_path.append(static_path) -def add_skip_text_js(app): - skip_text = app.config['copybutton_skip_text'] - app.add_js_file(None, body="var copybuttonSkipText = '{}';".format(skip_text)) +def add_to_context(app, config): + # Update the global context + config.html_context.update({'copybutton_prompt_text': config.copybutton_prompt_text}) + config.html_context.update({'copybutton_only_copy_prompt_lines': config.copybutton_only_copy_prompt_lines}) + config.html_context.update({'copybutton_remove_prompts': config.copybutton_remove_prompts}) + config.html_context.update({'copybutton_image_path': config.copybutton_image_path}) def setup(app): print('Adding copy buttons to code blocks...') # Add our static path app.connect('builder-inited', scb_static_path) - app.connect('builder-inited', add_skip_text_js) # configuration for this tool - app.add_config_value("copybutton_skip_text", ">>> ", "html") + app.add_config_value("copybutton_prompt_text", "", "html") + app.add_config_value("copybutton_only_copy_prompt_lines", True, "html") + app.add_config_value("copybutton_remove_prompts", True, "html") + app.add_config_value("copybutton_image_path", "copy-button.svg", "html") + + # Add configuration value to the template + app.connect("config-inited", add_to_context) # Add relevant code to headers app.add_css_file('copybutton.css') diff --git a/sphinx_copybutton/_static/copybutton.js b/sphinx_copybutton/_static/copybutton.js_t similarity index 66% rename from sphinx_copybutton/_static/copybutton.js rename to sphinx_copybutton/_static/copybutton.js_t index b79df3d..e91c792 100644 --- a/sphinx_copybutton/_static/copybutton.js +++ b/sphinx_copybutton/_static/copybutton.js_t @@ -65,24 +65,39 @@ const temporarilyChangeTooltip = (el, newText) => { var copyTargetText = (trigger) => { var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); var textContent = target.textContent.split('\n'); - // Prevent breaking of the copy functionality, for themes which don't - // set copybuttonSkipText properly. - if(! ("copybuttonSkipText" in window)){ - var copybuttonSkipText = ">>> "; - console.warn(`sphinx_copybutton: - The theme that was used to generate this document, does not support the setting for 'copybutton_skip_text', - which is why its default of '>>> ' was used. - Please tell the theme developers to include javascript files with the template tag 'js_tag' for sphinx>=1.8. - Example: https://github.com/readthedocs/sphinx_rtd_theme/blob/ab7d388448258a24f8f4fa96dccb69d24f571736/sphinx_rtd_theme/layout.html#L30 - `); - } + var copybuttonPromptText = '{{ copybutton_prompt_text }}'; // Inserted from config + var onlyCopyPromptLines = {{ copybutton_only_copy_prompt_lines | lower }}; // Inserted from config + var removePrompts = {{ copybutton_remove_prompts | lower }}; // Inserted from config - textContent.forEach((line, index) => { - if (line.startsWith(copybuttonSkipText)) { - textContent[index] = line.slice(copybuttonSkipText.length) + // Text content line filtering based on prompts (if a prompt text is given) + if (copybuttonPromptText.length > 0) { + // If only copying prompt lines, remove all lines that don't start w/ prompt + if (onlyCopyPromptLines) { + linesWithPrompt = textContent.filter((line) => { + return line.startsWith(copybuttonPromptText) || (line.length == 0); // Keep newlines + }); + // Check to make sure we have at least one non-empty line + var nonEmptyLines = linesWithPrompt.filter((line) => {return line.length > 0}); + // If we detected lines w/ prompt, then overwrite textContent w/ those lines + if ((linesWithPrompt.length > 0) && (nonEmptyLines.length > 0)) { + textContent = linesWithPrompt; + } + } + // Remove the starting prompt from any remaining lines + if (removePrompts) { + textContent.forEach((line, index) => { + if (line.startsWith(copybuttonPromptText)) { + textContent[index] = line.slice(copybuttonPromptText.length); + } + }); } - }); - return textContent.join('\n') + } + textContent = textContent.join('\n'); + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent } const addCopyButtonToCodeCells = () => { @@ -102,7 +117,7 @@ const addCopyButtonToCodeCells = () => { const clipboardButton = id => ` - ${messages[locale]['copy_to_clipboard']} + ${messages[locale]['copy_to_clipboard']} ` codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) })