How to colorize a tree output in Gatsby's Prism plugin

Written on

For a blog post I'm currently writing, I want to display a folder structure for a better overview. Of course I could use an image for this purpose, but I prefer to keep my content easy-to-read.

To display the output of my tree command I will use a code block in Markdown, which is then rendered by the gatsby-remark-prismjs plugin.

Let's see how the output of the command tree -F -a -L 2 demo/ will look like:

demo/
├── composer.json
├── Dockerfile
├── .env.sample
├── .gitignore
├── phpunit.xml -> tests/phpunit.xml
├── README.md
├── src/
│   └── Demo/
├── tests/
│   ├── integration/
│   ├── phpunit.xml
│   └── unit/
└── .travis.yml

Not so bad, but honestly not great either. And it would be even worse if I hadn't used the -F option, where the slashes are displayed to better distinction between files and folders.

There is a plugin for that

Yes, I know there is a Treeview plugin for Prism, but this is where the problem begins. There is simply no place in the gatsby-remark-prismjs configuration where you can define Prism1 plugins.

// In your gatsby-config.js
plugins: [
  {
    resolve: `gatsby-transformer-remark`,
    options: {
      plugins: [
        {
          resolve: `gatsby-remark-prismjs`,
          options: {
            // Class prefix for <pre> tags containing syntax highlighting;
            // defaults to 'language-' (e.g. <pre class="language-js">).
            // If your site loads Prism into the browser at runtime,
            // (e.g. for use with libraries like react-live),
            // you may use this to prevent Prism from re-processing syntax.
            // This is an uncommon use-case though;
            // If you're unsure, it's best to use the default value.
            classPrefix: "language-",
            // This is used to allow setting a language for inline code
            // (i.e. single backticks) by creating a separator.
            // This separator is a string and will do no white-space
            // stripping.
            // A suggested value for English speakers is the non-ascii
            // character '›'.
            inlineCodeMarker: null,
            // This lets you set up language aliases.  For example,
            // setting this to '{ sh: "bash" }' will let you use
            // the language "sh" which will highlight using the
            // bash highlighter.
            aliases: {},
            // This toggles the display of line numbers globally alongside the code.
            // To use it, add the following line in gatsby-browser.js
            // right after importing the prism color scheme:
            //  require("prismjs/plugins/line-numbers/prism-line-numbers.css")
            // Defaults to false.
            // If you wish to only show line numbers on certain code blocks,
            // leave false and use the {numberLines: true} syntax below
            showLineNumbers: false,
            // If setting this to true, the parser won't handle and highlight inline
            // code used in markdown i.e. single backtick code like `this`.
            noInlineHighlight: false,
            // This adds a new language definition to Prism or extend an already
            // existing language definition. More details on this option can be
            // found under the header "Add new language definition or extend an
            // existing language" below.
            languageExtensions: [
              {
                language: "superscript",
                extend: "javascript",
                definition: {
                  superscript_types: /(SuperType)/,
                },
                insertBefore: {
                  function: {
                    superscript_keywords: /(superif|superelse)/,
                  },
                },
              },
            ],
            // Customize the prompt used in shell output
            // Values below are default
            prompt: {
              user: "root",
              host: "localhost",
              global: false,
            },
            // By default the HTML entities <>&'" are escaped.
            // Add additional HTML escapes by providing a mapping
            // of HTML entities and their escape value IE: { '}': '&#123;' }
            escapeEntities: {},
          },
        },
      ],
    },
  },
]

Gatsby also provides an explanation in their implementation notes for it:

PrismJS plugins assume you're running things client side, but we are build-time folks. — Excerpt from the gatsby-remark-prismjs Readme on GitHub.

What now?

Of course, I could wait until the feature is implemented, especially since the topic is up for debate — most recently ~170 days ago.

On the other hand, I can use the LanguageExtensions option in the gatsby-remark-prismjs configuration to write my own implementation, which I did as you can see below:

demo/
├── composer.json
├── Dockerfile
├── .env.sample
├── .gitignore
├── phpunit.xml -> tests/phpunit.xml
├── README.md
├── src/
│   └── Demo/
├── tests/
│   ├── integration/
│   ├── phpunit.xml
│   └── unit/
└── .travis.yml

Isn't that much nicer and easier to read? It almost feels like viewing a terminal output.

How to implement your own extension

The configuration itself is quite simple, the tricky part are the regular expressions. I used most of the rules from the Treeview plugin you'll find on GitHub as a starting point, like the line and stroke rules.

// In your gatsby-config.js
plugins: [
  {
    resolve: `gatsby-transformer-remark`,
    options: {
      plugins: [
        {
          resolve: `gatsby-remark-prismjs`,
          options: {
            languageExtensions: [
              {
                language: "tree",
                extend: "json",
                definition: {
                  'entry-line': [
                    {
                      pattern: /\|-- |├── /,
                      alias: 'line-h'
                    },
                    {
                      pattern: /\|   |│   /,
                      alias: 'line-v'
                    },
                    {
                      pattern: /`-- |└── /,
                      alias: 'line-v-last'
                    },
                    {
                      pattern: / {4}/,
                      alias: 'line-v-gap'
                    }
                  ],
                  'entry-dir': {
                    pattern: /.*[\/](?!\w).*/,
                    inside: {
                      // symlink
                      'operator': / -> /,
                    }
                  },
                  'entry-symlink': {
                    pattern: /.*\S.* (-> .*)/,
                    inside: {
                      'operator': / -> /,
                      'file': /(.*)/,
                    }
                  },
                  'entry-name': {
                    pattern: /.*\S.*/,
                    inside: {
                      // symlink
                      'operator': / -> /,
                    }
                  },
                },
              },
            ]
          }
        },
      ],
    }
  }
]

All that remains to be done is to style the elements according to your own needs.

What's next?

Well, for now, I've' created a gist with the configuration above, so you can either use it for yourself or help me improve it if you like.

Disclaimer: I am by far not a professional when it comes to regular expressions or JavaScript. So if you find an error, feel free to correct it in the gist or leave me a comment here.