VSCode Markdown Basics: Customize Fenced Code Block Choices

Published 01 May 2024 · 7 min read
Customize the fenced code block snippet in VSCode to include additional languages for a more efficient markdown writing experience in engineering docs and blog posts.

VSCode is a great editor for authoring markdown documents. I use it frequently for writing engineering documentation and blog posts. VSCode comes bundled with some Markdown extensions that make authoring easier, such as including snippets to generate markdown syntax such as images, links, bold, etc. For technical writing, a commonly used feature is to generate a block of code that is syntax highlighted according to the language (Ruby, Python, JavaScript, etc.). This is provided through the "fenced code block" snippet. However, I ran into a limitation of this snippet where it didn't support all the languages I needed to include in my docs. This post will explain a solution to customizing the list of languages provided by the built-in Markdown extension.

Fenced Code Block Overview

Before getting into the solution, here's a quick overview of the fenced code block snippet that ships with VSCode. To see it in action, open or create a new markdown file such as "experiment.md". Then anywhere in the markdown file, type the letters fen and hit Ctrl + Space. The following intellisense menu will pop up:

vscode fenced code block built in

Notice that some of the options have help text beside them such as Insert fenced code block and Insert fenced math. These are snippets: reusable and customizable code fragments that can be quickly inserted into a file to facilitate code writing and save time on boilerplate.

If you go ahead and select the option for fenced codeblock, then it will insert a markdown fenced code block in your document. You'll see two lines of three backticks each, with an empty line between them, and the language python selected by default. A new intellisense menu is now displayed where you can select the language for the code block. This will control what syntax highlighting gets applied when the markdown is rendered. It looks like this:

vscode fenced code block languages

Now you can use the arrow keys to select a language, or just start typing for whichever language you want, for example, to select ruby, start typing r. This will narrow down the selection like this:

vscode fenced r ruby

When the language you want is highlighted, hit Enter to select it. This will result in a new fenced code block added to the markdown document, with your language selected, and the cursor inside the code block ready for you to type in code:

vscode fenced ruby example

Where is the Language List Defined?

The built-in fenced code snippet is great if you only need the list of languages provided by VSCode. At the time of this writing, these are: python, c, c++, c#, ruby, go, java, php, htm, css, javascript, json, markdown, and console.

However, I found myself needing more such as erb and yml. A search through all the VSCode settings didn't reveal any way to customize this list. You can of course hit esc when the intellisense list of languages pops up to close it, and then hit delete to remove the default python language selection, then type in your own. But I wanted a more efficient solution where all my frequently used languages would show up in the intellisense list for easy selection.

VSCode does support creating your own custom snippets, and this is the solution here. However, the key is to get the syntax correct so that the language list will pop up. In order to do this, we first need to find where the built-in snippet is located. Since it's part of the Markdown Basics extension that ships with VSCode, we can find the snippet definition here:

# This is the extension path on a Mac,
# It may be different for Windows:
cd ~/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/extensions

# Just take a look at it, do not edit it!
vim markdown-basics/snippets/markdown.code-snippets

The contents of the markdown.code-snippets file have been condensed into a single line so it's difficult to read, but this actually is a VSCode json file. In the version below, I've applied some formatting to make it easier to read. The entry we're interested in is Insert fenced code block:

{
  "Insert bold text": {...},
  "Insert italic text": {...},
  ...
  "Insert fenced code block": {
    "prefix":"fenced codeblock",
    "body":[
      "```${1|python,c,c++,c#,ruby,go,java,php,htm,css,javascript,json,markdown,console|}",
      "${TM_SELECTED_TEXT}$0",
      "```"
    ],
    "description":"Insert fenced code block"
  },
  ...
}

Now we can see where the list of languages comes from: It's defined as a comma separated list between pipe symbols |...| in the body entry of the fenced code block snippet. Here's an explanation of what the Insert fenced code block snippet does:

prefix: Specifies the trigger word or phrase that activates the snippet. In this case, typing "fenced codeblock" followed by Ctrl + Space will insert this code block.

