CodeMirror's read-only experience, native to Go.
A code view and a diff view for Gio: syntax-highlighted code with a line gutter, wrapping or sideways scrolling, selection and copy, line decorations, widgets on and between lines, and diffs unified or side by side. Drawn as one image per visible row, so a full screen of code costs a handful of quads.
On the web, an app that shows code reaches for CodeMirror: highlighting, decorations, widgets between lines, a merge view. A native Go app has had nothing like it, and drawing code with a GUI toolkit's text stack is slow at full screen. godemirror brings that experience to Go, for viewing: file previews, diffs, code review, logs. It does not edit text.
godemirror lets you:
- Show code that reads like code. Monospace rows highlighted from the file
name and colored by a
HighlightStyle, as in CodeMirror. CSS, Go, HTML, Java, JavaScript, TypeScript, Python and Rust are parsed by a Go port of CodeMirror's own Lezer parser and grammars, and Kotlin and shell scripts by a port of the@codemirror/legacy-modesmodes CodeMirror reads them with, so they are tagged exactly as CodeMirror tags them; other languages are read by chroma and tagged with the CodeMirror tags their Lezer grammars give the same constructs.StyleTagsmake a Lezer grammar's tags finer, as CodeMirror'sstyleTagsdoes (RicherJavaTags,RicherGoTags,RicherJavaScriptTags,RicherPythonTags,RicherRustTags), andStyleRule.Namestags a name by its text, asGoBuiltinTagsand its siblings do for builtins. The themes:DarkTheme,LightTheme, a ported CodeMirror theme (OneDark,Dracula,GitHubLight,GitHubDark,Nord) or a theme of your own. Highlighting runs off the UI goroutine: a 13,000-line file shows at once and takes its colors when they are ready. - Wrap, or don't. Lines break after spaces the way CSS
pre-wrapdoes, or scroll sideways with Shift and the wheel. - Decorate lines. Tint a line and its gutter cell, put a strip down the gutter, rule a separator, hatch a filler row, and replace the line numbers with labels of your own: everything a diff needs.
- Put widgets on lines. Any Gio widget after a line's text or against the right edge (an Expand 44 unchanged lines button, a Revert), optionally shown only while the pointer is over the line's hover group.
- Open space between lines. A
BlockWidgetlays out any Gio widget under a line: a review thread, a comment box with an Editor. Its state is yours, so rebuilding the content around it never loses what was being typed. - Split side by side. Two documents row for row, scrolling down together and sideways each on its own. Unlike two CodeMirror editors, the split can wrap: a pair of lines takes the taller side's rows, and a block on one side leaves the same room on the other.
- Show a diff.
DiffViewdiffs two documents itself or shows a file of agit diffpatch: unified with old and new line numbers, or side by side, unchanged runs collapsed behind an expander, a Revert on each chunk, and review threads anchored to file lines. Its colors are part of the theme. - Search.
SetSearchhighlights every match of a literal or a regexp, case-sensitive or not, whole words or not;FindNextandFindPreviouswalk the matches, wrapping, selecting each one and scrolling it into view. The search box is yours to draw. In aDiffViewone query looks through the unified view and both sides of the split. - Select and copy. Drag (past an edge the view scrolls along), double click
for a word, triple click for a line, Shift+click to extend, Ctrl+A and
Ctrl+C. Copying gives back the text as it was, tabs included, and
SelectedLinesturns the selection into the line span an action works on.
godemirror needs Go 1.27 or later.
go get github.com/lichdotdev/godemirrorview, err := godemirror.New("JetBrainsMono Nerd Font", unit.Sp(13))
if err != nil {
return err
}
view.SetContent(godemirror.Content{Filename: "main.go", Text: source})
// in your frame loop
if _, err := view.Layout(gtx); err != nil {
return err
}Content also carries Labels, Decorations, Widgets and Blocks, indexed
by document line. SetContent shows a new document from its top;
UpdateContent swaps in a new version of the same one (a diff with lines pulled
in) without moving the scroll. For two sides, build a godemirror.Split and
give Left and Right their own content.
The deliberate limits, and what they mean for a caller, are in
docs/ceilings.md.
| CodeMirror 6 | godemirror |
|---|---|
EditorView with EditorState.readOnly |
View |
EditorView.lineWrapping |
View.Wrap |
EditorView.theme |
Theme |
tags (@lezer/highlight), HighlightStyle.define |
package tags, NewHighlightStyle |
lineNumbers({formatNumber}) |
Content.Labels |
Decoration.line, gutterLineClass |
LineDecoration |
Decoration.mark |
LineDecoration.Spans |
Decoration.widget |
LineWidget (on a line), BlockWidget (between lines) |
state.selection, dispatch({selection}) |
Selection, Select, SelectLines |
SearchQuery, findNext, findPrevious |
SearchQuery, SetSearch, FindNext, FindPrevious, Matches (no panel: draw your own search box) |
EditorView.scrollIntoView |
ScrollToLine |
state.doc.lines, doc.line(n) |
LineCount, LineText |
MergeView, unifiedMergeView |
DiffView (Split on or off) |
collapseUnchanged |
DiffOptions.Collapse, or the gaps of a patch |
mergeControls |
DiffOptions.Revert, Chunks |
highlightChanges |
DiffOptions.HighlightChanges |
DiffView shows the difference between two versions of a file, the way
CodeMirror's merge view does: unified, with the old and the new line number
side by side in the gutter, or side by side, with each deletion paired with
the addition that replaced it.
diff, err := godemirror.NewDiffView("JetBrainsMono Nerd Font", unit.Sp(13), godemirror.DiffOptions{
Collapse: &godemirror.Collapse{Margin: 3, MinSize: 4},
HighlightChanges: true,
Revert: func(c godemirror.Chunk) { /* put c.Lines back in the file */ },
})
if err != nil {
return err
}
diff.SetDocs("main.go", original, modified)
// in your frame loop
if _, err := diff.Layout(gtx); err != nil {
return err
}SetDocs diffs two documents itself. To show what git already diffed, read the
patch with ParsePatch and pass a file to SetPatch; the unchanged lines git
left out are asked for through DiffOptions.Expand and laid in with FillGap,
in as many parts as your backend answers in. Chunks, SelectedRange and
SelectedChanges read the diff in file line numbers, and SetBlocks anchors
a review thread to a line of either file. The colors live in Theme.Diff, in
both DarkTheme and LightTheme.
cmd/godemirror-demo shows what the library draws, in a window or headless: a
file in a View, or a diff in a DiffView, with expandable gaps, Revert on
hover and comments anchored to lines as DiffBlocks.
go run ./cmd/godemirror-demo -file view.go # a file
go run ./cmd/godemirror-demo -old a.go -file b.go -changes # a diff of two files
git diff > /tmp/change.patch
go run ./cmd/godemirror-demo -diff /tmp/change.patch -new path/to/changed/file -splitIn the window: Alt+W toggles wrapping, Alt+S switches a diff between
unified and side by side, and Alt+N opens a comment under the selected
lines. Reverting a chunk of two files puts it back and shows the diff again.
-search text highlights its matches and selects the first, -light uses
LightTheme, and -bench N with -shot out.png runs it headless
and reports frame times.
- Go 1.27. The library is pure Go and builds with
CGO_ENABLED=0; the demo opens a window through Gio'sapppackage, which needs a C toolchain on Linux. - Gio's system libraries for your platform (on Linux: Wayland or X11,
libxkbcommon, EGL and Vulkan headers), as listed in Gio's install guide. - fontconfig (
fc-match,fc-list) to resolve font families and fall back for missing characters. This is what keeps godemirror Linux-first today. Without it,Newfails withErrNoFontconfig;NewFromFontstakes the font files' contents instead and draws a character they lack as a box.
go build ./...
go test ./...CONTRIBUTING.md has the gate a change goes through, how to run the demo headless, and how to report a bug or propose a change.
MIT © 2026 omartelo
internal/lezer ports Lezer and its CSS, Go,
HTML, Java, JavaScript, Python and Rust grammars, MIT licensed © 2018 Marijn Haverbeke and others: see
its license. internal/stream ports
@codemirror/language's stream parser and the Kotlin and shell modes of
@codemirror/legacy-modes, MIT
licensed © 2018-2021 Marijn Haverbeke and others: see
its license.