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: