All in One Toolchain for Article Writing with Visual Studio Code

0
57

Visuals Studio Code, Markdown support

Epigraph:


Beware of advice — even this.


Carl Sandburg

Contents

Introduction

In my previous article on the topic, Helpful Toolchain for Article Writing, I expressed the annoyance of article writing and tried to explain the importance of better tooling. Some variants of the toolchain I created in response to the challenge greatly improved my writing performance and promoted some piece in mind, but were far from perfection. Even the use of Visual Studio 2015 as a writing tool presented some noticeable delays. Anyway, it worked out as a proof of the concept: this activity pays off with the first article written.

Recently, I tried out Visual Studio Code for the first time and immediately paid attention to the support of Markdown. Could it be used for article writing? My verdict was: “almost there”. All the available support and extension could not make a really usable toolchain, without considerable additional efforts, including some code development. Some critically important features were simply missing. But after paying this effort, I found results quite practical. My toolchain became very lightweight and convenient. Note that my idea of preparation of an article for CodeProject is the possibility to paste the article to the article submission wizard in one step, without any modifications. After this step, it should immediately work correctly.

This article is fully written with the use of the all in one toolchain based on Visual Studio Code.

Visual Studio Code

Why would I try out Visual Studio Code? I’m afraid to say, the benefits of this tool are somewhat understated. The way it is marketed creates the impression that this is a Visual Studio dramatically cut down in features for the sake of multi-platform qualities. This is not true. For example, it’s quite unfair to consider Eclipse as an “integrated development environment” and Visual Studio Code as “source code editor”.

Perhaps the most impressive quality of Visual Studio Code is its speed, especially in contrast to “real” Visual Studio. Another pleasing feature: it is really friendly to the developers used to Visual Studio.

Anyway, my initial interest started with attempts to find the best support of development and debugging of .NET Core code. But this is a whole different story…

Markdown Extension

There is a number of out-of-the-box Markdown-related Visual Studio Code Extensions on the Visual Studio Marketplace. As I could see, at the moment of writing they all had considerable deficiencies or defects. If they were perfect, this article would be totally redundant.

Perhaps the closest approximation to what we need would be the extension “Markdown All in One”. This extension is described here. This extension is based on the node.js module markdown-it. It provides syntax coloring, some support of IntelliSense, semi-automatic TOC generation and optional automatic update, reasonably good preview and custom CSS support.

However, the most important part is not done: generation of the HTML file. To best of my knowledge, none of the extensions available to the moment of writing does it in a proper way. This is the main part of the solution shown below.

Code Spellchecker Extension

This extension is described here. It provides reasonably convenient spell checking and quick type fix workflow.

Spelling problems are shown directly in the text, so they can be fixed through IntelliSense. In particular, a correct word can be added either to a global (per user) or local (per workspace) dictionary. The Markdown formatting does not break the detection of spelling problems. Quite importantly, there is a summary view of the problems, “Spell Checker Information”, which can be used to make sure no problems are overlooked, and to turn the feature on or off. Below, I’ll show how to add this and other commands to keyboard binding.

Customized Workspace Settings

All settings can be customized either globally (per user), or per workspace. We can use it to customize markdown preview and behavior (first of all, IntelliSense) to become suitable for the workspace used for article authoring. To do it per workspace, we need to create a file “.vscode/settings.json”:

{
 "markdown.styles": [
 "style.css" 
 ],
 "markdown.preview.fontFamily": "-apple-system, BlinkMacSystemFont,
 'Verdana', 'HelveticaNeue-Light',
 'Helvetica', 'Ubuntu', sans-serif",
 "markdown.preview.fontSize": 12,
 "markdown.preview.lineHeight": 1.6, 
 "markdown.extension.toc.updateOnSave": true,
 "markdown.enableExperimentalExtensionApi": false,
 
 
 
 "editor.wordBasedSuggestions": false,
 "[markdown]": { 
 "editor.codeLens": true,
 "editor.lineNumbers": "off",
 "editor.rulers": [
 79 
 ]
 },
 "git.enabled": false, 
 "cSpell.enabled": true 
}

The process of modification of settings is explained here. It makes certain sense to open the original file in Visual Studio Code and edit in text format, to keep full control and take the benefits of IntelliSense.

Some settings are copied to the local file “.vscode/settings.json” and not modified but added for convenience of experimenting; most of them are omitted from this code sample. Note two critical items: “editor.wordBasedSuggestions” and “editor.rulers”. First one is a work-around against showing all previously entered words as IntelliSense suggestions. The second one draws a vertical line at column 79, which is important for code samples: CodeProject requires all code sample lines to be within the 80-column range.

