Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

possibility to add line numbers to code blocks #101

Open
pietroppeter opened this issue Jul 2, 2022 · 15 comments · May be fixed by #220
Open

possibility to add line numbers to code blocks #101

pietroppeter opened this issue Jul 2, 2022 · 15 comments · May be fixed by #220
Assignees
Labels
enhancement New feature or request

Comments

@pietroppeter
Copy link
Owner

we should have the ability to add line numbers to code blocks, both those generate by nbCode and nbFile. Ideally it should be something customizable by single block and one could set a global option. defaults would probably be to have it disabled in code blocks produce by nbCode and maybe enabled for blocks produce by nbFile.

@pietroppeter pietroppeter added the enhancement New feature or request label Jul 2, 2022
@dlesnoff
Copy link
Contributor

What about the new block nbCodeSkip?
Should the global option enable line numbers for both, or only for nbCode?
Should this work for nbPython code blocks too?

@HugoGranstrom
Copy link
Collaborator

IMO, we should have a single switch in nimib that all the official nbCode-like blocks use. nbPython will be moved to nimibex but I think we should implement it for it as well.

@pietroppeter
Copy link
Owner Author

pietroppeter commented Mar 29, 2023

Yes I think one way to implement a global switch for all code like blocks would be to make sure that nbCodeSource partial is general enough to support all code blocks and is used by all code blocks. Then implement a addLineNumbers renderProc and make sure it is present in all code blocks. This proc would only adds line numbers if a boolean flag enableLineNumbers is present (and true) in either block context or (only if not present) it would check the presence of enableLineNumbers true in doc context. In this way can have a global switch (add enable true in doc context), that can be switched off for specific blocks (add enable false to specific block). Also line numbers can be added only to specific blocks if disabled globally (add enable true for specific block).

@pietroppeter
Copy link
Owner Author

pietroppeter commented Jun 10, 2023

notes from call nimib speaking hours:

proc addLineNumbers(doc: var NbDoc, blk: var NbBlock) =
  if blk.context["enableLineNumbers].castBoolor doc.context["enableLineNumbers].castBool:
    blk.context["codeHighlighted"] = addLineNumbersToHIghlightedCode(blk.context["codeHighlighted"].castStr)

template enableLineNumbersDoc =
  nb.context["enableLineNumber"] = true

template enableLineNumbersBlock =
  nb.blk.context["enableLineNumber"] = true

assert addLineNumbersToHIghlightedCode("""
func</span> decode(secret: openArray[<span class="hljs-built_in">int</span>]): <span class="hljs-built_in">string</span> =
  <span class="hljs-comment">## classified by NSA as &lt;strong&gt;TOP SECRET&lt;/strong&gt;</span>
  <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> secret:
    <span class="hljs-literal">result</span>.add <span class="hljs-built_in">char</span>(c)
""") == """
<span class="hljs-comment">1</span> func</span> decode(secret: openArray[<span class="hljs-built_in">int</span>]): <span class="hljs-built_in">string</span> =
<span class="hljs-comment">2</span>   <span class="hljs-comment">## classified by NSA as &lt;strong&gt;TOP SECRET&lt;/strong&gt;</span>
<span class="hljs-comment">3</span>   <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> secret:
<span class="hljs-comment">4</span>     <span class="hljs-literal">result</span>.add <span class="hljs-built_in">char</span>(c)
"""

@dlesnoff
Copy link
Contributor

dlesnoff commented Oct 4, 2023

I noticed that the example you gave to me has one issue. If someone tries to select the code, he would also select the line numbers.
I have asked ChatGPT4 for a small html/JS example to render a code block snippet with a copy-to-clipboard button and lines numbering.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Code Renderer</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <button id="copyButton">Copy to Clipboard</button>
  <pre id="code"><code>// Here is your code snippet
function helloWorld() {
  console.log('Hello, world!');
}</code></pre>
  <script src="script.js"></script>
</body>
</html>
JS
document.addEventListener("DOMContentLoaded", function() {
  const codeElement = document.getElementById('code');
  const copyButton = document.getElementById('copyButton');
  const codeText = codeElement.innerText;
  const lineCount = codeText.split('\n').length;

  let lineNumberHTML = '';
  for (let i = 1; i <= lineCount; i++) {
    lineNumberHTML += i + '\n';
  }

  const lineNumberElement = document.createElement('code');
  lineNumberElement.className = 'line-numbers';
  lineNumberElement.innerText = lineNumberHTML;
  codeElement.insertBefore(lineNumberElement, codeElement.firstChild);

  copyButton.addEventListener('click', function() {
    navigator.clipboard.writeText(codeText).then(function() {
      console.log('Code copied to clipboard');
    }).catch(function(err) {
      console.error('Could not copy text: ', err);
    });
  });
});
CSS
body {
  font-family: 'Courier New', Courier, monospace;
}

