Reverted to this update based on your request
I'm glad you figured out the issue. I hadn't considered or tested the code for a chunk that only had a single line of code. However, based on your feedback, I've accounted for it now.
If you'd like it accounted for and would like to keep the color in the code, let me know. I'll add it back with the fix for single lines of code. (I'll stick to the ES6.)
This version uses the line numbers you'll see in the source pane of RStudio. You must use RStudio for this to work. The following changes to the RMD are necessary:
- The
library(jsonlite)
and library(dplyr)
- An R chunk, which you could mark include and echo as false
- A set of script tags outside of and after that chunk
- A JS chunk (modified from my original answer)
The R chunk and R script need to be placed at the end of your RMD.
The JS chunk can be placed anywhere.
The R chunk and script tags **in order!
Put me at the end of the RMD.
```{r ignoreMe,include=F,echo=F}
# get all lines of RMD into object for numbering; R => JS object
cx <- rstudioapi::getSourceEditorContext()$contents
cxt <- data.frame(rws = cx, id = 1:length(cx)) %>% toJSON()
```
<script id='dat'>`r cxt`</script>
The JS chunk
This collects the R object that you made in the R chunk, but its placement does not matter. All of the R code will execute before this regardless of where you place it in your RMD.
```{r gimme,engine="js",results="as-is",echo=F}
setTimeout(function(){
scrCx = document.querySelector("#dat"); // call R created JSON*
cxt = JSON.parse(scrCx.innerHTML);
echoes = document.querySelectorAll('pre > code'); // capture echoes to #
j = 0;
for(i=0; i < echoes.length; i++){ // for each chunk
txt = echoes[i].innerText;
ix = finder(txt, cxt, j); // call finder, which calls looker
stxt = txt.replace(/^/gm, () => `${ix++} `); // for each line
echoes[i].innerText = stxt; // replace with numbered lines
j = ix; // all indices should be bigger than previous numbering
}
}, 300);
function looker(str) { //get the first string in chunk echo
k = 0;
if(str.includes("\n")) {
ind = str.indexOf("\n");
} else {
ind = str.length + 1;
}
sret = str.substring(0, ind);
oind = ind; // start where left off
while(sret === null || sret === "" || sret === " "){
nInd = str.indexOf("\n", oind + 1); // loop if string is blank!
sret = str.substring(oind + 1, nInd);
k++;
ind = oind;
oind = nInd;
}
return {sret, k}; // return string AND how many rows were blank/offset
}
function finder(txt, jstr, j) {
txsp = looker(txt);
xi = jstr.findIndex(function(item, j){ // search JSON match
return item.rws === txsp.sret; // search after last index
})
xx = xi - txsp.k + 1; // minus # of blank lines; add 1 (JS starts at 0)
return xx;
}
```
If you wanted to validate the line numbers, you can use the object cx
, like cx[102]
should match the 102 in the HTML and the 102 in the source pane.
I've added comments so that you're able to understand the purpose of the code. However, if something's not clear, let me know.

ORIGINAL
What I think you're looking for is a line number for each line of the echoes, not necessarily anything else. If that's the case, add this to your RMD. If there are any chunks that you don't want to be numbered, add the chunk option include=F
. The code still runs, but it won't show the content in the output. You may want to add that chunk option to this JS chunk.
```{r gimme,engine="js",results="as-is"}
setTimeout(function(){
// number all lines that reflect echoes
echoes = document.querySelectorAll('pre > code');
j = 1;
for(i=0; i < echoes.length; i++){ // for each chunk
txt = echoes[i].innerText.replace(/^/gm, () => `${j++} `); // for each line
echoes[i].innerText = txt; // replace with numbered lines
}
}, 300)
```
It doesn't matter where you put this (at the end, at the beginning). You won't get anything from this chunk if you try to run it inline. You have to knit for it to work.
I assembled some arbitrary code to number with this chunk.