Bayan Bennett

Styling CodeMirror v6 with Material UI | DevLog 005

TypeScript
CodeMirror
Material UI
Video version of this post

In the last post I wrapped CodeMirror v6 with a react component. However, it still looked like a plain <textarea> when it should be looking like a code editor.

CodeMirror's base setup has a grocery store's worth of extensions to get started with full-fledged code editor. I have a much humbler objective, so I picked out the two extensions that I thought would be applicable to my use case:

  • @codemirror/highlight
  • @codemirror/matchbrackets

CodeMirror on its own is oblivious to code syntax and requires a set of rules to label segments of code. Fortunately, the @codemirror/lang-javascript extension contains the rules for tagging JavaScript/JSX and TypeScript/TSX.

npm i \
@codemirror/highlight \
@codemirror/matchbrackets \
@codemirror/lang-javascript

I created a new file ~/components/editor/extensions.ts and import the relevant extensions. Most extensions will be the returned value of a function call. Some, like defaultHighlightStyle.fallback, will be values.

import { javascript } from "@codemirror/lang-javascript"
import { defaultHighlightStyle } from "@codemirror/highlight"
import { bracketMatching } from "@codemirror/matchbrackets"

export const extensions = [
  bracketMatching(),
  defaultHighlightStyle.fallback,
  javascript(),
]

The EditorState.create configuration object in ~/components/editor/index.ts can then be updated with the extensions.

import { extensions } from "./extensions";

/* ... */

const state = EditorState.create({
  doc: "console.log('hello there')",
  extensions,
});

/* ... */

Going back to the site, the once dull text box now resembles a code editor.

plain code editor

Linking to Material UI

Having a unified colour palette for the site and the code editor seems like a reasonable requirement. Material UI's theme has plenty of colours that I could pick from.

CodeMirror 6 has a straightforward way of modifying the highlight style.

import {tags, HighlightStyle} from "@codemirror/highlight"

const myHighlightStyle = HighlightStyle.define([
  {tag: tags.keyword, color: "#fc6"},
  {tag: tags.comment, color: "#f5d", fontStyle: "italic"}
])

In @codemirror/lang-javascript, I can see which tags are in use. With some regex magic I was able to rearrange the tags into a Map. As tedious as it is, I'll need to map a colour to each tag.

/* Note: In this case using a `Map` was not the best choice */

const tagColorMap = new Map<Tag, Omit<TagStyle, "tag">>([
  [tags.angleBracket, { color: theme.palette.primary.light }],
  /* ... */
])

In retrospect, I would choose a better way of organizing tagColorMap. Perhaps, putting the key as the style object and the value being an Array of tags that match that style.

I'll need to convert the tagColorMap to the array of TagStyle objects that HighlightStyle.define requires.

const specs = Array.from(tagColorMap.entries()).map(([tag, style]) => ({
  tag,
  ...style,
}))

export const highlightStyle = HighlightStyle.define(specs)

In ~/components/editor/extensions.ts I'll import highlightStyle and add it to the exported extensions.

import { highlightStyle } from "./highlight-style"

/* ... */

export const extensions = [
  highlightStyle,
  /* ... */
]
code editor with custom color

Line Wrapping and Gutter Numbering

I noticed that long lines were pushing the output box off the screen. To enable line wrapping, EditorView.linewrapping needs to be added to the extensions.

Originally, I thought gutter numbering wouldn't look good, but I changed my mind. All that's needed is the @codemirror/gutter library.

// ~/components/editor/extensions.ts

import { EditorView } from "@codemirror/view"
import { lineNumbers } from "@codemirror/gutter"

/* ... */

export const extensions = [
  EditorView.lineWrapping
  lineNumbers(),
  /* ... */
]

Turning on Dark Mode

Using EditorView.theme and setting the second object to { dark: true }, will enable compatibility with dark themes. Most noticeably, the gutter numbers and the cursor become lighter.

// ~/components/editor/extensions.ts

/* ... */

export const extensions = [
  EditorView.theme({}, { dark: true });
  /* ... */
]

After all the changes, this is what the code editor looks like:

code editor final form

TL; DR

Added custom styling to CodeMirror v6 using CodeMirror extensions and Material UI's theme object.

© 2022 Bayan Bennett