andrewbranch / gatsby-remark-vscode

Gatsby plugin to provide VS Code’s syntax highlighting to Markdown code fences

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feature request: Inline code blocks

leafac opened this issue · comments

Something like what gatsby-remark-prismjs has.

(By the way, thanks for this project. It’s great!)

Feature parity with and ease of migration from gatsby-remark-prismjs is an unofficial goal, so this seems like a reasonable feature to add. I’ll admit I don’t love their API for it, but for ease of migration I guess we should match it.

Feature parity with gatsby-remark-prismjs would be awesome. From an author’s perspective, I’d love to write Markdown that isn’t too tied to a single vendor. In fact, being able to write in CommonMark instead of Kramdown is one of the reasons why I’m considering moving from Jekyll to Gatsby.

That said, gatsby-remark-vscode can always provide extra goodies, and who knows, maybe gatsby-remark-prismjs will incorporate them too. What API do you have in mind for inline code blocks?

I’d love to write Markdown that isn’t too tied to a single vendor

This is exactly why I don’t like gatsby-remark-prismjs’s approach. If you write some Markdown like

Blah blah `js•console.log('whatever')`

then every other Markdown renderer is going to render it as “Blah blah js•console.log('whatever')”, which looks wrong and distracting. In my own blog, I have some custom Remark plugins that get triggered by HTML comments, which I like because they’re invisible to any Markdown renderer. If I were designing syntax highlighting for inline code spans from a fresh slate, I would probably use a comment:

Blah blah <!--@ lang: js -->`console.log('whatever')`

And here’s how GitHub renders that exact Markdown in this comment:

Blah blah console.log('whatever')

So of course it doesn’t do the syntax highlighting, but at least the content isn’t broken.

Looks good to me. How about just:

Blah blah <!-- js -->`console.log('whatever')`

You could make it work with that, but I’ve sometimes thought about trying to make a convention out of YAML-containing comments, starting with a special sequence (like <!--@), being interpreted as metadata for the following node. For example, I forked gatsby-remark-images to be able to set custom options per-image:

<!--@@
  maxWidth: 307
  linkImagesToOriginal: false
  backgroundColor: transparent
  wrapperClassName: light-only
  tracedSVG: true
-->

![ ](./images/rabbit-light.png)

https://github.com/andrewbranch/blog/blame/master/posts/2019-08-05_Writing_Type_Safe_Polymorphic_React_Components_(Without_Crashing_TypeScript).md#L121-L128

So I’m somewhat interested in setting a precedent that can be reliably disambiguated from non-metadata comments and is sufficiently flexible to use in other remark plugins.

But, if I’m going to copy gatsby-remark-prismjs’s way of doing this, I may not do a comment trigger at all.

I really want this to happen and it seems like it’s been in the plans for a while. Can you help me get started? I’m familiar with Gatsby, but this project is more complex than anything I’ve ever done with it…

I'm interested in this too. This VS plugin is beautiful, and adding inline code blocks would really complete it in my opinion.

Thanks for the interest! The first step is to commit to an API. I would like to leave room in the future for customization, but I also want migration from gatsby-remark-prismjs to be simple. I think the ideas I’ve thrown out earlier in this thread are a little overambitious, and would cause unnecessary friction for PrismJS users. That said, I think it’s important to be able to control the theme for inline code independently of the theme for code blocks. (A lot of blogs use black text on a white background in the body, but a dark theme for code blocks. Rendering inline code with a dark background would look really heavy.)

API

So, I propose an options API like this:

