sphinx-copybutton icon indicating copy to clipboard operation
sphinx-copybutton copied to clipboard

Exclude unselectable text from being copied

Open bicarlsen opened this issue 4 years ago • 16 comments

In the sphinx_rtd_theme I was using the :linenos: option for the literalinclude directive to display line numbers for my code blocks. However, when using the copybutton extenstion for users to easily copy the code, the line numbers were being included in the copied text, even though they aren't selectable by the user.

I added functionality to exclude this unselectabel text from the copied text, along with an option copybutton_exclude_unselectable to determine its behavior. This option is by default true.

bicarlsen avatar Sep 20 '21 16:09 bicarlsen

Thanks for submitting your first pull request! You are awesome! :hugs:
If you haven't done so already, check out EBP's Code of Conduct and our Contributing Guide, as this will greatly help the review process.
Welcome to the EBP community! :tada:

welcome[bot] avatar Sep 20 '21 16:09 welcome[bot]

When running pre-commint run --all and npm test on my local machine all the tests pass, so I am not sure why it is failing here.

bicarlsen avatar Sep 20 '21 19:09 bicarlsen

This should be merged, @choldgraf. Could you give it a look?

theletterf avatar Oct 11 '21 09:10 theletterf

One more quick thought - another option would be to let users specify one or more classes that should be removed from the text before copying. That way I think we could simplify the copying logic considerably. For example, if the default class to be excluded was .linenos, then the code could simply be:

// Clone our target so we don't modify the live DOM
clone = target.cloneNode(true)

// Select the classes we want to remove, and then remove each one
clone.querySelectorAll(".linenos").forEach((node) => (node.remove()))

// This should not have any elements we don't want
textToCopy = clone.innerText;

I've just merged a PR to add a couple minimal examples to the docs that should demonstrate this behavior not working. We can use this to confirm that this PR does, in fact, fix the problem :-)

https://github.com/executablebooks/sphinx-copybutton/pull/140

choldgraf avatar Oct 11 '21 15:10 choldgraf

can we imagine any situation where an author would want a non-selectable text to be copied? If not then I'd be fine just making this happen and not exposing another config key

I can not, but figured I would leave it as an option just in case. I'm happy to remove it though.

bicarlsen avatar Oct 11 '21 20:10 bicarlsen

One more quick thought - another option would be to let users specify one or more classes that should be removed from the text before copying. That way I think we could simplify the copying logic considerably.

I agree and I mentioned this in regard to Issue #9 as I think it is all tied in together. I'm happy to add that functionality, but wanted to get commetns on it first.

bicarlsen avatar Oct 11 '21 20:10 bicarlsen

I do like the simplicity of the implementation in https://github.com/executablebooks/sphinx-copybutton/pull/138#issuecomment-940149343 so unless we can see any downsides to that approach, I'd be fine going that direction

choldgraf avatar Oct 13 '21 23:10 choldgraf

@choldgraf , I agree about your comment regarding #138 (comment). There is a reason I did it recursively, but can't remember the exact reason now. I'll reimplmenet using the Node.clone method, and keep the recursive method off to the side incase it's needed later.

bicarlsen avatar Oct 18 '21 09:10 bicarlsen

I remember why I did it recursively. It's because I exclude Node's who have the userSelect style set to none. However, this property is only available when using getComputedStyle, which must be used on each Node individually.

@choldgraf , what is your opinion on excluding unselectable Nodes via th .linenos class, or by checking directly the userSelect property?

bicarlsen avatar Oct 18 '21 19:10 bicarlsen

@bicarlsen sorry for the slow response! My general tendency is to go with the simpler possible where we can, unless there's a strong reason to use the more complex approach. Here, it seems like excluding the .linenos class will get us roughly the same functionality with a lot less JS, so I'd be +1 on taking that approach

choldgraf avatar Oct 25 '21 02:10 choldgraf

@choldgraf Great, we should be good to go. I haven't written any tests for it yet, but will do so as soon as I have time.

bicarlsen avatar Oct 25 '21 07:10 bicarlsen

@choldgraf just wanted to give this a bump, since it's been a while.

bicarlsen avatar Nov 30 '21 18:11 bicarlsen

you mentioned that you wanted to write tests, I think that still needs to happen right?

I tried to implement testing but apparently it is diffuclt to test functions that use .innerText because it requires a layout engine, which jsdom, which most of the testing utilities use, doesn't include.

I've included the tests in the test.js file, but commented out their actual running.

bicarlsen avatar Dec 01 '21 12:12 bicarlsen

I just found a reason why "unselectable text" is better than .linenos:

When rendering shell prompts, the $ shell prompt before a command is un-selectable, so that copying does the right thing when copying. Using the copybutton currently includes $. Who knows what other highlighters would be this smart.

Example: text boxes found in this section: https://coderefinery.org/git-intro/basics/#recording-a-snapshot-with-git

So, my vote for the "unselectable text" method (not that it counts for much).

rkdarst avatar Mar 22 '22 10:03 rkdarst

This became an issue in a workshop, so I did more investigation. The previous version of this PR (minimum working copy: https://github.com/rkdarst/sphinx-copybutton/tree/exclude-unselectable-new ) did work to exclude unselectable text, including the $ in shell-session highlighted blocks.

But, in my live site, even normal un-selection didn't work. It turns out that not selecting $ comes from sphinx_rtd_theme, which is interesting, since the parsing comes from pygments (and the reason it doesn't work on my live site on readthedocs is that it uses sphinx_rtd_theme that is too old): https://github.com/readthedocs/sphinx_rtd_theme/blob/0da22b885be387b79f7552c92be00fd14d11228a/src/sass/_theme_rst.sass#L103-L106

  div.highlight
    span.linenos, .gp
      user-select: none
      pointer-events: none

If you search pygments, I find that gp stands for the Generic.Prompt token, so there is already some sort of abstraction layer leakage between the themes and parsers.

So my proposal:

  • decide if we go with unselectable text or configurable.
    • Now I am more on the side of the current strategy (explicitly define the excluded classes), but the .lineno .gp information will start being copied around more and more places which doesn't seem ideal, but anyway it's still going to be copied to the themes...
  • add .gp to the list of default excludes if we stay with this strategy
  • add .gp as user-select: none to sphinx-book-theme? a quick grep doesn't show that string in the relevant context. I don't even know if it uses pygments as a highlighter so that this is relevant, though. (I don't yet use the theme myself)

rkdarst avatar Jun 10 '22 07:06 rkdarst

For reference, I just found this:

Apparently .gp (GenericPrompt) is also now excluded in the basic Sphinx CSS: https://github.com/sphinx-doc/sphinx/pull/9120/files (a bit more commentary on this)

Sphinx basic.css_t excludes the following things: table.highlighttable td.linenos, span.linenos, div.highlight span.gp.

Upstream pygments doesn't seem to do any user-select: none in any CSS (though I'm not sure if it is designed to do this, there aren't many *.css* files in there)

rkdarst avatar Jun 16 '22 10:06 rkdarst

#178 is a rebase of this that resolves conflicts with the docs and hopefully includes the suggestions from above.

rkdarst avatar Sep 13 '22 08:09 rkdarst