Implement a simple html syntax highlight using Nim

xflywind profile image flywind ・2 min read

We introduce how to implement simple html syntax highlight using Nim. We use highlight in stdlib to parse syntax. Then we use karax and htmlgen to generate Html.


import module

We need karax which constructs DSL to generate Html file and use htmlgen to generate Html strings.

First you should use command nimble install karax to install karax.

karax can be used as server side rendering and also single page application. To be simple, karax can be used to generate Html and Javascript.

import karax / [karaxdsl, vdom]
import packages/docutils/highlite
from htmlgen import span
from xmltree import escape

## code block
const code = """
import hello

  TreeObj* = object
    left*: ref TreeObj
    right*: ref TreeObj
    value*: char
    priority*: float
  Tree* = ref TreeObj

## escape `>=`
if a >= b:
  echo a + b

Generate HTML

karax supply text function accepting strings and verbatim function accepting raw Html strings.

We use link CSS to render keywords, comments, symbols, etc.

CSS looks like this.(highlight.css)

span.Keyword {
  color: blue;
  font-size: 18px;

span.Operator {
  color: purple;
  font-size: 18px;

span.Comment {
  color: green;
  font-size: 18px;

List Nim code as below, buildLang function uses stdlib highlight to parse syntax information and generates Html strings.

proc buildPre*(code: string): string =
  let vnode = buildHtml(tdiv):
    pre(class = "text"): text code
  result = $vnode

proc buildVerbatimPre*(code: string, lang: string = "lang-Nim"): string =
  let vnode = buildHtml(tdiv):
    link(rel="stylesheet", `type`="text/css", href = "highlight.css")
    pre(class = lang): verbatim code
  result = $vnode

proc buildLang*(code: string): string =
  var toknizr: GeneralTokenizer
  initGeneralTokenizer(toknizr, code)
  while true:
    getNextToken(toknizr, langNim)
    case toknizr.kind
    of gtEof: break 
    of gtNone, gtWhitespace:
      result.add substr(code, toknizr.start, toknizr.length +
      toknizr.start - 1)
      result.add span(class=tokenClassToStr[toknizr.kind], escape(substr(code, toknizr.start, toknizr.length +
          toknizr.start - 1)))

proc buildCode*(code: string, lang: string = "Nim"): string =
  if getSourceLanguage(lang) != langNim:
    return buildPre(code)

Let’s test our results,use openDefaultBrowser to preview.

import browsers
let file = "highlight.html"
let f = open(file, fmWrite)
f.write buildCode(code)


markdown guide
import browsers
const file = "highlight.html"
writeFile(file, buildCode(code))