Keyboard Binding

When we set up specialized processing for one or another activity (article writing or not), it would be convenient to adjust keybindings to access frequently used commands faster. Unfortunately, to best of my knowledge, keybindings can only be modified in the global scope (per user). Perhaps, it makes certain sense. From the other hand, it’s possible to discriminate some key combination by “editor language id”, which will be perfectly fine for our purposes.

First thing here is to locate the file to be modified. The location depends on the platform; at the same time, it is recommended, for advanced customization, to open and edit the file in Visual Project Code text editor. From [Main Menu] -> Preferences -> Keyboard Shortcuts, locate the first paragraph: “For advanced customization, open and edit key keybindings.json”. Click on “keybindings.json” to open it. This is how it looks:

c:\Users<UserName>\AppData\Roaming\Code\User\keybindings.json

[

 {
 "key": "ctrl+f9",
 "command": "markdown.extension.toc.create",
 "when": "editorTextFocus && editorLangId == markdown"
 },

 {
 "key": "ctrl+shift+f9",
 "command": "markdown.extension.toc.update",
 "when": "editorTextFocus && editorLangId == markdown"
 },

 { 
 "key": "ctrl+shift+v",
 "command": "markdown.showPreview",
 "when": "editorFocus && editorLangId == markdown"
 },

 { 
 "key": "ctrl+k v",
 "command": "markdown.showPreviewToSide",
 "when": "editorFocus && editorLangId == markdown"
 },

 

]

For reference purposes, the sample copy of the keybindings file can be found in the source code sample (downloadable from the page of the present article) in “4Reference/keybindings.json”.

Markdown to HTML Conversion

Here, we are coming to the heart of the problem. We did not get acceptable out-of-the-box Visual Studio Code extensions. Some output the document as HTML, but do in not in the way we can easily use, for example, put the content in the system clipboard.

The best ideas are provided on this Visual Studio Code documentation page. The main idea is: the conversion to HTML should work as project build, with familiar Ctrl+Shift+B shortcut, show the result in the output window, and so on. For this purpose, proper build task should be defined in “.vscode/tasks.json”.

This is great, but does the recipe described on this page work? Actually, it doesn’t. There is a number of problems rendering the whole process virtually unusable. Some problems are minor and are easy to fix. First of all, the solution is based on separate installation of npm and markdown-it, unrelated to the installation performed by the extension “Markdown All in One”. Another little problem is hard-coded input/output file name, which is later solved with Gulp used to convert a whole set of Markdown files at once, which is also not exactly the best thing for article writing.

But the worst problem is different: the generated file lacks id attributes in headings. This raises the question: why would we automatically generate a TOC with the extension code, if it won’t work with the resulting HTML anyway?! Notably, the extension preview navigates with TOC correctly. This is done via another node.js module, “markdown-it-named-headers”, a plug-in for “markdown-it”. It cannot be used via the command-line “markdown-it” interface, as named headers are optional and not exposed through command-line arguments. Besides, they can be considered dangerous. Before explaining my solution, I need to give the reader a warning:

Warning:

Unfortunately, in automatic id and TOC generation, there is nothing to guarantee uniqueness of id values, as it is done in some processors (such as Pandoc, for example). Possible id clash, strictly speaking, would render HTML invalid. At least, the anchor-based navigation on the CodeProject article page can be broken. That said, it can happen if one creates two or more identical headers or explicitly enter non-unique id attributes in HTML.

Now, here is the idea: 1) the node.js modules supplied by the extension “Markdown All in One” can be reused, 2) the Markdown to HTML conversion should be done directly through “markdown-it” API, not the command-line interface of the script.

So, the solution is pretty simple. Instead of using a “markdown-it” command-line script, let’s write a separate script doing entire job using “markdown-it”. In the provided source code sample, it’s called “build.js”. This script takes three command-line arguments: a path to the module directory of the “Markdown All in One” extension and two file names, Markdown input and HTML output. The parameters are passed by the “tasks.json” settings:

{
 
 "version": "0.1.0",
 
 
 "command": "node",
 "args": [
 ".vscode/build.js",
 
 "c:/Users/SA/.vscode/extensions/yzhang.markdown-all-in-one-0.7.1/node_modules",
 "${fileDirname}/${fileBasenameNoExtension}.md",
 "${fileDirname}/${fileBasenameNoExtension}.html"
 ],
 "isShellCommand": true,
 "isBuildCommand": true,
 "echoCommand": false,
 "dummy": null
}

