pietroppeter / nimib

nimib 🐳 - nim πŸ‘‘ driven β›΅ publishing ✍
https://pietroppeter.github.io/nimib/
MIT License
180 stars 10 forks source link

possibility to add line numbers to code blocks #101

Open pietroppeter opened 2 years ago

pietroppeter commented 2 years ago

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.

dlesnoff commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 ```html Code Renderer
// Here is your code snippet
function helloWorld() {
  console.log('Hello, world!');
}
```
JS ```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 ```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 commented 11 months ago

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 commented 11 months ago

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: https://github.com/pietroppeter/nimib/blob/e256687a007f4e45e7f0291d5be96121aea9a470/src/nimib/renders.nim#L48 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 commented 10 months ago

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 commented 10 months ago

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 commented 10 months ago

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 commented 10 months ago

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 commented 10 months ago

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 commented 7 months ago

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 commented 7 months ago

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 commented 6 months ago

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