pre {
  position: relative;
  border: 1px solid #ccc;
  padding-left: 30px;
}

code {
  display: block;
  white-space: pre;
}

.line-numbers {
  position: absolute;
  left: 0;
  top: 0;
  padding: 0 10px;
  user-select: none; /* Prevent text selection */
  color: #888;
}

It encloses the code inside <pre> and <code> tags which could be replaced by <span class="hljs-comment"> but inside a <pre>. I do not know how I could do something similar as the JS code.

@dlesnoff dlesnoff linked a pull request Nov 5, 2023 that will close this issue
3 tasks
@dlesnoff
Copy link
Contributor

dlesnoff commented Nov 9, 2023

Hello,
I am not sure how to integrate the last changes that I made in my draft Pull Request, to the code block template.
I guess that I have to give as argument nb and nb.blk so my best guess for now is to change the template definition in src/nimib.nim:

template nbCode*(body: untyped) =
  newNbCodeBlock("nbCode", body):
    addLineNumbers(nb, nb.blk)
    captureStdout(nb.blk.output):
      body

This does absolutely nothing. Would you have any hint on how to proceed?

@HugoGranstrom
Copy link
Collaborator

We have something we call renderPlan which are procs that are run when we render a proc. We do the code highlighting in the renderPlan for example. It is defined here:

doc.renderPlans["nbCode"] = @["highlightCode"] # default partial automatically escapes output (code is escaped when highlighting)

A renderPlan consists of a list of renderProcs that are run in order. So that would be the correct place to add this feature.

I think the reason your example doesn't work is because the codeHighlighted hasn't been filled yet in the context because it is filled on render. So you pass in an empty string to your proc basically. And then it is overridden on render.

Let me know if you want a more thorough explaination, but it might take some time. I'm quite busy at the moment 😄

@HugoGranstrom
Copy link
Collaborator

To flesh out my previous answer, you could say nimib has 3 phases when it generates a document:

  1. Initialization: all the partials, renderProcs and renderPlans are populated. This is basically what you see in render.nim.
  2. Creation of blocks: this is what happens when you use a block, like nbCode: echo "Hello world". This will populate the block with its inputs in its context object. In the case of nbCode these are the raw code and captured output.
  3. Render: here the renderPlan (and its renderProcs) are run. And once they have run the partial for the block is rendered. It's here that the highlighted code is populated in the context.

So the problem you had was that you tried to add the line numbers in step 2 while the highlighted code wasn't available until step 3. But if you add it as a renderProc and add it to the renderPlan och nbCode it should be available to you.

@dlesnoff
Copy link
Contributor

Thank you for the explanations. I was able to move forward and debug the code. There are still discussions to have on the enableLineNumbersBlock and the HTML rendering of the numbers, but otherwise, I have a working proof of concept.

I wish you all the best for your work.

@HugoGranstrom
Copy link
Collaborator

Awesome, thanks 😄 regarding the copy-ability of the code blocks without including the line numbers, is wrapping the code in a table the usual solution to this? If so I'd say we could try it out and see how it works out 👍

@dlesnoff
Copy link
Contributor

If I believe the above JS generated by ChatGPT4 (thus highly not reliable), it's first approach is a div container.
It replaces in the above HTML:

<div class="code-container">
  <button id="copyButton" class="copy-button">Copy</button>
  <pre id="code"><code class="line-numbers">1
2
3
4</code><code>// Here is your code snippet
function helloWorld() {
  console.log('Hello, world!');
}</code></pre>
</div>

I am unsure of what is the “usual” method here. It could be interpreted as table values as well as element of a div. I'll give in a second post a table solution.

@pietroppeter
Copy link
Owner Author

pietroppeter commented Nov 15, 2023

from a google search, it appears there are alternative approaches that do not even need to add numbers in text but automatically generate them through css, it seems something we could explore if it works for us too: https://stackoverflow.com/a/41354764

@dlesnoff
Copy link
Contributor

dlesnoff commented Feb 19, 2024

Hello,
I tried to update the HTML approach but I don't remember exactly what I had in mind for the HTML div approach.
The table approach is too complex and risky. It removes code's indentation.

Both approaches, CSS and HTML should be tested, as some CSS styling might turn off line numbers.
I think that generating them through CSS is the best approach, as it can be changed on the fly.

@pietroppeter
Copy link
Owner Author

we were discussing about this with @HugoGranstrom during speaking hours, it is not clear to us how we can help? do you plan to work on the CSS one and we should discard the HTML one? to you want a review on the PR for the HTML approach (#220) and see if we can merge that? Let us know and thanks for working on this!

@pietroppeter
Copy link
Owner Author

This css has an interesting recipe based on simple css and html manipulation: https://css.winterveil.net/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants