DEV Community

SATO, Yoshiyuki
SATO, Yoshiyuki

Posted on • Edited on

I made a VSCode extension: "markdown-table-rainbow"

Last month when I was writing Markdown, I thought it would be easier to understand if each column in the table had its own color, so I made a VSCode extension that colors the columns in the table.

Of course, I knew there were extensions to format Markdown tables, but I have a problem when:

  • If the table has very long items and formatting makes them too wide
  • When CKJ characters are used and formatting does not align well

As far as I know, I haven't found an extension with similar functionality.

This extension is published on the marketplace.

The source code is on GitHub.

GitHub logo yoshi389111 / vscode-md-table-rainbow

A VS Code extension that colorizes table columns in Markdown for clarity.

markdown-table-rainbow

An extension that colorizes table columns in Markdown for clarity.

Features

This extension colors by columns in markdown tables.

demo

Extension Settings

You can use it without any setting. However, you can also change:

  // Delay time in milliseconds before updating colors.
  // Smaller values will color faster, but will increase processing cost.
  "markdownTableRainbow.updateDelay": 100,
  // An array of color strings (hex, rgba, rgb) for each column.
  // You can specify any number of colors. used cyclically.
  "markdownTableRainbow.colors": [
    "rgba(79,236,236,0.1)",
    "rgba(255,255,63,0.1)",
    "rgba(127,255,127,0.1)",
    "rgba(255,127,255,0.1)"
  ],
  // Cursor column color strings (hex, rgba, rgb).
  "markdownTableRainbow.cursorColor": "rgba(100,50,180,0.8)",
Enter fullscreen mode Exit fullscreen mode



How to install

Download it from the marketplace or search for markdown-table-rainbow in VSCode Extensions and install it.

Features

This extension colors by columns in markdown tables.

Also, when the cursor is on a table column, all columns at the same position as the corresponding column are colored.

demo

Extension Settings

You can use it without any setting. However, you can also change:

  // Delay time in milliseconds before updating colors.
  // Smaller values will color faster, but will increase processing cost.
  "markdownTableRainbow.updateDelay": 100,
  // An array of color strings (hex, rgba, rgb) for each column.
  // You can specify any number of colors. used cyclically.
  "markdownTableRainbow.colors": [
    "rgba(79,236,236,0.1)",
    "rgba(255,255,63,0.1)",
    "rgba(127,255,127,0.1)",
    "rgba(255,127,255,0.1)"
  ],
  // Cursor column color strings (hex, rgba, rgb).
  "markdownTableRainbow.cursorColor": "rgba(100,50,180,0.8)",
Enter fullscreen mode Exit fullscreen mode

markdownTableRainbow.updateDelay

Specifies the delay time in milliseconds before updating. When not specified, it operates as 100ms.

Smaller values will color faster, but will increase processing cost.

markdownTableRainbow.colors

Specifies a list (array) of background colors for each column in the table.
If not specified, the default color will be used.
Cycles through the specified list if the number of columns in the table is greater than the array length.

The colors that can be specified are as follows.

  • #RRGGBB
  • rgb(R, G, B)
  • rgba(R, G, B, A)

markdownTableRainbow.cursorColor

Specifies the color of the column at the same position as the cursor position.

The colors that can be specified are the same as markdownTableRainbow.colors.

Sites used as reference for creation

Miscellaneous Notes

Parsing Markdown

When I first came up with this extension, I thought I would have to use a library to parse the structure of Markdown.
However, when I checked some sources of extension functions for manipulating Markdown tables, I couldn't find any that used such a library.
So I decided to use regular expressions for table-like expressions, i.e. rows containing | at the beginning of a line and multiple | on the same line.

Lines starting with > are also included to account for tables within quoted blocks.

So, for example, if there is a Markdown table description inside a code block, it will be misidentified as a table as well. This is the intended behavior.

range to color

This time, the range of columns to be colored is inclusive of the leading | and not including the trailing |.

At first, I thought it would be better not to include both, but I found an article that says that depending on the type of markdown, colspan-like things can be written like | here is a two-column join ||.
Therefore, I thought that it would be difficult to understand if the column color was not displayed at all, so I put a | on one side.

I could have included either before or after, but for now I included the previous |.

About resource dispose

In the official sample code mentioned above, we only create an instance of vscode.TextEditorDecorationType, which is a definition for decoration, and do not do anything particularly post-processing.

But if you look at the source of vscode.TextEditorDecorationType there is a dispose() method.
It may not be a problem if I don't call it, but it bothers me so I called dispose() at the end.

Just register it with the instance of vscode.ExtensionContext that is passed when activate() is called, and it will be processed when it is no longer needed.

rangeBehavior

By default, the decoration range of VSCode is a specification that if you add a character immediately before or after it, the added character will also be included in the decoration range.

Specifically, the rangeBehavior specified in vscode.TextEditorDecorationType is OpenOpen by default.