// gatsby-config.js
{
  // ...
  plugins: [{
    resolve: `gatsby-transformer-remark`,
    options: {
      plugins: [{
        resolve: `gatsby-remark-vscode`,
        options: {
          inlineCode: {
            theme: 'Light+ (default light)', // same type as root `theme` option
            marker: '•', // just like gatsby-remark-prismjs
            className: 'grvsc-inline-code'

And the highlighting can be triggered from markdown the same way that gatsby-remark-prismjs does:

When calling `ts•Array.prototype.reduce`, blah blah whatever

Notes about those options:

  • If inlineCode is not supplied, inline code is not processed by the plugin.
  • inlineCode.theme defaults to the root theme option.
  • inlineCode.marker is required—an error should be thrown if it is not provided.
  • inlineCode.theme can be a string, a ThemeSettings object, or a function that returns either of those things. The function should receive a callback parameter with information about the inline code node.
  • inlineCode.className defaults to 'grvsc-inline-code'. It should accept a callback function too, with the same parameter type as inlineCode.theme.

Here are the typings changes represented by this API:

interface InlineCodeData {
  language?: string;
  markdownNode: MarkdownNode;
  inlineCodeNode: MDASTNode;
}

type ThemeOption<T extends object> = string | ThemeSettings | ((data: T) => string | ThemeSettings);

interface PluginOptions {
  theme?: ThemeOption<CodeFenceData>;
  // ...
  inlineCode?: {
    theme?: ThemeOption<InlineCodeData>;
    marker: string;
    className?: string | ((data: InlineCodeData) => string | undefined);
  }
}

Implementation

Not going to sugar coat it; this is fairly involved. But, I’m happy to help along the way, or if you get stuck and drop it, I’ll pick up where you leave off, or no worries if you decide not to start right now. Even though there are a lot of moving parts, I think as of v2, the existing architecture will support this feature fairly well.

Overview of how you’d implement this:

  1. Make the changes to types.d.ts I mentioned above.
  2. Introduce a new type into schema.graphql to represent inline code. It should look just like GRVSCCodeBlock, bet can replace preClassName and codeClassName with just className, omit meta, and replace tokenizedLines with tokens: [GRVSCToken!]!. (Run npm run build after this to update schema.d.ts.)
  3. Add validation for “if options.inlineCode exists, options.inlineCode.marker must exist in validateOptions.js.
  4. Update the AST visitor to look for both code and inlineCode nodes, if options.inlineCode is defined. (I think you can replace the string 'code' test with a callback function that accepts a node, where you can check its type. API)
  5. In the initial loop over code nodes, you’ll need to branch on node.type for some things. E.g., instead of calling parseCodeFenceHeader, you’ll need to call a new similar parsing function that separates the node text into language and text. If you don’t detect a language (i.e., you never see inlineCode.marker), I think you can just continue and be done with that node. The call to getPossibleThemes needs to use inlineCode.theme || theme if node.type === 'inlineCode'. Otherwise, I think most of that loop body stays the same. In the registerCodeBlock function, I think we should probably not run line transformers on inline code nodes.
  6. The next loop, registry.forEachCodeBlock, goes back over the nodes and generates HTML for them. The actual HTML generation is in getCodeBlockDataFromRegistry. Probably about half the stuff in here is actually specific to code blocks, and I think it might be too messy to make it polymorphic, so I would probably recommend making registry.forEachInlineCode and getInlineCodeDataFromRegistry functions instead, and repeating/extracting the relevant things from what’s there now. While generating HTML is the bulk of the work in this function, its full purpose is to generate a JavaScript object matching the new type you added to the GraphQL schema in step 2.
  7. Back in index.js in your [codeBlockRegistry.forEachInlineCode] loop, you can finish generating the GraphQL node in the same way as registry.forEachCodeBlock and push it onto the same graphQLNodes array.
  8. Write some integration tests. Try to cover the API surface.

Thanks Andrew. It's a shame to hear it's a sizeable piece of work. I won't be able to pick it up but I hope someone else does, you've left them a great overview to give it a go.

Got it started over at #89

I really appreciate the detailed guide and I’m following along on the sidelines as you implement it. Thanks for this project and for teaching me so much 😃

Amazing! Thank you very much!

👍 It’s on npm as v2.1.0. Usage is documented in the README.

If you want to, feel free to post a link to a future blog post that uses this feature—I’d be interested to see how it looks in real content. I can’t decide whether I think it will look really slick or a little distracting. Maybe a really subtle theme could look really nice.

I’m using syntax highlighting of inline code in my dissertation: https://github.com/leafac/yocto-cfa/blob/0d978a1428f0f03aff2e9ce08913dd640d93abf0/dissertation/yocto-cfa-latex.pdf

(Not with gatsby-remark-vscode, but with my package https://www.npmjs.com/package/shiki-latex.)

Thanks @andrewbranch, this is great! I've updated my site to use it, here's an example blog post. Including a screenshot in case this post disappears.

Inline code blocks

One of the things I loved about the main code blocks were the rounded edges. As inline blocks didn't have this by default I added them with some extra css, along with some padding and a larger font size.

.default-dark {
    padding: 2px 3px 2px 3px;
    border-radius: 4px;
    font-size: 0.97rem;
}

Currently this css is targeting all code blocks and not just inline code blocks. Is it possible to add an extra css tag to allow easy targeting of inline code blocks? (Or maybe I'm missing something and it's already possible?)

Also, why did you choose `js•? I had to manually parse through my article to rewrite the inline code, previously written as ```js. This is what other implementations (and your main code blocks) use, it would be great if inline aligned to this too.

I don't want to come across as pedantic though, this is great as is and I'm really happy with the new inline code blocks look. Thanks again!

One of the things I loved about the main code blocks were the rounded edges.

Yeah, my thinking was to minimize the opinionated styles on inline code spans, as people would likely already have some amount of styles on inline code (like monospace font), and that people would want to apply those styles both to highlighted code spans and to non-highlighted code spans.

Currently this css is targeting all code blocks and not just inline code blocks. Is it possible to add an extra css tag to allow easy targeting of inline code blocks?

  1. code.default-dark should do it for that theme
  2. :not(pre) > code should do it for all inline code
  3. You can use the inlineCode.className plugin option to add a custom class name.

I thought about putting a default class name of grvsc-inline-code or something on every inline code span it processes, but decided against it for some reason. That might have been a good idea.

Also, why did you choose `js•? I had to manually parse through my article to rewrite the inline code, previously written as ```js. This is what other implementations (and your main code blocks) use, it would be great if inline aligned to this too.

I don’t understand; I must be missing something. The CommonMark spec allows for an arbitrary info string after opening code fences (the 3+ backticks opening a FencedCodeBlock), which is where it’s standard to put a language. But the spec doesn’t support anything like that for code spans, so you have to invent some way of demarcating the text that should be interpreted as the language. I don’t know who was the first to do it, but gatsby-remark-prismjs decided the way they were going to do it was to let users specify some special character that would serve as a separator in a code span, and everything to the left of that character would be interpreted as the language, and everything to the right would be interpreted as the code span content. I mentioned in some early discussion in this issue that I don’t love that approach, but copied it nonetheless for ease of migration from gatsby-remark-prismjs. What implementations are you referring to?

:not(pre) > code does the job for the css, thanks!

Ah sorry, I misspoke. I can't remember whether I had a single of triple backticks, but my previous inline code didn't have a language specified, e.g., ```let x = 2``` instead of ```js•let x = 2```.

This was considered valid and was rendered with gatsby-remark-prismjs, but isn't rendered with your implementation and therefore I had to add the language. If no language is specified, it would be nice to assume javascript and render. It's not important, and arguably my inline code wasn't valid, just replying to clarify :)

If no language is specified, it would be nice to assume javascript and render

As someone whose livelihood is inextricably tied to JS, I can sympathize with the JS-centric perspective, but ultimately I think it’s important to be able to specify code spans that shouldn’t be syntax highlighted (consider filenames, for example—foo/bar/package.json in JavaScript would appear to be a property access expression nested within a binary expression within another binary expression). And the level of pain for anyone not usually writing JavaScript to migrate to such a system would be pretty high. I actually didn’t realize that PrismJS didn’t require the inlineCodeMarker, but doing without seems tricky. I think I still feel good about requiring it in this plugin.