11

I'm working on adding line number support to Rainbow, a syntax highlighter, but I can't figure out how to make the line numbers uncopyable.

Disabling selection via user-select: none; makes an element unhighlightable, but you can still copy its text by highlighting around it and then copying, which ends up copying the line numbers along with code.

Here is a working example of the problem: http://jsfiddle.net/CjJLv/8/

Any help would be appreciated. Thanks!

Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96
Blender
  • 289,723
  • 53
  • 439
  • 496
  • I suggest a approach than table layout. So you have a
    with the line nr and a div that holds the table. If the line numbers floats left and the code goes to the right you cannot even select them. If a solution comes by it will work even better.
    – Tessmore Aug 20 '12 at 22:55
  • 1
    @Tessmore: Can you make a `
      ` have two columns that all scale according to their contents? I had a `
      ` layout originally, but it was impossible to justify the line numbers evenly in one column. I'm also trying to keep line wrapping enabled, which is why I think the table layout is the most appropriate.
    – Blender Aug 20 '12 at 22:57
  • If you give it the same font/line-height properties. They don't have to be in
    1. but it made sense as you use the css to generate the line nr's and then google doesn't have to index your numbers, just the code. (I didn't mean you should put the code in the
    2. as you get the same problem, but I clicked too fast)
    – Tessmore Aug 20 '12 at 22:59
  • Most syntax highlighters with line number support provide a link that allows you to copy the text only, without line numbers or any other sort of formatting. – Robert Harvey Aug 20 '12 at 23:16
  • @Blender I'd be interested in seeing the failed results of a `
      `; this has consistently been the best way I've found to create line numbers that aren't selectable.
    – Phrogz Aug 20 '12 at 23:29
  • @Phrogz: See [elclanrs's answer](http://stackoverflow.com/a/12046555/464744). The thing that prevents me from using `
      ` is line wrapping.
    – Blender Aug 20 '12 at 23:31
  • @Blender What is missing from this solution? http://jsfiddle.net/CjJLv/24/ (No, it doesn't have the whole library involved, but you can decorate the contents of those li appropriately.) It supports line-wrapping, and copies to the clipboard with leading whitespace intact. **Edit**: Oh, but only for Chrome. Nevermind! – Phrogz Aug 21 '12 at 14:55
  • @Phrogz: I didn't go with an `
      ` because the left column's width had to be fixed. I doubt that I would ever put anything with more than 100 lines inside of one of these boxes, but I just don't see what the problem is with tables in this instance.
    – Blender Aug 21 '12 at 20:52

4 Answers4

7

Okay, the easiest way in compliant browsers, and, sadly, not reliable cross-browser, is to use generated content (I've removed the various parts where index was being added to textual content in the plug-in, and used the following (at the end of the CSS) to implement un-copyable text:

table.rainbow {
    counter-reset: line;
}

table.rainbow tbody tr td:first-child {
    counter-increment: line;
}

table.rainbow tr td:first-child::before {
    content: counter(line);
}

JS Fiddle demo.

This does, though, have some rather large flaws (the cross-browser unfriendly approach being the biggest), so I'll try for something better...

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • 1
    Thanks! I think this is about as good as it gets for unselectable text. I'll just fallback to my old method if a browser doesn't support `:before`, which should account for IE and old versions of other browsers. – Blender Aug 20 '12 at 23:26
  • Perhaps you can use `attr()` in CSS for older browsers: http://jsfiddle.net/CjJLv/23/. According to MDN even IE8 supports it (admittedly not tested myself). – pimvdb Aug 20 '12 at 23:28
  • @pimvdb: I think the `attr()` approach is the cleanest, considering the number of browsers that support it. – Blender Aug 21 '12 at 00:10
1

I would just add a regular list.

if (window.Rainbow) window.Rainbow.linecount = (function(Rainbow) {
    Rainbow.onHighlight(function(block) {
        var lines = $(block).text().split('\n');
        var $lines = $('<ul class="lines"/>');
        for (var i = 0, len = lines.length; i < len; i++) {
            $lines.append('<li class="line"'+ i +'>'+ i +'</li>');
        }
        $(block).before($lines);
    });
})(window.Rainbow);​

And CSS:

.lines {
    float: left;
    padding-right: 1.5em;
    padding-left: .5em;
}

So now you can select just the code if you highlight carefully.

Demo: http://jsfiddle.net/elclanrs/CjJLv/18/

elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • This is actually very similar to what I had before (this is essentially what GitHub does for its syntax highlighting). The problem with this approach is that it doesn't allow line wrapping, which is something that I'd like to preserve: http://jsfiddle.net/CjJLv/22/ – Blender Aug 20 '12 at 23:23
  • Mmm... I see. I was thinking maybe you could trim the numbers from the clipboard but again clipboard is not cross-browser and probably buggy. – elclanrs Aug 20 '12 at 23:26
  • In any case you can just add a horizontal scrollbar, ugly but quick and effective. – elclanrs Aug 20 '12 at 23:28
  • I'm surprised that there aren't any CSS3 attributes that prevent browsers from copying text into the clipboard. If my way of doing it gets too complicated, I think I'll just put up with a horizontal scrollbar. – Blender Aug 20 '12 at 23:29
1

David Thomas's answer is perfect for line numbers. More generally, if you have other text you don't want to be copied, you can have it as generated content:

<style>#uniqueid::before { content: 'TEXT GOES HERE'; }</style>
<span id="uniqueid"></span>

But it's ugly to have to embed text in your CSS, so you can refine this using CSS attr() to read the text from an attribute in the HTML (as suggested by pimvdb):

<style>[data-nocopy]::before { content: attr(data-nocopy); }</style>
<span data-nocopy="TEXT GOES HERE"></span>
<span data-nocopy="AND HERE"></span>

Demo: http://jsbin.com/fob/1/edit

This works in Firefox, Safari, and Chrome due to 21-year-old(!) bugs in selecting generated content:

But in old IE (< 8) the text will be completely invisible; in newer IE it should be visible but may well be copyable. In general don't use this technique for anything critical, as these bugs might get fixed one day...

And use sparingly, as this can be very user-hostile.

John Mellor
  • 12,572
  • 4
  • 46
  • 35
0

You could display each line number as a sequence of <img>s.

Scott
  • 1
  • I found a [different line numbering plugin](https://github.com/Sjeiti/rainbow.linenumbers) for Rainbow that draws the line numbers on a long `` object to prevent copy/pasting, but the reason I chose my layout is to allow greater flexibility through CSS styling. `` tags suffer from the same limitation, so I can't really use them. – Blender Aug 20 '12 at 23:05