In this extension, | in front of the column is included in the decoration range, but when a character is added before that, it is strange that the added character will be colored in the column behind it, so marked Closed.

Since the trailing | is not included in the decoration range, it is often better to input immediately after the decoration range (that is, before |) in the column color, so immediately after I left it as Open (the exception is when entering a new |, but in that case, I decided that rewriting after the delay would not be a problem).

Now specifies ClosedOpen as result.

Loading timing of setting items

At first, like the official sample, I looked at the config when activate() was called and generated a definition for vscode.TextEditorDecorationType.

However, with this, even if the user rewrites settings.json, it will continue to work with the old definition unless activate() is called again. And since it is not called normally, it will not be reflected unless you restart.

Therefore, vscode.workspace.onDidChangeConfiguration() detects the configuration change and recreates the vscode.TextEditorDecorationType object again.

By the way, vscode.TextEditorDecorationType is registered in context.subscriptions in order to dispose() at the end of processing.
It may not be such a problem, but it is annoying to keep unnecessary resources all the time, and if you change the configuration many times, it will be registered many times, so when you recreate it, Added processing to remove the previous object from context.subscriptions.

I'm not sure if this method is the best.

Checking config array values

In the case of items defined from the setting screen, even if they are of string type, for example, they can be checked with regular expressions (regular expressions can be specified in the pattern of the property definition in package.json).

However, this time, the background color is defined as an array type, and it is necessary to have it listed directly in settings.json, so the format check cannot be applied as it is.

Therefore, I detected the configuration change with vscode.workspace.onDidChangeConfiguration() mentioned above and applied a format check when recreating the resource.

At that time, I am currently preparing a long regular expression and checking whether it matches.
Actually, I feel that it would be better to divide it into several short regular expressions or check it with logic instead of doing everything with regular expressions (for example, if the value of R/G/B is 0 to 255 is easier to do with logic than with a regular expression). This is because I thought that the regular expression could be used in common if I checked the format with the definition (pattern) of package.json and checked both with regular expressions.
In the future, when I increase other patterns, I just copy the same value for both.

Therefore, the JavaScript regular expression is defined like new RegExp("RE") instead of /RE/ (the other one defines it as a string in package.json, so use the same format wanted).

How to get config

I saw a source that acquired the config as follows.

const config = vscode.workspace.getConfiguration('myExtension');
const updateDelay: number = config['updateDelay'] || 500;
Enter fullscreen mode Exit fullscreen mode

This still works, but in this case the value returned by config[] is of the form any.

Looking at the API specifications, there is a method called get(), which seems to allow you to specify a generic type and specify a default value in the method. Of course, it is necessary to describe it with consistency between the definition and the code.

const config = vscode.workspace.getConfiguration('myExtension');
const updateDelay: number = config.get<number>('updateDelay', 500);
Enter fullscreen mode Exit fullscreen mode

Filtering documents of interest

At first, I misunderstood, and if I registered onLanguage:markdown in activationEvents of package.json, I thought that the event would be notified only in Markdown, but apparently it is activated when a Markdown file is opened. Subsequent events seem to be notified regardless of document type.

So before processing the target editor we need to check if the document is target like this:

  if (editor.document.languageId !== 'markdown') {
    return;
  }
Enter fullscreen mode Exit fullscreen mode

I thought this would be fine, but I thought it would be troublesome to have to modify both package.json and this logic if I wanted to process files other than markdown in the future.

It seems that context.extension.packageJSON can get the contents of the extension's package.json.
(Although he seems to be able to get other extensions with vscode.extensions.getExtension(). The key seems to be specified like "<publisher name>.<extension name>")

So I tried the following. The current situation is over spec.

    const onLanguages = (context.extension.packageJSON["activationEvents"] as string[])
        .filter(it => it.startsWith("onLanguage:"))
        .map(it => it.substring("onLanguage:".length));

    // ...

        if (!onLanguages.includes(editor.document.languageId)) {
            return;
        }
Enter fullscreen mode Exit fullscreen mode

By the way, are there any languages other than markdown that should be supported?

Minimum version of VSCode

There is a place to describe the minimum version of VSCode that works in engines of package.json.

    "engines": {
        "vscode": "^1.76.0"
    },
Enter fullscreen mode Exit fullscreen mode

This extension does not use any new features, so it should work even on very old versions.
However, I don't know how to test with older versions, so I specified the latest version at the time.

Is it usually the one that specifies the latest version?

Japanese version of this document

Top comments (4)

Collapse
 
cbid2 profile image
Christine Belzie

Great post @yoshi389111! :) Most of the Markdown VSCode extensions have mostly focused on generating ReadMe files, so it’s nice to see one that does something different! :)

Collapse
 
rahulbaruah profile image
Rahul Baruah

Nice one👍

Collapse
 
maheshmuttinti profile image
Mahesh Muttinti

Cool extension, installed right now.

Collapse
 
snowyang profile image
Snow Yáng

Really cool man.