diff --git a/godoc/server.go b/godoc/server.go
index 162e5a6a..ffe59972 100644
--- a/godoc/server.go
+++ b/godoc/server.go
@@ -621,7 +621,16 @@ func formatGoSource(buf *bytes.Buffer, text []byte, links []analysis.Link, patte
// linkWriter, so we have to add line spans as another pass.
n := 1
for _, line := range bytes.Split(buf.Bytes(), []byte("\n")) {
- fmt.Fprintf(saved, "%6d\t", n, n)
+ // The line numbers are inserted into the document via a CSS ::before
+ // pseudo-element. This prevents them from being copied when users
+ // highlight and copy text.
+ // ::before is supported in 98% of browsers: https://caniuse.com/#feat=css-gencontent
+ // This is also the trick Github uses to hide line numbers.
+ //
+ // The first tab for the code snippet needs to start in column 9, so
+ // it indents a full 8 spaces, hence the two nbsp's. Otherwise the tab
+ // character only indents about two spaces.
+ fmt.Fprintf(saved, ` `, n, n)
n++
saved.Write(line)
saved.WriteByte('\n')
diff --git a/godoc/static/static.go b/godoc/static/static.go
index c89ffaad..c8915765 100644
--- a/godoc/static/static.go
+++ b/godoc/static/static.go
@@ -2932,6 +2932,11 @@ pre .ln {
-ms-user-select: none;
user-select: none;
}
+.ln::before {
+ /* Inserting the line numbers as a ::before pseudo-element avoids making
+ * them selectable; it's the trick Github uses as well. */
+ content: attr(data-content);
+}
body {
color: #222;
}
diff --git a/godoc/static/style.css b/godoc/static/style.css
index 025327b8..e89ac290 100644
--- a/godoc/static/style.css
+++ b/godoc/static/style.css
@@ -37,6 +37,11 @@ pre .ln {
-ms-user-select: none;
user-select: none;
}
+.ln::before {
+ /* Inserting the line numbers as a ::before pseudo-element avoids making
+ * them selectable; it's the trick Github uses as well. */
+ content: attr(data-content);
+}
body {
color: #222;
}