# Auto Transforming Obsidian Code Blocks
In some of my [[Embedding Neovim HTML into Obsidian|previous posts]], we talked about how I transformed code blocks via Neovim to highlighted code blocks exactly like my Neovim configuration. The output is an **exact** match of my Neovim highlighting, with all of the colors, styles and even warnings and diagnostics directly copied into HTML which can be used to display in this blog.
But, I had a problem.
Even with ways to quickly add, edit, and update code blocks to use my Neovim highlighting, sometimes I would want to write something in pure Markdown and then instantly convert all the code blocks in my document to the Neovim highlighting. This wasn't too much of a hassle - copy the text, run my `Generate new NEOVIM INCLUDE` command from inside Obsidian, paste the text inside the opened Neovim, and then save and quit (which is quit difficult for some people).
But, I had a problem.
I'm a programmer! I don't want to do the same thing twice, let alone three times! Or even worse, N times!!
So, I had a solution.
I would continue to extend my [Obsidian plugin](https://github.com/tjdevries/obsidian-include-code) to be able to automatically transform all of the code blocks that are "regular code" into my "Neovim include" style.
# Usage
Here's an example of me actually what you're about to read on this very article! A little bit meta :)
![[auto-transform-code-blocks-example.mp4]]
# Implementation
The first thing I did was to add a new command to my Obsidian Plugin. This is really easy, it just involves registering a new command in the `onload` for a Plugin.
```neovim {"update":true,"path":"_/code/17242693352805.ts","timestamp":1725473857406}
<pre>
<span class="-keyword">async</span> <span class="-variable">onload</span><span class="Delimiter">(</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="Comment"><span class="-spell">// ... other plugin code ...</span></span>
<span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">addCommand</span></span><span class="Delimiter">(</span><span class="Delimiter">{</span>
<span class="-variable">id</span><span class="Delimiter">:</span> <span class="String">"transform-to-neovim-blocks"</span><span class="Delimiter">,</span>
<span class="-variable">name</span><span class="Delimiter">:</span> <span class="String">"Transform all code blocks to neovim blocks"</span><span class="Delimiter">,</span>
<span class="-variable"><span class="-function">callback</span></span><span class="Delimiter">:</span> <span class="Delimiter">(</span><span class="Delimiter">)</span> <span class="Operator">=></span> <span class="Delimiter">{</span>
<span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">transformAllCodeBlocksToNeovimBlocks</span></span><span class="Delimiter">(</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span><span class="Delimiter">,</span>
<span class="Delimiter">}</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
</pre>
```
^17242693352805
The command code is relatively straightforward as well, and is effectively put into three main sections:
1. Find all codeblocks that are not of type `neovim` (my special codeblock for Neovim highlighted code)
2. Create a file with the contents of the codeblock, and then replace the codeblock with an un-updated Neovim codeblock.
3. Update all the Neovim codeblocks in the file.
## Implementation: Find all codeblocks
Fortunately, Obsidian's API is very good, so it's quite easy to get all of the different Markdown "sections" in a file. A "section" is (to my understanding) defined as more-or-less anything that is a distinct Markdown element: header, paragraph, codeblock, etc. For us, we're only interested in codeblocks that are not of type `neovim`.
This code:
1. Gets the only the `"code"` sections
2. Skips if the first line contains `"neovim"` (since the first line of the block is the header)
3. Adds them to our `toTransform` array.
```neovim {"update":true,"path":"_/code/17242693352794.ts","timestamp":1725473857073}
<pre>
<span class="-keyword">const</span> <span class="-variable">contents</span> <span class="Operator">=</span> <span class="-keyword">await</span> <span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable">app</span><span class="Delimiter">.</span><span class="-variable">vault</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">read</span></span><span class="Delimiter">(</span><span class="-variable">file</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">lines</span><span class="Delimiter"><span class="Delimiter">:</span></span> <span class="-type-builtin">string</span><span class="Delimiter">[</span><span class="Delimiter">]</span> <span class="Operator">=</span> <span class="-variable">contents</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">split</span></span><span class="Delimiter">(</span><span class="String">"<span class="SpecialChar">\n</span>"</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">activeFile</span> <span class="Operator">=</span> <span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable">app</span><span class="Delimiter">.</span><span class="-variable">workspace</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">getActiveFile</span></span><span class="Delimiter">(</span><span class="Delimiter">)</span><span class="Operator">!</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">fileCache</span> <span class="Operator">=</span> <span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable">app</span><span class="Delimiter">.</span><span class="-variable">metadataCache</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">getFileCache</span></span><span class="Delimiter">(</span><span class="-variable">activeFile</span><span class="Delimiter">)</span><span class="Operator">!</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">sections</span> <span class="Operator">=</span> <span class="-variable">fileCache</span><span class="Delimiter">.</span><span class="-variable">sections</span><span class="Operator">!</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">toTransform</span> <span class="Operator">=</span> <span class="Delimiter">[</span><span class="Delimiter">]</span><span class="Delimiter">;</span>
<span class="-keyword">for</span> <span class="Delimiter">(</span><span class="-keyword">const</span> <span class="-variable">s</span> <span class="-keyword">of</span> <span class="-variable">sections</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-keyword">if</span> <span class="Delimiter">(</span><span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">type</span> <span class="Operator">===</span> <span class="String">"code"</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-keyword">const</span> <span class="-variable">first_line</span> <span class="Operator">=</span> <span class="-variable">lines</span><span class="Delimiter">[</span><span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">position</span><span class="Delimiter">.</span><span class="-variable">start</span><span class="Delimiter">.</span><span class="-variable">line</span><span class="Delimiter">]</span><span class="Delimiter">;</span>
<span class="-keyword">if</span> <span class="Delimiter">(</span><span class="-variable">first_line</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">includes</span></span><span class="Delimiter">(</span><span class="String">"neovim"</span><span class="Delimiter">)</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-keyword">continue</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="-variable">toTransform</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">push</span></span><span class="Delimiter">(</span><span class="-variable">s</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
<span class="Delimiter">}</span>
</pre>
```
^17242693352794
The reason we are keeping them in an array is because they are sorted top-to-bottom. This means, after pushing the elements into the array, we can just reverse it to modify the lines in place (nothing in the last code block will change any of the positions of the first code block)!
```neovim {"update":true,"path":"_/code/17242693352783.ts","timestamp":1725473856763}
<pre>
<span class="Comment"><span class="-spell">// Go back to front so we can modify lines</span></span>
<span class="-variable">toTransform</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">reverse</span></span><span class="Delimiter">(</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
</pre>
```
^17242693352783
## Implementation: Creating Files and Codeblocks
Now we iterate through each of the sections that we want to transform (remember, in reverse). After getting the extension of the markdown block and it's contents, we create a new file that goes int `_/code/<filename>.<extension>`. The reason I do this is because I love the [[File over App]] philosophy of Obsidian - even though for now I want to store all of the code blocks directly as formatted HTML in my Markdown documents, maybe someday I will want to recover all of the original files. Or even just update the colors on everything! If I didn't keep the original source files, then I wouldn't be able to do that.
So I create the file in this ignored folder `_/code/` in my Obsidian vault and then populate it with whatever was inside of the original codeblock.
Then I get the new Neovim codeblock lines (which is basically just a codeblock with type `neovim` and some additional metadata saved within the file so that I can update and reload it later), and splice those into the original `lines` of the file. Since we're going in reverse, these start and end lines never need to get updated while iterating.
```neovim {"update":true,"path":"_/code/17242693352782.ts","timestamp":1725473856422}
<pre>
<span class="-keyword">let</span> <span class="-variable">i</span> <span class="Operator">=</span> <span class="-number">0</span><span class="Delimiter">;</span>
<span class="-keyword">for</span> <span class="Delimiter">(</span><span class="-keyword">const</span> <span class="-variable">s</span> <span class="-keyword">of</span> <span class="-variable">toTransform</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="-keyword">const</span> <span class="-variable">first_line</span> <span class="Operator">=</span> <span class="-variable">lines</span><span class="Delimiter">[</span><span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">position</span><span class="Delimiter">.</span><span class="-variable">start</span><span class="Delimiter">.</span><span class="-variable">line</span><span class="Delimiter">]</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">extension</span> <span class="Operator">=</span> <span class="-variable"><span class="-function">filetypeToExtension</span></span><span class="Delimiter">(</span>
<span class="-variable">first_line</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">slice</span></span><span class="Delimiter">(</span><span class="-variable">first_line</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">indexOf</span></span><span class="Delimiter">(</span><span class="String">"```"</span><span class="Delimiter">)</span> <span class="Operator">+</span> <span class="-number">3</span><span class="Delimiter">)</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">trim</span></span><span class="Delimiter">(</span><span class="Delimiter">)</span><span class="Delimiter">,</span>
<span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">blockContents</span> <span class="Operator">=</span> <span class="-variable">lines</span>
<span class="Delimiter">.</span><span class="-variable"><span class="-function">slice</span></span><span class="Delimiter">(</span><span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">position</span><span class="Delimiter">.</span><span class="-variable">start</span><span class="Delimiter">.</span><span class="-variable">line</span> <span class="Operator">+</span> <span class="-number">1</span><span class="Delimiter">,</span> <span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">position</span><span class="Delimiter">.</span><span class="-variable">end</span><span class="Delimiter">.</span><span class="-variable">line</span><span class="Delimiter">)</span>
<span class="Delimiter">.</span><span class="-variable"><span class="-function">join</span></span><span class="Delimiter">(</span><span class="String">"<span class="SpecialChar">\n</span>"</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">now</span> <span class="Operator">=</span> <span class="String">`<span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable"><span class="-type"><span class="-type-builtin">Date</span></span></span><span class="Delimiter">.</span><span class="-variable"><span class="-function">now</span></span><span class="Delimiter">(</span><span class="Delimiter">)</span><span class="Delimiter"><span class="Special">}</span></span></span></span>{i}`</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">newFileName</span> <span class="Operator">=</span> <span class="String">`<span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable">now</span><span class="Delimiter"><span class="Special">}</span></span></span></span>.<span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable">extension</span><span class="Delimiter"><span class="Special">}</span></span></span></span>`</span><span class="Delimiter">;</span>
<span class="-keyword">await</span> <span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable">app</span><span class="Delimiter">.</span><span class="-variable">vault</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">create</span></span><span class="Delimiter">(</span><span class="String">`_/code/<span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable">newFileName</span><span class="Delimiter"><span class="Special">}</span></span></span></span>`</span><span class="Delimiter">,</span> <span class="-variable">blockContents</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-keyword">const</span> <span class="-variable">newLines</span> <span class="Operator">=</span> <span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">getNeovimBlockLines</span></span><span class="Delimiter">(</span><span class="-variable">now</span><span class="Delimiter">,</span> <span class="-variable">newFileName</span><span class="Delimiter">,</span> <span class="-variable">config</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="Comment"><span class="-spell">// Replace the lines</span></span>
<span class="-variable">lines</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">splice</span></span><span class="Delimiter">(</span>
<span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">position</span><span class="Delimiter">.</span><span class="-variable">start</span><span class="Delimiter">.</span><span class="-variable">line</span><span class="Delimiter">,</span>
<span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">position</span><span class="Delimiter">.</span><span class="-variable">end</span><span class="Delimiter">.</span><span class="-variable">line</span> <span class="Operator">-</span> <span class="-variable">s</span><span class="Delimiter">.</span><span class="-variable">position</span><span class="Delimiter">.</span><span class="-variable">start</span><span class="Delimiter">.</span><span class="-variable">line</span> <span class="Operator">+</span> <span class="-number">1</span><span class="Delimiter">,</span>
<span class="Operator">...</span><span class="-variable">newLines</span><span class="Delimiter">,</span>
<span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-variable">i</span> <span class="Operator">+=</span> <span class="-number">1</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
</pre>
```
^17242693352782
(here's the Neovim Block lines function if you are interested)
```neovim {"update":true,"path":"_/code/17242693352771.ts","timestamp":1725473856102}
<pre>
<span class="-variable"><span class="-function">getNeovimBlockLines</span></span><span class="Delimiter">(</span>
<span class="-variable">id</span><span class="Delimiter">:</span> <span class="-variable">string</span><span class="Delimiter">,</span>
<span class="-variable">file</span><span class="Delimiter">:</span> <span class="-variable">string</span><span class="Delimiter">,</span>
<span class="-variable">config</span><span class="Delimiter">:</span> <span class="-variable"><span class="-type">NeovimIncludeConfig</span></span><span class="Delimiter">,</span>
<span class="Delimiter">)</span><span class="Delimiter">:</span> string<span class="Delimiter">[</span><span class="Delimiter">]</span> <span class="Delimiter">{</span>
<span class="-variable">return</span> <span class="Delimiter">[</span>
<span class="String">`<span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable"><span class="-type"><span class="-constant">NEOVIM_START_FLAG</span></span></span><span class="Delimiter"><span class="Special">}</span></span></span></span> <span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable">file</span><span class="Delimiter"><span class="Special">}</span></span></span></span> <span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable"><span class="-type"><span class="-constant">JSON</span></span></span><span class="Delimiter">.</span><span class="-variable"><span class="-function">stringify</span></span><span class="Delimiter">(</span><span class="-variable">config</span><span class="Delimiter">)</span><span class="Delimiter"><span class="Special">}</span></span></span></span> %%`</span><span class="Delimiter">,</span>
<span class="String">"```neovim"</span><span class="Delimiter">,</span>
<span class="String">"```"</span><span class="Delimiter">,</span>
<span class="String">`^<span class="-none"><span class="-none"><span class="Special">${</span><span class="-variable">id</span><span class="Delimiter"><span class="Special">}</span></span></span></span>`</span><span class="Delimiter">,</span>
<span class="Delimiter">]</span><span class="Delimiter">;</span>
<span class="Delimiter">}</span>
</pre>
```
^17242693352771
## Implementation: Updating File
Lastly, we just replace the contents of the file with our updated lines and then call the function I had already written in the earlier blog post to find all of the `neovim` codeblocks and update them. This function opens Neovim, runs a Lua plugin that emits the HTML which displays the highlighting for the file and then exits. Once we have the highlights, we apply those to each codeblock and then save the file again.
```neovim {"update":true,"path":"_/code/17242693352760.ts","timestamp":1725473855792}
<pre>
<span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable">app</span><span class="Delimiter">.</span><span class="-variable">vault</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">modify</span></span><span class="Delimiter">(</span><span class="-variable">file</span><span class="Delimiter">,</span> <span class="-variable">lines</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">join</span></span><span class="Delimiter">(</span><span class="String">"<span class="SpecialChar">\n</span>"</span><span class="Delimiter">)</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
<span class="-keyword">await</span> <span class="-variable-builtin">this</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">updateNeovimHighlightBlocksCommand</span></span><span class="Delimiter">(</span><span class="Delimiter">)</span><span class="Delimiter">;</span>
</pre>
```
^17242693352760
This made it really easy to re-use all of the work that I had already done for actually updating the code blocks from existing files!
# Conclusion
Obsidian is really fun and cool :) And Obsidian Publish is great too :) This isn't an ad, but @kepano if you're reading this I would be happy to make it one! haha
Thanks everyone!