body: Defines the content of the snippet, i.e. what will be rendered into the document when the snippet is actioned. It can contain any mix of literals and special symbols. The first line uses a special syntax with ${1|python,c,c++,c#,ruby,go,java,php,htm,css,javascript,json,markdown,console|}. This creates a dropdown menu with the listed languages, and the user can choose one. The selected language will replace the placeholder ${1}, which is the first tab stop.

$0 is the final tab stop, indicating where the cursor will be placed after the user has filled in the necessary information.

description: Provides a brief description of the snippet. In this case, it describes the purpose of the snippet, which is to insert a fenced code block with syntax highlighting for a chosen programming language.

Here's some visuals of this snippet in action, pointing out each part of it. Starting in any Markdown document, type in "fenced codeblock" (or just the first few characters of this phrase), and then hit Ctrl + space:

vscode fenced snippet explainer

vscode fenced snippet explainer detail

One final detail about this snippet - the body contains another special symbol after the language list: ${TM_SELECTED_TEXT}. This is a placeholder that represents the currently selected text in the editor. If there's any text selected when the snippet is triggered, it will be inserted at this position. This is useful when you've already written some code in your markdown document, and you want to highlight it and then insert the fenced codeblock snippet around the code you've already written.

Here is ${TM_SELECTED_TEXT} in action:

vscode fenced snippet code first

Use Cmd + Shift + P to bring up the Command Palette (or Ctrl for Windows). Type snippets, then select Snippets: Insert Snippet as shown below:

vscode fenced snippet command palette

Then start typing to find the fenced codeblock snippet, and hit Enter to action it:

vscode fenced snippet command palette fenced

Then the line you highlighted will be wrapped in the fenced code block, with the language list selection open:

vscode fenced wrapped

Now that we've found where the language list is defined, it may be tempting to simply edit the snippets file in the ~/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/extensions directory to add more languages here. But I don't recommend this solution. Why? Because the next time VSCode updates, if there are any changes to the Markdown Language Basics extension, your custom edits will be lost.

Customizing the Language List

A more durable solution is to create your own snippets file for Markdown content. This gets saved in your home directory, and gets backed up if you have Sync settings enabled, so it will survive application updates.

Start by opening the Command Palette with Cmd + Shift + P (or Ctrl on Windows). Search and select the option: Snippets: Configure User Snippets. If it's your first time creating custom snippets, select select markdown from the list of languages. If you've created markdown snippets before, then there will be an option for markdown.json, select that.

This will open a markdown.json file in VSCode, the path will be in your home directory, although the details could be different depending on Mac vs Windows installation. It will look something like this:

// ~/Library/Application Support/Code/User/snippets/markdown.json
{
	// Place your snippets for markdown here. Each snippet is defined under a snippet name and has a prefix, body and
	// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
	// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
	// same ids are connected.
	// Example:
	// "Print to console": {
	// 	"prefix": "log",
	// 	"body": [
	// 		"console.log('$1');",
	// 		"$2"
	// 	],
	// 	"description": "Log output to console"
	// }
}

Now we can add an entry in this file for the fenced code block snippet. We start with a copy of the original found earlier in the VSCode extensions directory, then make our customizations. Here is what I changed:

  1. Updated the prefix to fenc (for fenced custom but you can use whatever you like)
  2. Added erb,bash,yml,mermaid in the language list.
  3. Updated the description to indicate this is a custom block.

The resulting custom markdown snippets file looks like this:

// ~/Library/Application Support/Code/User/snippets/markdown.json
{
  "Custom Fenced Code Block": {
    "prefix": "fenc",
    "body": [
        "```${1|python,sql,c,c++,c#,ruby,go,java,php,htm,css,javascript,json,markdown,console,erb,bash,yml,mermaid|}",
        "${TM_SELECTED_TEXT}$0",
        "```"
    ],
    "description": "Insert custom fenced code block with languages"
  }
}

You could also change the order of the languages, or remove some entries. Save your changes.

Using Custom Snippet

Now that our custom snippet is in place, we can try it out in any markdown file. For example, to add a fenced code block for erb language, start typing fen, then Ctrl + space. This time you should see the "Custom Fenced Code Block" option show up in the intellisense. This is coming from the custom snippet file created earlier:

vscode fenced custom snippet

After selecting the custom option, you should see the new languages you added show up in the languages menu. For example, given that I added erb, I can just type e to see that option show up:

vscode fenced erb

Conclusion

Customizing the language list for fenced code blocks in VSCode's Markdown editor can enhance your efficiency and adapt to your specific documentation needs. While the built-in fenced code block snippet is a good starting point, its default language list might not cover all your requirements. By creating and configuring your own custom snippet, as explained in this post, you can integrate additional language options into the intellisense menu.