Note that the file naming schema assumes the input file is “*.md”. If this is not the case, the script will detect it as a file which does not exist and show an error. This schema also saves the output file in the same directory as input. One can invent any other layout, such as using a separate directory for output, or something like that.

And this is the complete “build.js” node.js script, also quite simple:

"use strict";

const path = require('path');


const moduleLocation = process.argv[2];
const input = path.normalize(process.argv[3]);
const output = path.normalize(process.argv[4]); 

const fs = require("fs");
const named = require(moduleLocation + '/markdown-it-named-headers');
const md = require(moduleLocation + '/markdown-it')()
 .set({ html: true, breaks: true, typographer: true })
 .use(named);

if (!fs.existsSync(input)) {
 console.error("Cannot convert .md file to HTML");
 console.error("Markdown file\n'" + input + "'\ndoes not exist");
 return;
} 

var result = md.render(fs.readFileSync(input, "utf8"));

var prefix = '...'; 
const suffix = '...'; 
const util = require('util');
prefix = util.format(prefix,
 util.format("Converted from: %s", path.basename(input)));
result = prefix + result + suffix;

fs.writeFileSync(output, result);

console.log(
 util.format("Markdown file \n%s\nis converted to:\n%s", input, output));

Another feature to implement is adding the HTML header and footer to the generated body. The “markdown-it” itself does not do it. There is one particular reason why it is important enough: UTF-8 encoding needs to be specified in the head element, to make rendering correct. CodeProject does it anyway, but it’s important to check up the document before pasting it to the CodeProject article submission wizard.

One noticeable annoyance of the extension preview is that the typographer feature of the node.js module “markdown-it” is not used. I wrote about similar feature in my previous article on the topic; check out the description of the “–smart” command-line option. In terms of the “markdown-it” module, this is the typographer option, which should be set before rendering. This is done in the conversion script “build.js”, so the input markup such as em dash (“‐‐‐”), apostrophes or unpaired quotation marks will be shown in a typographically correct way as “—”, “”, “’”, etc. This way, the extension preview and the final output will look different. Another reason for the difference is “fancy formatting” of the preview, not entirely controlled by CSS.

Putting All Together: Cookbook Recipe

  1. Install Visual Studio Code;
  2. Install Visual Studio Code extension “Markdown All in One”;
  3. Install node.js, latest LTS version; optionally, install just the latest production version;
  4. Install Visual Studio Code extension “Code Spellchecker”;
  5. Copy subdirectory “.vscode” (from source code downloadable from the page of the present article) with all files to some folder;
  6. Open the file “.vscode/tasks.js” and modify the second argument item in the value of the property “args” (see the comment “path to the node_modules directory”);
  7. Open the containing folder with Visual Studio Code; in the source code sample supplied with this article, this is “ArticleWorkspace”.

Optionally, I would also recommend installing Visual Studio Code extension “Numbered Bookmarks”; they are very useful for editing.

Everything is ready. Now you can test it. Create and open some .md file. In the editor, you will get the following commands:

  1. “Ctrl-space”: see a list of suggestions in Markdown syntax and chose a snippet to enter;
  2. “Ctrl+.”: if “Code Spellchecker” extension is activated, a problematic word is underlined and a cursor is located within this word, get a list of quick-fix suggestion and chose a corrected word;
  3. “Ctrl+F9”: create Table of Contents;
  4. “Ctrl+Shift+F9”: update Table of Contents;
  5. “Ctrl+Shift+V”: Open Preview;
  6. “Ctrl+K V”: Open Preview on side;
  7. “Ctrl+/”: comment out or un-comment current line or selection;
  8. “Ctrl+Shift+B”: build: convert .md file opened in a currently focused editor to HTML.

Some of these commands will be triggered by corresponding key combinations if keybindings are properly modified — compare with “keybindings.json”.

For full list of Markdown-related commands, press either F1 or Ctrl+Shift+P and enter “Markdown”; look for other commands as required.

Conclusions

In a way, all this work is just a pair of crutches. A decent approach would be writing a fully-fledged all-in-one Visual Studio Code extension, perhaps using some richer Wiki markup. But writing articles and wasting less time and nerve on formatting and integrity is more pressing necessity.

Happy writing!

LEAVE A REPLY