Hello all,

I noticed that when exporting a code block to html with line numbers that the 
line numbers get included when your try to copy/paste from a browser.

I made a small change to the default CSS so that line numbers are no longer 
included in a copy/paste from a browser such as firefox (but the existing 
behavior remains for text-based browsers such as eww).

A note on the attribute name, data-linenr: It could be named the same as the 
existing attribute used for the javascript highlighting, data-ox-html-linenr 
and everything would still work (since the CSS added in this patch is selecting 
on the .linenr class). However that might be a source of subtle bugs in the 
future...

In addition to the patch, I have attached an example org file that I used for 
manual testing.
From 32b6138e87e033970c8968a94d90203727ed8750 Mon Sep 17 00:00:00 2001
From: Catsup4 <[email protected]>
Date: Thu, 9 Apr 2026 15:18:55 +0700
Subject: [PATCH] ox-html.el: support line numbers as a :before element in code
 blocks

* ox-html.el (org-html-do-format-code): When supported, display the
line number of a code block in a `:before' element so that it isn't
included in copy/paste actions.

Without this patch, when you export a code block with line numbers,
the numbers are prepended to the same html element as the line of
code.  Therefore, when you highlight the line in a browser to
copy/paste the code, the line numbers are also copied.

This change adds the data attribute `data-linenr' to the span that
contains the line number.  The default CSS has been modified so that:

1.  If the browser supports the the `attr()' CSS
   function (e.g.  firefox), then the inlined line number is hidden and
   the line number is displayed in a `:before' element.[fn:1]

2.  If the browser does not support this CSS (e.g.  eww), the existing
   behavior is used.

This means you can copy/paste multiple lines from a code block without
having the line numbers included.

TINYCHANGE
---
 lisp/ox-html.el | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index 654e510e4..c1b57f873 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -441,7 +441,13 @@ This affects IDs that are determined from the ID property.")
   #org-div-home-and-up
    { text-align: right; font-size: 70%; white-space: nowrap; }
   textarea { overflow-x: auto; }
-  .linenr { font-size: smaller }
+  .linenr {
+    font-size: smaller;
+    @supports (content: attr(data-linenr)) {
+      visibility: hidden;
+      &::before { content: attr(data-linenr); visibility: visible; }
+    }
+  }
   .code-highlighted { background-color: #ffff00; }
   .org-info-js_info-navigation { border-style: none; }
   #org-info-js_console-label
@@ -2473,8 +2479,8 @@ wrapped in code elements."
 	     (concat
 	      ;; Add line number, if needed.
 	      (when num-start
-		(format "<span class=\"linenr\">%s</span>"
-			(format num-fmt line-num)))
+                (let ((ln (format num-fmt line-num)))
+		  (format "<span data-linenr=\"%s\" class=\"linenr\">%s</span>" ln ln)))
 	      ;; Transcoded src line.
 	      (if wrap-lines
 		  (format "<code%s>%s</code>"
-- 
2.53.0

#+OPTIONS: toc:nil num:nil timestamp:nil 

* An example =bash= script

#+begin_src bash -n -r
  hello_world() {
      local greeting="${1:-hello}"    (ref:arg1)
      local name="${2:-$USER}"        (ref:arg2)

      echo $greeting $name            (ref:out)
  }

  hello_world 
#+end_src

#+RESULTS:
: hello catsup

- In line [[(arg1)]] we take the first argument as the greeting, defaulting to =hello=.
- In line [[(arg2)]] we take the second argument as the name, defaulting to the =$USER= global.
- Finally, at [[(out)][Line (out)]] we output the args concatenated together.


I tested the patch with various combinations of the following local
variables toggled:

# Local Variables:
# org-html-html5-fancy: t
# org-html-head-include-scripts: t
# org-html-wrap-src-lines: t
# End:

Reply via email to