简体   繁体   中英

In knitr HTML output, include line numbers of R Markdown source code?

Question: Is there an automatic way to add the line numbers of the original R Markdown source code to the formatted code portions of the HTML output produced by knitr?

Purpose: My ultimate goal is to be able to quickly move to parts of my source R Markdown code that I identify need editing while reviewing the HTML output. Using line numbers is the fastest way I know to do this, but I welcome hearing others' strategies.

Solutions I've tried:

  • Although the chunk option attr.source = '.numberLines' will attractively add line numbers to the code parts of the HTML output, that option doesn't provide the source-code line numbers automatically (you must force that manually using .startFrom ) -- instead, the lines are renumbered at the beginning of each chunk and after each piece of output. In the following illustration, I've included .startFrom to force the line numbering to start at 10, to match the line number for test_data <- rnorm(10) which is the line number I want to see. A practical solution, however, needs the starting number to be automatic. Also, in the HTML output (shown beneath the code) the hist(test_data) line is renumbered starting with the same starting number, 10. I would want that to be 12, as in the source code. 示例源 R Markdown 的屏幕截图,显示行号 编织后输出HTML
  • This question ( How can I add line numbers that go across chunks in Rmarkdown? ) is related, but the OP just needed any unique identifier for each line, not necessarily the line numbers of the source code, with the solution being sequential numbers unrelated to the source-code line numbers.

Considered option: I've considered preprocessing my code by running an initial script that will add line numbers as comments at the end of lines, but I'd prefer a solution that is contained within the main knitr file.

UPDATED

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;
  ind = str.indexOf("\n");
  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.

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM