Code Block Reference
Code Block
The code block is a number of transformers built on top of Shiki. I needed some functionality not supported out of the box in either Shiki or Rehype Pretty (and Rehype has a lot of functionality I don’t want). So I made my own. Some important considerations are that
- Long lines should wrap into multiple lines at a reasonable indent level. They should not interfere with the line numbering, at any browser width.
- There should be flexibility in highlighting and annotations that make discussing the code easier.
- The ability to cite sources is a must.
- It must be easy to start the code with any indentation level without having to make the actual code ugly due to mismatched indentations. Luckily, using Markdown processing makes this much easier. Picture this:
- Meta options should be easy to write and read.
- Functionality should be easy to extend.
Much of the advances here are in the CSS and elements that are created to make it work. For example, it would have been easy to create line numbers with CSS, as oft suggested on Stack Overflow.
However, this would have failed as soon as a line of code wraps around, or a message or diff requires the numbering to skip a line.
Nevertheless, the major features that were added are listed below.
meta: string
Out of the box, Shiki parses a raw meta (or info) string to pass in options.
To make parsing easier, I changed the meta data to be delimited by semi-colons. Spacing can be added or omitted around any of the terms and they will be stripped.
However, this starts to get pretty unweldy. With more options, it would be desirable to move these into their own lines, into a meta data section. We’d need to determine the fence to keep this meta data separate from the code. That’s what meta does, its assigned value being the fence. For example,
start-line: integer = 1
Line numbers are automatic. start-line specifies the line number to show for the first line of code. Every line thereafter is incremented.
highlight:
\[...startLine[-endLine[:startIndex[-endIndex[#id]]]]]\]
Both line and range highlighting are allowed. Ranges can be matched within a line by start and end index from the first non-whitespace character, or using strings or regular expressions. The number of matches in a line can be specfied. If only one index is specified, then the rest of the line is highlighted from that character. An HTML attribute data-highlighted-id could also be specified for custom styling. For example,
Second line highlighted:
Both lines highlighted:
“message” in the second and fourth lines highlighted. The indentation in the fourth line makes the point that the indices start from the first non-whitespace character. Notice that the ranges are the same despite the indent.
“message” in both lines highlighted and assigned the attribute data-highlighted-id = "message":
data-highlighted-id can be used in unique circumstances, but they can also used for more common use cases. One example is when we want to point out a problem with the code.
We can also select the range with a string to match:
We can match both “message” and “world” in the first line using a regular expression:
Finally, we can limit the range of matches.
title: string
Give a title or file name to the code block. Code language is automatically pulled from the meta info string and displayed on the upper right. Title is displayed on the upper left.
dir-level-fade: integer
Long directories in the code block title, such as /src/pages/blog/tag/[tag]/[page].astro could get tiresome to read. It would be helpful to put focus on the directories closer to the leaf nodes with more variation, since it is likely that most files might tend to exist under, say, /src/ or /src/pages/. When a “level” is specified, the higher level directories (on the left) are greyed out. For example,
On the other hand, when dir-level-fade isn’t specified and there is a domain or name before the first slash, then that name is automatically bolded. For example,
directory-separator: string = ’/’
The default directory separator on the title is a forward slash (/); however, this can be changed. The separator is useful for the directory level fade and bolding of the domain or root.
tab-size: integer = 4
Whitespaces in the code are automatically converted to symbols representing spaces or tabs, for easier reading in my opinion. tab-size specifies the number of spaces that a tab takes up.
flexible-indents: boolean = true
One common problem is that deeply nested code have narrow real estate on small screens like mobile phones. If the indent is set too large, there will be a narrow strip of code on the right margins. Oppositely, too small indents are hard to tell apart on larger screens. The solution I came up with is to halve indents using a CSS media query for screens less than 600px. This setting is true by default, and can be turned off. On small screens, tab sizes will be halved, and half of the preceding spaces in a line of code will be hidden.
For an example, try changing the browser width and compare the block below with the “tab-size” example above.
Citations
While not part of the code block, CSS can be used to attribute a source to it, making it easier to copy code examples.
We can use styles like this.
Diff
Diff transformers are inspired from Shiki transformers. Adding [!code ++] and [!code --] in comments at the end of a line gives the line some additional classes, which are used to highlight them and prefix them with ”++” and ”—” respectively, specifying them as changes from an original code. The remove diff lines do not count toward the line number.
Annotation / Log / Warning / Error Line Messages
Lines beginning with a comment and [!code (level)], then a message, where (level) is either annotation | log | warning | error, are highlighted with an appropriate icon on the left. This is inspired from TwoSlash. To be clear,
The other messages look like
Skip To Lines
Lines beginning with a comment and [!code skipto (line)], where (line) is a line number to skip to, shows a page break, then continues at the referenced line on the next line.
Skip Line Numbering
This is used throughout this document for pedagogical use, to make the examples clearer. For example, when I say I want to highlight the second line, I mean it!
add-classes: string
In some cases, it might be desirable to create a code-block with one (or two) off styling. This can be done by adding a unique class on the outer <figure> tag.
Inline Code
unist-util-visit package can take in an HTML tree, convert it to hast code and iterate through it. This can be used to make a Rehype plugin, since we can’t directly access inline code from Markdown content in frameworks like Astro. Plugins can be something like
This detects inline code with a particular suffix pattern to be picked up for syntax highlighting, such as console.log("Hello world!");.