4

I would like to add code line numbering to the HTML output of my Rmarkdown file. I'd be happy with any approach that uniquely identifies every line of code in the output (e.g., sequential line numbers that increment across the entire document, OR code chunks are identified by their own index, and within these code chunks line numbers start at 1).

I have been unable to achieve this.

Using other stackoverflow answers and this blog post I have gotten line numbers, but they reset with every new chunk.

I have the following Rmd file:

---
output:
  html_document:
    highlight: kate
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  class.source = "numberLines lineAnchors"
  )
```

```{r}
5 + 5
3 * 9
```

```{r}
x <- 5
x * 3
```

Which compiles to:

As you can see, when a chunk is split by output or a new chunk begins, the line numbers reset. What I would like to see is line numbers as 1, 2, 3, 4, instead of 1, 1, 1, 2.

Anyone know how to get this to work? line numbers reset when chunk is split or new chunk

richarddmorey
  • 976
  • 6
  • 19

3 Answers3

3

I managed to get sequential line numbers on an HTML document through the addition of some jquery at the end of the Rmd document:

---
output:
  rmdformats::html_clean:
    highlight: kate
    toc: true
    use_bookdown: true
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  class.source = "numberLines lineAnchors"
  )
```

```{r}
5 + 5
3 * 9
```

```{r}
x <- 5
x * 3
```

<!-- 
The javascript below will reset the line numbers when
the document loads. 
-->

<script>
$(function() {
  $(".sourceLine").each( function( index ){
    $( this ).attr( "data-line-number", index + 1 );
  });
});
</script>

The resulting document has the sequential line numbers, as shown below: sequential line numbers across chunks

It's a bit hacky because it requires jquery (probably a way to do it by including some css that overrides the existing), but it works and looks tidy.

UPDATE AS OF 4 AUGUST 2020

They changed the way line numbering works (now it is based on CSS counters). In order to get it to work sequentially across the whole document now, you'll need to insert some CSS. Below is a complete working example. The important code is in the <style> block. It resets the relevant counter at the body (document) level, and prevents the counter from being reset for each block of code.

---
output:
  rmdformats::html_clean:
    highlight: kate
    toc: true
    use_bookdown: true
---

<style>
body
  { counter-reset: source-line 0; }
pre.numberSource code
  { counter-reset: none; }
</style>

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  class.source = "numberLines lineAnchors"
  )
```

```{r}
5 + 5
3 * 9
```

```{r}
x <- 5
x * 3
```

richarddmorey
  • 976
  • 6
  • 19
1

As @mb21 pointed out in the comments, one can control the first line number of a code block by adding a startFrom attribute. However, this cannot be done manually, as knitr can split code blocks into multiple blocks, depending on content. We'll want to add this attribute programmatically.

The easiest method that I'm aware of is to let pandoc modify the structure, as all blocks have already been evaluated by the time pandoc sees them. We will use Lua instead of R for scripting, as that is the most efficient when working with pandoc filters.

The script will keep track of the number of code lines it has seen so far, and add the correct startFrom attribute to source code blocks. We can distinguish between source blocks and result blocks by checking for the numberLines class: only the former have that class.

-- Number of code lines seen so far.
local total_lines_count = 0

-- Count the number of newlines in a string.
function count_lines (text)
  local count = 0
  local last_pos = 0
  repeat
    last_pos = string.find(text, '\n', last_pos + 1, true)
    count = count + 1
  until not last_pos
  return count
end

function CodeBlock (cb)
  -- do nothing for result blocks
  if not cb.classes:includes 'numberLines' then return nil end

  cb.attributes['startFrom'] = total_lines_count + 1
  total_lines_count = total_lines_count + count_lines(cb.text)
  return cb
end

Now the only thing left is to tell pandoc to invoke the filter during conversion. That can be done by adding the --lua-filter option to pandoc_args:

---
output:
  html_document:
    highlight: kate
    pandoc_args: ['--lua-filter=number-lines.lua']
---

The file number-lines.lua should contain the Lua code above and be placed in the same folder as your document, or in the filters folder below pandoc's data directory (see pandoc -v).

The advantage of this approach is that it works with HTML as well as PDF output.

tarleb
  • 19,863
  • 4
  • 51
  • 80
0

Here is what I can propose: using line auto-numbering feature + using a CSS counter to display chunk number on the right ("cell"). You may then refer to a chunk+line.

---
title: "ChunkLine numbering"
author: "WeLoveDataScience"
output:
  html_document:
    highlight: pygments
---

```{css, echo=FALSE}
body {
  counter-reset: nchunk;
}


pre.r {
  counter-increment: nchunk;
  position: relative;
  overflow: visible;
}

pre.r::before {
  content: 'C[' counter(nchunk) ']';
  display: inline-block;
  position: absolute;
  right: 0em;
  color: rgb(50, 60, 160);
}
```

```{r cars, class.source = c("numCode", "r", "numberLines")}
summary(cars)
head(cars)
foo=function(x){
  2*x
}
```

```{r other, class.source = c("numCode", "r", "numberLines")}
1+1
```
Eric Lecoutre
  • 1,461
  • 16
  • 25