godemirror

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 47 Imported by: 0

README

godemirror

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.

Go Reference CI Go Gio Platform License

Why godemirror

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-modes modes 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. StyleTags make a Lezer grammar's tags finer, as CodeMirror's styleTags does (RicherJavaTags, RicherGoTags, RicherJavaScriptTags, RicherPythonTags, RicherRustTags), and StyleRule.Names tags a name by its text, as GoBuiltinTags and 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-wrap does, 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 BlockWidget lays 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. DiffView diffs two documents itself or shows a file of a git diff patch: 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. SetSearch highlights every match of a literal or a regexp, case-sensitive or not, whole words or not; FindNext and FindPrevious walk the matches, wrapping, selecting each one and scrolling it into view. The search box is yours to draw. In a DiffView one 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 SelectedLines turns the selection into the line span an action works on.

Getting started

godemirror needs Go 1.27 or later.

go get github.com/lichdotdev/godemirror
view, 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.

Coming from CodeMirror

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

The diff view

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.

The demo

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 -split

In 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.

Requirements

  • Go 1.27. The library is pure Go and builds with CGO_ENABLED=0; the demo opens a window through Gio's app package, 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, New fails with ErrNoFontconfig; NewFromFonts takes the font files' contents instead and draws a character they lack as a box.

Build from source

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.

License

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.

Documentation

Overview

Package godemirror is a read-only code view for Gio: a monospace document with a line-number gutter, wrapping or horizontal scrolling, drawn as one image per visible row so a full screen of code costs a handful of quads.

A View shows one document and a Split shows two side by side. Build them with New or NewSplit, which resolve a font family through fontconfig, or with NewFromFonts, which takes the font files themselves. Give them a Content and call Layout every frame.

A View or a Split belongs to the goroutine that lays it out: call its methods and set its fields there, between frames. Only highlighting runs on a goroutine of its own, and its result is taken in by a later Layout.

The view's deliberate limits, and the traps they set for a caller, are in docs/ceilings.md.

Index

Examples

Constants

This section is empty.

Variables

View Source
var DarkTheme = Theme{
	Background:       rgb(0x09090b),
	Border:           over(color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 26}, rgb(0x09090b)),
	Selection:        color.NRGBA{R: 0x3b, G: 0x82, B: 0xf6, A: 90},
	SearchMatch:      percent(amber400, 30),
	SearchCurrent:    percent(orange500, 60),
	GutterBackground: rgb(0x09090b),
	GutterForeground: rgb(0x9f9fa9),
	Foreground:       rgb(0xfafafa),
	Highlight: zincHighlight(zincColors{
		keyword: rgb(0xc396f5), str: rgb(0x82cb92), comment: rgb(0x9f9fa9), literal: rgb(0xeca760),
		typeName: rgb(0x71d0d5), property: rgb(0x82b1ed), definition: rgb(0x82b1ed),
		punctuation: rgb(0x9f9fa9), invalid: rgb(0xff6467),
	}),
	Diff: DiffColors{
		Added: ChangeColors{
			Background: percent(emerald400, 14), HoverBackground: percent(emerald500, 20),
			GutterBackground: percent(emerald500, 18), Strip: emerald500,
			Changed: percent(emerald400, 30),
		},
		Deleted: ChangeColors{
			Background: percent(red400, 12), HoverBackground: percent(red500, 17),
			GutterBackground: percent(red500, 15), Strip: red500,
			Changed: percent(red400, 30),
		},
		Separator:       percent(rgb(0xffffff), 10),
		Filler:          percent(rgb(0xffffff), 7),
		AffordanceHover: rgb(0x27272a),
	},
}

DarkTheme is the palette a new View starts with: zinc greys under an oklch syntax palette, converted to sRGB. A theme of your own starts as a copy of DarkTheme or LightTheme with the fields you want changed.

View Source
var Dracula = cmTheme(true, Theme{
	Background:       rgb(0x282a36),
	Foreground:       rgb(0xf8f8f2),
	Selection:        rgba(0xffffff1a),
	GutterBackground: rgb(0x282a36),
	GutterForeground: rgb(0x6d8a88),
	Highlight: NewHighlightStyle(
		TagStyle{Tags: []*tags.Tag{tags.Comment}, Color: rgb(0x6272a4)},
		TagStyle{Tags: []*tags.Tag{tags.String}, Color: rgb(0xf1fa8c)},
		TagStyle{Tags: []*tags.Tag{tags.Atom}, Color: rgb(0xbd93f9)},
		TagStyle{Tags: []*tags.Tag{tags.Meta}, Color: rgb(0xf8f8f2)},
		TagStyle{Tags: []*tags.Tag{tags.Keyword, tags.Operator, tags.TagName}, Color: rgb(0xff79c6)},
		TagStyle{Tags: []*tags.Tag{tags.Function(tags.PropertyName), tags.PropertyName}, Color: rgb(0x66d9ef)},
		TagStyle{Tags: []*tags.Tag{tags.Definition(tags.VariableName), tags.Function(tags.VariableName), tags.ClassName, tags.AttributeName}, Color: rgb(0x50fa7b)},
		TagStyle{Tags: []*tags.Tag{tags.Atom}, Color: rgb(0xbd93f9)},
	),
})

Dracula is @uiw/codemirror-theme-dracula 4.25.12.

View Source
var ErrNoFontconfig = glyphs.ErrNoFontconfig

ErrNoFontconfig is what New and NewSplit fail with on a machine without fontconfig's fc-match, where a family name cannot be resolved to font files. NewFromFonts needs no fontconfig.

View Source
var GitHubDark = cmTheme(true, Theme{
	Background:       rgb(0x0d1117),
	Foreground:       rgb(0xc9d1d9),
	Selection:        rgb(0x003d73),
	GutterBackground: rgb(0x333338),
	GutterForeground: rgb(0xcccccc),
	Highlight: NewHighlightStyle(
		TagStyle{Tags: []*tags.Tag{tags.Standard(tags.TagName), tags.TagName}, Color: rgb(0x7ee787)},
		TagStyle{Tags: []*tags.Tag{tags.Comment, tags.Bracket}, Color: rgb(0x8b949e)},
		TagStyle{Tags: []*tags.Tag{tags.ClassName, tags.PropertyName}, Color: rgb(0xd2a8ff)},
		TagStyle{Tags: []*tags.Tag{tags.VariableName, tags.AttributeName, tags.Number, tags.Operator}, Color: rgb(0x79c0ff)},
		TagStyle{Tags: []*tags.Tag{tags.Keyword, tags.TypeName, tags.TypeOperator}, Color: rgb(0xff7b72)},
		TagStyle{Tags: []*tags.Tag{tags.String, tags.Meta, tags.Regexp}, Color: rgb(0xa5d6ff)},
		TagStyle{Tags: []*tags.Tag{tags.Name, tags.Quote}, Color: rgb(0x7ee787)},
		TagStyle{Tags: []*tags.Tag{tags.Heading, tags.Strong}, Color: rgb(0xd2a8ff), Bold: true},
		TagStyle{Tags: []*tags.Tag{tags.Emphasis}, Color: rgb(0xd2a8ff), Italic: true},
		TagStyle{Tags: []*tags.Tag{tags.Deleted}, Color: rgb(0xffdcd7)},
		TagStyle{Tags: []*tags.Tag{tags.Atom, tags.Bool, tags.Special(tags.VariableName)}, Color: rgb(0xffab70)},
		TagStyle{Tags: []*tags.Tag{tags.Link}, Underline: true},
		TagStyle{Tags: []*tags.Tag{tags.Strikethrough}, Strikethrough: true},
		TagStyle{Tags: []*tags.Tag{tags.Invalid}, Color: rgb(0xf97583)},
	),
})

GitHubDark is githubDark from @uiw/codemirror-theme-github 4.25.12. It sets no gutter colors, so its gutter is @codemirror/view's dark one.

View Source
var GitHubLight = cmTheme(false, Theme{
	Background:       rgb(0xffffff),
	Foreground:       rgb(0x24292e),
	Selection:        rgb(0xbbdfff),
	GutterBackground: rgb(0xffffff),
	GutterForeground: rgb(0x6e7781),
	Highlight: NewHighlightStyle(
		TagStyle{Tags: []*tags.Tag{tags.Standard(tags.TagName), tags.TagName}, Color: rgb(0x116329)},
		TagStyle{Tags: []*tags.Tag{tags.Comment, tags.Bracket}, Color: rgb(0x6a737d)},
		TagStyle{Tags: []*tags.Tag{tags.ClassName, tags.PropertyName}, Color: rgb(0x6f42c1)},
		TagStyle{Tags: []*tags.Tag{tags.VariableName, tags.AttributeName, tags.Number, tags.Operator}, Color: rgb(0x005cc5)},
		TagStyle{Tags: []*tags.Tag{tags.Keyword, tags.TypeName, tags.TypeOperator}, Color: rgb(0xd73a49)},
		TagStyle{Tags: []*tags.Tag{tags.String, tags.Meta, tags.Regexp}, Color: rgb(0x032f62)},
		TagStyle{Tags: []*tags.Tag{tags.Name, tags.Quote}, Color: rgb(0x22863a)},
		TagStyle{Tags: []*tags.Tag{tags.Heading, tags.Strong}, Color: rgb(0x24292e), Bold: true},
		TagStyle{Tags: []*tags.Tag{tags.Emphasis}, Color: rgb(0x24292e), Italic: true},
		TagStyle{Tags: []*tags.Tag{tags.Deleted}, Color: rgb(0xb31d28)},
		TagStyle{Tags: []*tags.Tag{tags.Atom, tags.Bool, tags.Special(tags.VariableName)}, Color: rgb(0xe36209)},
		TagStyle{Tags: []*tags.Tag{tags.URL, tags.Escape, tags.Regexp, tags.Link}, Color: rgb(0x032f62)},
		TagStyle{Tags: []*tags.Tag{tags.Link}, Underline: true},
		TagStyle{Tags: []*tags.Tag{tags.Strikethrough}, Strikethrough: true},
		TagStyle{Tags: []*tags.Tag{tags.Invalid}, Color: rgb(0xcb2431)},
	),
})

GitHubLight is githubLight from @uiw/codemirror-theme-github 4.25.12. Its deleted text's background is not drawn, nor would CodeMirror draw it: the theme writes it without a #.

View Source
var LightTheme = Theme{
	Background:       rgb(0xebebed),
	Border:           rgb(0xdadadd),
	Selection:        color.NRGBA{R: 0x3b, G: 0x82, B: 0xf6, A: 90},
	SearchMatch:      percent(amber400, 55),
	SearchCurrent:    percent(orange500, 60),
	GutterBackground: rgb(0xebebed),
	GutterForeground: rgb(0x64636e),
	Foreground:       rgb(0x18181b),
	Highlight: zincHighlight(zincColors{
		keyword: rgb(0x6c329f), str: rgb(0x206b38), comment: rgb(0x64636e), literal: rgb(0x9d5300),
		typeName: rgb(0x006e74), property: rgb(0x17559b), definition: rgb(0x17559b),
		punctuation: rgb(0x64636e), invalid: rgb(0xe7000b),
	}),
	Diff: DiffColors{
		Added: ChangeColors{
			Background: percent(emerald500, 12), HoverBackground: percent(emerald500, 20),
			GutterBackground: percent(emerald500, 18), Strip: emerald500,
			Changed: percent(emerald500, 25),
		},
		Deleted: ChangeColors{
			Background: percent(red500, 10), HoverBackground: percent(red500, 17),
			GutterBackground: percent(red500, 15), Strip: red500,
			Changed: percent(red500, 22),
		},
		Separator:       rgb(0xdadadd),
		Filler:          percent(rgb(0x18181b), 7),
		AffordanceHover: rgb(0xdedee0),
	},
}

LightTheme is DarkTheme's light counterpart, from the same oklch palette. Its selection is DarkTheme's blue.

View Source
var Nord = cmTheme(true, Theme{
	Background:       rgb(0x2e3440),
	Foreground:       rgb(0xffffff),
	Selection:        rgba(0x00000073),
	GutterBackground: rgb(0x2e3440),
	GutterForeground: rgb(0x4c566a),
	Highlight: NewHighlightStyle(
		TagStyle{Tags: []*tags.Tag{tags.Keyword}, Color: rgb(0x5e81ac)},
		TagStyle{Tags: []*tags.Tag{tags.Name, tags.Deleted, tags.Character, tags.PropertyName, tags.MacroName}, Color: rgb(0x88c0d0)},
		TagStyle{Tags: []*tags.Tag{tags.VariableName}, Color: rgb(0x8fbcbb)},
		TagStyle{Tags: []*tags.Tag{tags.Function(tags.VariableName)}, Color: rgb(0x8fbcbb)},
		TagStyle{Tags: []*tags.Tag{tags.LabelName}, Color: rgb(0x81a1c1)},
		TagStyle{Tags: []*tags.Tag{tags.Color, tags.Constant(tags.Name), tags.Standard(tags.Name)}, Color: rgb(0x5e81ac)},
		TagStyle{Tags: []*tags.Tag{tags.Definition(tags.Name), tags.Separator}, Color: rgb(0xa3be8c)},
		TagStyle{Tags: []*tags.Tag{tags.Brace}, Color: rgb(0x8fbcbb)},
		TagStyle{Tags: []*tags.Tag{tags.Annotation}, Color: rgb(0xd30102)},
		TagStyle{Tags: []*tags.Tag{tags.Number, tags.Changed, tags.Annotation, tags.Modifier, tags.Self, tags.Namespace}, Color: rgb(0xb48ead)},
		TagStyle{Tags: []*tags.Tag{tags.TypeName, tags.ClassName}, Color: rgb(0xebcb8b)},
		TagStyle{Tags: []*tags.Tag{tags.Operator, tags.OperatorKeyword}, Color: rgb(0xa3be8c)},
		TagStyle{Tags: []*tags.Tag{tags.TagName}, Color: rgb(0xb48ead)},
		TagStyle{Tags: []*tags.Tag{tags.SquareBracket}, Color: rgb(0xbf616a)},
		TagStyle{Tags: []*tags.Tag{tags.AngleBracket}, Color: rgb(0xd08770)},
		TagStyle{Tags: []*tags.Tag{tags.AttributeName}, Color: rgb(0xebcb8b)},
		TagStyle{Tags: []*tags.Tag{tags.Regexp}, Color: rgb(0x5e81ac)},
		TagStyle{Tags: []*tags.Tag{tags.Quote}, Color: rgb(0xb48ead)},
		TagStyle{Tags: []*tags.Tag{tags.String}, Color: rgb(0xa3be8c)},
		TagStyle{Tags: []*tags.Tag{tags.Link}, Color: rgb(0xa3be8c), Underline: true},
		TagStyle{Tags: []*tags.Tag{tags.URL, tags.Escape, tags.Special(tags.String)}, Color: rgb(0x8fbcbb)},
		TagStyle{Tags: []*tags.Tag{tags.Meta}, Color: rgb(0x88c0d0)},
		TagStyle{Tags: []*tags.Tag{tags.Monospace}, Color: rgb(0xd8dee9), Italic: true},
		TagStyle{Tags: []*tags.Tag{tags.Comment}, Color: rgb(0x4c566a), Italic: true},
		TagStyle{Tags: []*tags.Tag{tags.Strong}, Color: rgb(0x5e81ac), Bold: true},
		TagStyle{Tags: []*tags.Tag{tags.Emphasis}, Color: rgb(0x5e81ac), Italic: true},
		TagStyle{Tags: []*tags.Tag{tags.Strikethrough}, Strikethrough: true},
		TagStyle{Tags: []*tags.Tag{tags.Heading}, Color: rgb(0x5e81ac), Bold: true},
		TagStyle{Tags: []*tags.Tag{tags.Special(tags.Heading1)}, Color: rgb(0x5e81ac), Bold: true},
		TagStyle{Tags: []*tags.Tag{tags.Heading1}, Color: rgb(0x5e81ac), Bold: true},
		TagStyle{Tags: []*tags.Tag{tags.Heading2, tags.Heading3, tags.Heading4}, Color: rgb(0x5e81ac), Bold: true},
		TagStyle{Tags: []*tags.Tag{tags.Heading5, tags.Heading6}, Color: rgb(0x5e81ac)},
		TagStyle{Tags: []*tags.Tag{tags.Atom, tags.Bool, tags.Special(tags.VariableName)}, Color: rgb(0xd08770)},
		TagStyle{Tags: []*tags.Tag{tags.ProcessingInstruction, tags.Inserted}, Color: rgb(0x8fbcbb)},
		TagStyle{Tags: []*tags.Tag{tags.ContentSeparator}, Color: rgb(0xebcb8b)},
		TagStyle{Tags: []*tags.Tag{tags.Invalid}, Color: rgb(0x434c5e)},
	),
})

Nord is @uiw/codemirror-theme-nord 4.25.12. The dotted border under invalid code is not drawn.

View Source
var OneDark = cmTheme(true, Theme{
	Background:       rgb(0x282c34),
	Foreground:       rgb(0xabb2bf),
	Selection:        rgb(0x3e4451),
	GutterBackground: rgb(0x282c34),
	GutterForeground: rgb(0x7d8799),
	SearchMatch:      rgba(0x72a1ff59),
	SearchCurrent:    rgba(0x6199ff2f),
	Highlight: func() *HighlightStyle {
		chalky, coral, cyan, ivory := rgb(0xe5c07b), rgb(0xe06c75), rgb(0x56b6c2), rgb(0xabb2bf)
		stone, malibu, sage, whiskey := rgb(0x7d8799), rgb(0x61afef), rgb(0x98c379), rgb(0xd19a66)
		violet, invalid := rgb(0xc678dd), rgb(0xffffff)
		return NewHighlightStyle(
			TagStyle{Tags: []*tags.Tag{tags.Keyword}, Color: violet},
			TagStyle{Tags: []*tags.Tag{tags.Name, tags.Deleted, tags.Character, tags.PropertyName, tags.MacroName}, Color: coral},
			TagStyle{Tags: []*tags.Tag{tags.Function(tags.VariableName), tags.LabelName}, Color: malibu},
			TagStyle{Tags: []*tags.Tag{tags.Color, tags.Constant(tags.Name), tags.Standard(tags.Name)}, Color: whiskey},
			TagStyle{Tags: []*tags.Tag{tags.Definition(tags.Name), tags.Separator}, Color: ivory},
			TagStyle{Tags: []*tags.Tag{tags.TypeName, tags.ClassName, tags.Number, tags.Changed, tags.Annotation, tags.Modifier, tags.Self, tags.Namespace}, Color: chalky},
			TagStyle{Tags: []*tags.Tag{tags.Operator, tags.OperatorKeyword, tags.URL, tags.Escape, tags.Regexp, tags.Link, tags.Special(tags.String)}, Color: cyan},
			TagStyle{Tags: []*tags.Tag{tags.Meta, tags.Comment}, Color: stone},
			TagStyle{Tags: []*tags.Tag{tags.Strong}, Bold: true},
			TagStyle{Tags: []*tags.Tag{tags.Emphasis}, Italic: true},
			TagStyle{Tags: []*tags.Tag{tags.Strikethrough}, Strikethrough: true},
			TagStyle{Tags: []*tags.Tag{tags.Link}, Color: stone, Underline: true},
			TagStyle{Tags: []*tags.Tag{tags.Heading}, Color: coral, Bold: true},
			TagStyle{Tags: []*tags.Tag{tags.Atom, tags.Bool, tags.Special(tags.VariableName)}, Color: whiskey},
			TagStyle{Tags: []*tags.Tag{tags.ProcessingInstruction, tags.String, tags.Inserted}, Color: sage},
			TagStyle{Tags: []*tags.Tag{tags.Invalid}, Color: invalid},
		)
	}(),
})

OneDark is @codemirror/theme-one-dark 6.1.3.

Functions

This section is empty.

Types

type Align

type Align int

Align is where ScrollToLine puts its line in the view.

const (
	// AlignNearest scrolls as little as brings the whole line into view, and
	// not at all when it is already in view.
	AlignNearest Align = iota
	// AlignCenter puts the line in the middle of the view.
	AlignCenter
	// AlignTop puts the line at the top of the view.
	AlignTop
)

type BlockWidget

type BlockWidget struct {
	// After is the document line (0-based) the block opens under.
	After int
	// Widget is laid out exactly once every frame, on screen or not, so the
	// document's height is known; its height is the one it lays itself out
	// at, and its width the code area's.
	Widget layout.Widget
}

BlockWidget is a Gio widget laid out between two lines, across the code area, as tall as it lays itself out: a review thread under the line it is about, or the box a comment is being typed into. It takes the pointer over it; the view does not select or hover there.

Its state is the caller's, held outside the view, so rebuilding the content around it (a gap pulled in, a thread resolved) never loses what is half typed inside.

type ChangeColors

type ChangeColors struct {
	Background, HoverBackground, GutterBackground, Strip, Changed color.NRGBA
}

ChangeColors are how an added or a deleted line is drawn: its Background, and HoverBackground while the pointer is over its chunk; its gutter cell's GutterBackground, and a Strip down that cell's left edge. Changed tints the words that changed within the line (DiffOptions.HighlightChanges), over its background.

type ChangedLine

type ChangedLine struct {
	Side Side
	// Line is 1-based.
	Line int
	Text string
}

ChangedLine is a deleted line (OldSide, numbered in the original file) or an added one (NewSide, numbered in the modified file), with its text.

type Chunk

type Chunk struct {
	OldStart, OldCount, NewStart, NewCount int
	// Lines are the chunk's lines in diff order: its deletions, then its
	// additions.
	Lines []ChangedLine
}

Chunk is a run of changed lines with no unchanged line between them. Each side's start is the 1-based number of its first line there; a side the chunk has no lines in (Count 0) starts at the line the chunk sits before.

type Collapse

type Collapse struct{ Margin, MinSize int }

Collapse leaves unchanged runs out of a diff of two documents, the way CodeMirror's collapseUnchanged does: Margin lines stay next to each change, and a run is left out only when that hides at least MinSize lines (and at least one).

type Content

type Content struct {
	// Filename picks the language Text is highlighted as, by its name or
	// extension; a name no lexer claims is shown plain.
	Filename string
	// Text is the document. Lines end in LF or CRLF, and a final line end
	// does not start an empty line.
	Text string
	// Labels replace the gutter's line numbers; nil numbers the lines from 1.
	Labels      []string
	Decorations []LineDecoration
	// Widgets are drawn on lines, and Blocks between them, in slice order.
	Widgets []LineWidget
	Blocks  []BlockWidget
	// StyleTags add rules to the Lezer grammars they name, for finer tags
	// than CodeMirror's own. A StyleTags naming no Lezer grammar, or a
	// rule whose path does not parse, makes Layout return an error.
	StyleTags []StyleTags
}

Content is everything a View shows of one document. Labels and Decorations are indexed by document line (0-based); a line past the end of either has no label or decoration.

type DiffBlock

type DiffBlock struct {
	At     LineRef
	Widget layout.Widget
}

DiffBlock is a BlockWidget anchored to a line of a file instead of a row of the layout: a review thread under the line it is about, wherever unified or side by side puts that line. Side by side, it opens on its line's side and leaves the same room on the other.

type DiffColors

type DiffColors struct {
	Added, Deleted ChangeColors
	// Separator rules the row standing for lines a diff leaves out, when it
	// offers nothing to expand them with.
	Separator color.NRGBA
	// Filler hatches the row that, side by side, stands for a line only the
	// other side has.
	Filler color.NRGBA
	// AffordanceHover is the background of the default expand and revert
	// affordances under the pointer. Their text takes GutterForeground.
	AffordanceHover color.NRGBA
}

DiffColors are the colors of a diff. Like a LineDecoration's, each may be translucent and is mixed over the theme's background.

type DiffOptions

type DiffOptions struct {
	// Collapse leaves unchanged runs out of a diff set with SetDocs; nil shows
	// every line. A patch set with SetPatch leaves out what git left out.
	Collapse *Collapse

	// Expand asks for the lines of a gap of a patch, which the DiffView does
	// not have; hand them back with FillGap. nil offers no way to expand a
	// patch's gaps. A diff set with SetDocs fills its gaps itself.
	Expand func(Gap)
	// Revert asks to put a chunk back the way the original had it. The
	// DiffView changes nothing itself: set the diff again once the files
	// have changed. nil offers no revert.
	Revert func(Chunk)
	// ExpandLabel is the text of the default expand affordance for a gap of
	// hidden lines; nil is "N unchanged lines".
	ExpandLabel func(hidden int) string
	// ExpandWidget and RevertWidget replace the default affordances: each
	// gets what it acts on and the function its click calls.
	ExpandWidget func(g Gap, expand func()) layout.Widget
	RevertWidget func(c Chunk, revert func()) layout.Widget

	// HighlightChanges tints the words that changed within a line, in each
	// deleted line and the added line it pairs with by position in its
	// chunk, with the theme's ChangeColors.Changed. Lines with no word in
	// common are left whole.
	HighlightChanges bool

	// StyleTags add rules to the Lezer grammars they name, on both sides,
	// as Content.StyleTags does for a View.
	StyleTags []StyleTags
}

DiffOptions are what a DiffView asks of its caller. Every field may be left zero.

type DiffRange

type DiffRange struct {
	OldFrom, OldTo, NewFrom, NewTo int
}

DiffRange is a span of lines in each file, 1-based and inclusive; a side with no line in the span has From and To 0.

type DiffView

type DiffView struct {
	// Split shows the diff side by side, the original on the left; otherwise
	// it is one column with the deletions above the additions.
	Split bool
	// Wrap breaks long lines instead of scrolling them sideways.
	Wrap bool
	// Nested is View.Nested: the diff sits in a parent that scrolls
	// vertically.
	Nested bool
	// contains filtered or unexported fields
}

DiffView shows the difference between two versions of a file, unified in one column or side by side, the way CodeMirror's merge view does: changed lines tinted in the theme's Diff colors, each run of them a chunk that lights up under the pointer. Build one with NewDiffView or NewDiffViewFromFonts, give it a diff with SetDocs or SetPatch, and call Layout every frame. Like a View, it belongs to the goroutine that lays it out.

func NewDiffView

func NewDiffView(family string, size unit.Sp, opts DiffOptions) (*DiffView, error)

NewDiffView builds a diff view drawing family (a fontconfig name) at size. It fails as New does.

func NewDiffViewFromFonts

func NewDiffViewFromFonts(fonts Fonts, size unit.Sp, opts DiffOptions) (*DiffView, error)

NewDiffViewFromFonts builds a diff view drawing fonts at size, with no fontconfig. It fails as NewFromFonts does.

func (*DiffView) Chunks

func (d *DiffView) Chunks() []Chunk

Chunks are the runs of changed lines of the diff set last, in order, with lines filled into a gap counted. Like everything set on a DiffView, they reach the screen once highlighted (see UpdateContent).

func (*DiffView) FillGap

func (d *DiffView) FillGap(g Gap, lines []string)

FillGap lays lines into g, the gap DiffOptions.Expand asked for, in place. They may come in parts: a later part is FillGap(g2, more), with g2 a copy of g whose From has moved past the lines filled so far. A part that does not start where the gap now starts (two expands of one gap in flight), or that answers a diff since replaced, is dropped; lines past the gap's end are ignored.

func (*DiffView) FindNext

func (d *DiffView) FindNext() (Match, bool)

FindNext is View.FindNext in the view on screen: the unified one, or, side by side, the side that holds the selection, or else the right one. A match lies in one side, so a search that wraps never leaves it.

func (*DiffView) FindPrevious

func (d *DiffView) FindPrevious() (Match, bool)

FindPrevious is View.FindPrevious in the view FindNext searches.

func (*DiffView) Layout

func (d *DiffView) Layout(gtx layout.Context) (layout.Dimensions, error)

Layout draws the diff into the whole of gtx.Constraints.Max, unified or side by side, and then runs what its affordances were clicked for: DiffOptions.Expand and Revert are called from here, after the frame is drawn. Its error means what View.Layout's does.

func (*DiffView) Matches

func (d *DiffView) Matches() []Match

Matches are View.Matches of the view FindNext searches: what a search box counts ("3 of 12"). Their lines are that view's rows, not file lines.

func (*DiffView) SelectedChanges

func (d *DiffView) SelectedChanges() []ChangedLine

SelectedChanges are the changed lines the selection covers, in diff order and as Chunk.Lines names them, ready for DiffOptions.Revert's caller to revert just those; nil when nothing changed is selected.

func (*DiffView) SelectedRange

func (d *DiffView) SelectedRange() (r DiffRange, ok bool)

SelectedRange is the span of each file's lines the selection covers, in whichever layout shows it, or ok false when nothing is selected. Side by side, a selection lives on one side, which shows one file, so the other's span is 0.

func (*DiffView) SetBlocks

func (d *DiffView) SetBlocks(blocks []DiffBlock)

SetBlocks lays blocks out under their lines, replacing those set before, from the next Layout once highlighting allows (as UpdateContent does). A block whose line is in a gap, or not in the diff, is not shown until the line is. Its Widget is laid out as a BlockWidget's is: once a frame.

func (*DiffView) SetDocs

func (d *DiffView) SetDocs(filename, original, modified string)

SetDocs shows the diff between two versions of a document, worked out here line by line. filename picks the language both are highlighted as, and names the file: a diff of the same filename replaces the one shown in place, keeping the scroll, and another opens from its top. Either way, what was expanded is collapsed again. Two documents more than 10,000 lines of edits apart in one stretch show that stretch as a single chunk.

func (*DiffView) SetPatch

func (d *DiffView) SetPatch(p FilePatch)

SetPatch shows one file of a patch. Like SetDocs, a new version of the file shown (the same Path, another diff) replaces it in place, and another file opens from its top.

func (*DiffView) SetSearch

func (d *DiffView) SetSearch(q SearchQuery) error

SetSearch is View.SetSearch for the diff: the same query looks through every view it has, unified and both sides of the split, and follows the diff as it is replaced, expanded or given blocks. A Match's Pos counts the rows of the view on screen, deletions and additions included, not file lines; select it with FindNext and read the file lines off SelectedRange.

func (*DiffView) SetTheme

func (d *DiffView) SetTheme(t Theme)

SetTheme colors the diff with t, its code and its changes (t.Diff), from the next Layout. A DiffView starts in DarkTheme.

type FilePatch

type FilePatch struct {
	// Path is the file's path on the new side, or on the old side for a
	// deleted file; it also picks the language the diff is highlighted as.
	Path string
	// OldPath is the file's path on the old side, "" for an added file.
	OldPath        string
	Added, Deleted int
	// contains filtered or unexported fields
}

FilePatch is one file of a unified diff in git's format, as ParsePatch reads it. DiffView.SetPatch shows it.

func ParsePatch

func ParsePatch(patch string) ([]FilePatch, error)

ParsePatch reads every file of a unified diff in git's format (the output of git diff), in order. Text before the first "diff --git" line is skipped. It fails on a hunk header it cannot read.

type Fonts

type Fonts struct {
	Regular, Bold, Italic, BoldItalic []byte
}

Fonts are the contents of a monospace family's font files (TrueType, OpenType or a collection, whose first font is used). Regular is required; a style left nil is drawn with Regular.

type Gap

type Gap struct {
	From, To, OldFrom int
	// contains filtered or unexported fields
}

Gap is an unchanged run of lines a diff leaves out: new-file lines From to To (1-based, inclusive), with OldFrom the old-file line that pairs with From. A DiffView hands one to DiffOptions.Expand and takes the lines back through FillGap.

type HighlightStyle added in v0.2.0

type HighlightStyle struct {
	// contains filtered or unexported fields
}

HighlightStyle maps highlighting tags to styles, as CodeMirror's does. Each of a token's tags takes the spec of the most specific tag in its Set that has one. A token can have several tags (a grammar gives some nodes more than one, and a node's tags can apply to everything inside it): it is drawn with every spec its tags take, merged in spec order, a later spec's color, bold, italic, underline or line-through winning over an earlier one's, as CSS cascades the classes CodeMirror gives such a token. A token no style reaches is drawn plain in Theme.Foreground. A HighlightStyle is immutable, so a Theme holding it compares equal exactly while it draws the same.

func NewHighlightStyle added in v0.2.0

func NewHighlightStyle(specs ...TagStyle) *HighlightStyle

NewHighlightStyle builds a HighlightStyle from specs, in CodeMirror's order: when two specs name the same tag, the later one is the tag's style and the earlier one no longer applies to it.

func (*HighlightStyle) Specs added in v0.2.0

func (h *HighlightStyle) Specs() []TagStyle

Specs are the specs h was built from, in their order: a copy, so changing them leaves h as it is. A theme of your own can start from another's style with specs of its own appended, which win over the ones they name the same tags as:

t := godemirror.OneDark
t.Highlight = godemirror.NewHighlightStyle(append(t.Highlight.Specs(),
	godemirror.TagStyle{Tags: []*tags.Tag{tags.Keyword}, Color: pink, Bold: true})...)

type LineDecoration

type LineDecoration struct {
	// Background tints the code area behind the line.
	Background color.NRGBA
	// GutterBackground tints the line's gutter cell.
	GutterBackground color.NRGBA
	// GutterStrip is a bar down the left edge of the gutter cell.
	GutterStrip color.NRGBA
	// Rule is a hairline through the middle of the line, across the code area.
	Rule color.NRGBA
	// Hatch strikes thin diagonal lines across the code area: the row a
	// side-by-side diff stands in for a line only the other side has.
	Hatch color.NRGBA
	// HoverGroup groups lines that light up together: while the pointer is
	// over any line of a group, every line of it takes HoverBackground in
	// place of Background. 0 is no group.
	HoverGroup int
	// HoverBackground replaces Background while the line's group is under
	// the pointer; zero keeps Background.
	HoverBackground color.NRGBA
	// Spans tint stretches of the line's characters over its background:
	// the words that changed in a diff, say.
	Spans []Span
	// contains filtered or unexported fields
}

LineDecoration styles one document line across every visual row it wraps into. A zero color draws nothing; a translucent one is mixed over the theme's background in sRGB, the way a browser mixes CSS colors.

type LineRef

type LineRef struct {
	Side Side
	Line int
}

LineRef names a line of one file of a diff, 1-based.

type LineWidget

type LineWidget struct {
	// Line is the document line (0-based) the widget sits on.
	Line      int
	Placement Placement
	// WhileHovered shows the widget only while the pointer is over its line's
	// hover group (LineDecoration.HoverGroup).
	WhileHovered bool
	// Widget is laid out once in every frame its line is on screen and
	// shown, and not at all in the others.
	Widget layout.Widget
}

LineWidget is a Gio widget drawn on a line of the document: a button on a diff's hunk separator, say. It is laid out with at most the code area's width and one row's height. A press on it is the widget's, never the start of a selection.

type Match

type Match struct{ From, To Pos }

Match is one match of a search: the runes from From to To (To excluded), counted as Pos counts them, so a tab is one. A match never spans lines.

type Placement

type Placement int

Placement is where on its line a LineWidget sits.

const (
	// AfterText puts the widget one cell past the line's last character, on
	// the last row the line wraps into.
	AfterText Placement = iota
	// RightEdge puts the widget against the code area's right edge, on the
	// line's first row.
	RightEdge
)

type Pos

type Pos struct{ Line, Col int }

Pos is a place in the document between two characters: Line is 0-based, and Col counts the runes of the line's text before it, a tab or a wide character as one.

type SearchQuery

type SearchQuery struct {
	Text string
	// CaseSensitive makes a letter match only itself; otherwise Unicode simple
	// case folding applies.
	CaseSensitive bool
	// Regexp reads Text as a Go regular expression (regexp/syntax), matched
	// against one line at a time; otherwise Text is looked for as written.
	Regexp bool
	// WholeWord keeps only the matches that do not cut a word in two: no word
	// character, as the double click reads one (letters, digits and
	// underscore), sits against a match's first or last character when that
	// character is one too.
	WholeWord bool
}

SearchQuery is what SetSearch looks for. Text "" clears the search.

type Side

type Side uint8

Side is which file of a diff a line is in.

const (
	// OldSide is the original file, the one a deletion is taken from.
	OldSide Side = iota
	// NewSide is the modified file, the one an addition is put in.
	NewSide
)

type Span

type Span struct {
	From, To int
	Color    color.NRGBA
}

Span is a stretch of a line's characters, From to To in rune columns (0-based, To excluded, a tab or a wide character one column), and the Color it is tinted with.

type Split

type Split struct {
	// Left and Right are the two sides. Each is a View, but it is laid out
	// by the Split's Layout, never its own.
	Left, Right *View
	// Wrap breaks both sides' lines instead of scrolling them sideways.
	Wrap bool
	// Nested is View.Nested for both sides.
	Nested bool
}

Split shows two documents side by side whose line i pairs with line i of the other: the two sides of a diff. Each pair of lines starts on the same row: unwrapped that holds by itself, and when the sides wrap, the side that wraps into fewer rows is padded to the other's. A block under a line on one side leaves as much room under its pair on the other. They scroll down together and sideways each on its own.

Content, Theme and selection are each side's own, set on Left and Right; Wrap is the split's, and Layout sets it on both sides.

func NewSplit

func NewSplit(family string, size unit.Sp) (*Split, error)

NewSplit builds a split view whose two sides draw family at size, from one set of loaded fonts. It fails as New does.

func (*Split) Layout

func (s *Split) Layout(gtx layout.Context) (layout.Dimensions, error)

Layout draws both sides into the whole of gtx.Constraints.Max, halved, with a 1dp Border between them. Its error means what View.Layout's does, for either side.

func (*Split) SetTheme

func (s *Split) SetTheme(t Theme)

SetTheme gives both sides t. The divider between them takes the left side's Theme.Border.

type StyleRule added in v0.5.0

type StyleRule struct {
	Selector string
	Tags     []*tags.Tag
	Names    []string
}

StyleRule is one styleTags entry: space-separated paths and the tags the nodes they name take. A path is a node name or group, optionally under its parents ("ArgumentList/Identifier", "*" for any parent), ending in "/..." to give the tags to everything inside the node too, or in "!" to stop matching inside it. A name with "/" or "!" in it is quoted, as in JSON.

Names, when set, limit the rule to nodes whose text is exactly one of them, as the builtin sets (GoBuiltinTags) use it. A node whose text is not among them falls through to the next rule that matches it, a rule with Names being tried before any as deep without. CodeMirror's styleTags cannot read a node's text, so a rule with Names has no CodeMirror equivalent.

type StyleTags added in v0.5.0

type StyleTags struct {
	// Language names the grammar by its @lezer package: "css", "go",
	// "html", "java", "javascript" (JavaScript, JSX, TypeScript and TSX
	// alike), "python" or "rust". Kotlin and shell have no Lezer grammar,
	// so no rules.
	Language string
	// Rules are styleTags' entries, in the order the object would list
	// them.
	Rules []StyleRule
}

StyleTags adds styleTags rules to one language's Lezer grammar, as a CodeMirror caller does with language.configure({props: [styleTags({...})]}): a node the rules match takes their tags, and a rule wins over the grammar's own when its path is at least as deep. It is how the tags a grammar gives can be made finer, telling apart what the tree tells apart and the grammar's own rules do not, as RicherJavaTags does.

func GoBuiltinTags added in v0.6.0

func GoBuiltinTags() StyleTags

GoBuiltinTags tags Go's predeclared identifiers: a type is standard(typeName), a call of a builtin function function(standard(variableName)), a conversion to a builtin type, as in int(x), standard(typeName), and iota standard(variableName). true, false and nil are already literals in @lezer/go, and make before a type is a keyword RicherGoTags tags.

func JavaScriptBuiltinTags added in v0.6.0

func JavaScriptBuiltinTags() StyleTags

JavaScriptBuiltinTags tags JavaScript's and TypeScript's builtins: a global constructor is standard(className), a global function called function(standard(variableName)), any other global standard(variableName), and a TypeScript primitive type standard(typeName).

func PythonBuiltinTags added in v0.6.0

func PythonBuiltinTags() StyleTags

PythonBuiltinTags tags Python's built-in functions and types: called, function(standard(variableName)), and standard(variableName) anywhere else (isinstance(x, int)).

func RicherGoTags added in v0.6.0

func RicherGoTags() StyleTags

RicherGoTags tags Go finer than @lezer/go does: a parameter, receiver or named result is definition(local(variableName)), and make, which the grammar parses as a keyword and leaves untagged when its first argument is a type, function(standard(variableName)). The other builtins (len, append, new, int, error) are names like any other in the tree, so they stay as @lezer/go tags them.

func RicherJavaScriptTags added in v0.6.0

func RicherJavaScriptTags() StyleTags

RicherJavaScriptTags tags JavaScript and TypeScript finer than @lezer/javascript does: a function's, method's or arrow function's parameter is definition(local(variableName)), and so is a name bound by a destructuring pattern one level inside the parameter list.

func RicherJavaTags added in v0.5.0

func RicherJavaTags() StyleTags

RicherJavaTags tags Java finer than @lezer/java does, where its tree tells more than its styleTags do: an enum constant is constant(variableName), an annotation's "@" and name annotation, a field definition(propertyName), a package or import path namespace, and a method's, constructor's or lambda's parameter definition(local(variableName)). A field read, as in a.b, stays variableName: a path cannot tell its object from its field, both being an Identifier under FieldAccess.

func RicherPythonTags added in v0.6.0

func RicherPythonTags() StyleTags

RicherPythonTags tags Python finer than @lezer/python does: a function's or lambda's parameter, self included, is definition(local(variableName)). A default value that is a bare name (x=DEFAULT) is tagged so too: the tree puts it under the parameter list beside the parameter, and a path cannot tell the two apart.

func RicherRustTags added in v0.6.0

func RicherRustTags() StyleTags

RicherRustTags tags Rust finer than @lezer/rust does: a function's or closure's parameter is definition(local(variableName)), and so is a name bound one pattern deep in it ((a, b): (u8, u8), &c: &u8).

func RustBuiltinTags added in v0.6.0

func RustBuiltinTags() StyleTags

RustBuiltinTags tags Rust's primitive types standard(typeName). The prelude (Vec, Option, Some) is ordinary library code and stays as @lezer/rust tags it, as does a primitive type used as a path (i32::MAX), which the grammar parses as a namespace.

type TagStyle added in v0.2.0

type TagStyle struct {
	Tags                                   []*tags.Tag
	Color                                  color.NRGBA
	Bold, Italic, Underline, Strikethrough bool
}

TagStyle is how the code under some tags is drawn, as one spec of CodeMirror's HighlightStyle.define: a color, the CSS fontWeight bold and fontStyle italic, and the textDecoration underline and line-through. A zero Color leaves the text in Theme.Foreground, the way a CSS rule with no color leaves the editor's.

type Theme

type Theme struct {
	// Background fills the code area.
	Background color.NRGBA
	// Selection fills the selected cells.
	Selection color.NRGBA
	// Border divides the two sides of a Split.
	Border color.NRGBA
	// GutterBackground fills the gutter, and GutterForeground draws the line
	// numbers or Content.Labels in it.
	GutterBackground color.NRGBA
	GutterForeground color.NRGBA
	// Foreground is the code's color where Highlight gives it none, as the
	// color of CodeMirror's editor theme is.
	Foreground color.NRGBA
	// Highlight styles the code by its highlighting tags; nil draws it all in
	// Foreground.
	Highlight *HighlightStyle
	// SearchMatch tints every match of the search (SetSearch), and
	// SearchCurrent the one that is selected, the current match. Like
	// Selection they may be translucent: they are mixed over the line's
	// background in sRGB. Zero highlights nothing.
	SearchMatch, SearchCurrent color.NRGBA
	// Diff colors a DiffView's changes and the rows around them.
	Diff DiffColors
}

Theme is the view's palette. Selection may be translucent, as may the colors of a LineDecoration: they are mixed over Background in sRGB, the way a browser mixes CSS colors. The other colors are painted as they are and should be opaque, since Gio blends alpha in linear light.

type View

type View struct {
	// Wrap breaks lines at the view's width instead of scrolling sideways.
	Wrap bool
	// Nested is a view laid out inside a parent that scrolls vertically, a
	// panel listing several: a vertical wheel the view cannot scroll down by
	// goes on to the parent, and turns sideways only while the pointer last
	// reported Shift held.
	Nested bool
	// Theme colors the view; it starts as DarkTheme. A change shows on the
	// next Layout, which composes the rows on screen again.
	Theme Theme
	// contains filtered or unexported fields
}

View shows a document. Its zero value is not usable; build one with New or NewFromFonts.

Example
package main

import (
	"errors"
	"fmt"
	"image"
	"log"

	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/unit"
	"golang.org/x/image/font/gofont/gomono"

	"github.com/lichdotdev/godemirror"
)

func main() {
	view, err := godemirror.New("monospace", unit.Sp(13))
	if errors.Is(err, godemirror.ErrNoFontconfig) {
		// Without fontconfig, hand the view the font files instead.
		view, err = godemirror.NewFromFonts(godemirror.Fonts{Regular: gomono.TTF}, unit.Sp(13))
	}
	if err != nil {
		log.Fatal(err)
	}
	view.SetContent(godemirror.Content{
		Filename: "main.go",
		Text:     "package main\n\nfunc main() {\n\tprintln(\"hi\")\n}\n",
	})
	view.SelectLines(2, 4)
	view.ScrollToLine(2, godemirror.AlignNearest)

	// In an app, gtx comes from each app.FrameEvent of the window.
	var ops op.Ops
	gtx := layout.Context{
		Ops:         &ops,
		Metric:      unit.Metric{PxPerDp: 1, PxPerSp: 1},
		Constraints: layout.Exact(image.Pt(640, 480)),
	}
	if _, err := view.Layout(gtx); err != nil {
		log.Fatal(err)
	}

	from, to, _ := view.SelectedLines()
	fmt.Printf("%d lines; lines %d-%d selected:\n%s", view.LineCount(), from, to, view.SelectedText())
}
Output:
5 lines; lines 2-4 selected:
func main() {
	println("hi")
}

func New

func New(family string, size unit.Sp) (*View, error)

New builds a view drawing family at size. family is a fontconfig name (resolved with fc-match, as in "JetBrains Mono" or "monospace"), and a character it lacks is drawn from whichever installed font covers it. New fails with ErrNoFontconfig when fontconfig is not installed, and with another error when a font file it names cannot be read.

func NewFromFonts

func NewFromFonts(fonts Fonts, size unit.Sp) (*View, error)

NewFromFonts builds a view drawing fonts at size, with no fontconfig. A character the fonts do not cover is drawn as a box, since there is no installed font to fall back to. It fails when Regular is nil or a style is not a font.

func (*View) ClearSelection

func (v *View) ClearSelection()

ClearSelection drops the selection.

func (*View) FindNext

func (v *View) FindNext() (m Match, ok bool)

FindNext selects the first match that starts at or after the end of the selection (or the first one, with nothing selected), wrapping around the document, and scrolls it into view as little as it takes. ok is false when there is no match. The match is the current one: lit in Theme.SearchCurrent for as long as it stays selected.

func (*View) FindPrevious

func (v *View) FindPrevious() (m Match, ok bool)

FindPrevious is FindNext backwards: the last match that ends at or before the start of the selection (with nothing selected, the last one), wrapping around the document.

func (*View) Layout

func (v *View) Layout(gtx layout.Context) (layout.Dimensions, error)

Layout draws the view into the whole of gtx.Constraints.Max, takes the pointer and key events addressed to it (scroll, select, copy), and lays out each LineWidget on screen and every BlockWidget exactly once.

While a selection is dragged beyond the view's top or bottom edge, or, when unwrapped, its left or right, Layout scrolls the view by the time since its last frame (gtx.Now) and asks for the next frame, faster the farther the pointer is beyond the edge. It scrolls no more at the document's ends, on release, or once the pointer is back inside.

An error means nothing was drawn this frame: either the fonts could not be scaled to the size gtx asks for, or the content last set could not be highlighted. In the second case the error is reported once; that content stays plain after SetContent, and is never swapped in after UpdateContent.

func (*View) LineCount

func (v *View) LineCount() int

LineCount is the number of lines shown. A final newline does not start a line of its own. Between UpdateContent and the swap it describes (see UpdateContent), it is the old content's.

func (*View) LineText

func (v *View) LineText(i int) string

LineText is line i (0-based) as written, tabs and all, without its line end. i must be in [0, LineCount()). Like LineCount, it reads the content on screen, which UpdateContent replaces only once highlighted.

func (*View) Matches

func (v *View) Matches() []Match

Matches are the matches of the search, in document order, in the content on screen: between UpdateContent and its swap, the old content's (see UpdateContent). It is a copy.

func (*View) ScrollToLine

func (v *View) ScrollToLine(line int, align Align)

ScrollToLine scrolls the view down or up so that line (0-based) sits where align says, as far as the document's ends allow. It takes effect on the next Layout, against the content shown then; a line past the end is the last line. It never scrolls sideways. On either side of a Split it scrolls both.

func (*View) Select

func (v *View) Select(from, to Pos)

Select selects the text between from and to, in either order, as a drag between them would. A position past the document's start or end, or past its line's end, is clamped to it. The view does not scroll to it.

func (*View) SelectLines

func (v *View) SelectLines(from, to int)

SelectLines selects lines from to to (0-based, inclusive) whole, with the newline after the last of them, as a triple click selects one line; it is the span SelectedLines then reports. Lines past the document are clamped.

func (*View) SelectedLines

func (v *View) SelectedLines() (from, to int, ok bool)

SelectedLines is the span of document lines (0-based, inclusive) the selection covers. A selection that ends at the very start of a line does not take that line in. ok is false when nothing is selected.

func (*View) SelectedText

func (v *View) SelectedText() string

SelectedText is the selected text as the document had it, tabs and all, or "" when nothing is selected.

func (*View) Selection

func (v *View) Selection() (from, to Pos, ok bool)

Selection is the selection's start and end, from before to, or ok false when nothing is selected. A selection made with the pointer that starts or ends inside a tab is read as starting or ending after it.

func (*View) SetContent

func (v *View) SetContent(c Content)

SetContent shows c from its top, with nothing selected. Its text shows at once; its highlighting is worked out off the UI goroutine and swapped in by a later Layout.

func (*View) SetSearch

func (v *View) SetSearch(q SearchQuery) error

SetSearch highlights every match of q in the content shown, and keeps doing so as the content changes (SetContent, and UpdateContent once its content is on screen). Matches are looked for line by line. An empty q.Text clears the search; a Text that is not a valid regexp clears it too and is the only error. A theme with no SearchMatch color highlights nothing.

Example
package main

import (
	"errors"
	"fmt"
	"log"

	"gioui.org/unit"
	"golang.org/x/image/font/gofont/gomono"

	"github.com/lichdotdev/godemirror"
)

func main() {
	view, err := godemirror.New("monospace", unit.Sp(13))
	if errors.Is(err, godemirror.ErrNoFontconfig) {
		view, err = godemirror.NewFromFonts(godemirror.Fonts{Regular: gomono.TTF}, unit.Sp(13))
	}
	if err != nil {
		log.Fatal(err)
	}
	view.SetContent(godemirror.Content{Text: "foo bar\nFoo(bar)\nbaz foo"})

	// A search box calls SetSearch as its text changes, and FindNext on Enter.
	if err := view.SetSearch(godemirror.SearchQuery{Text: "foo", WholeWord: true}); err != nil {
		log.Fatal(err)
	}
	for range 4 {
		m, _ := view.FindNext()
		fmt.Println(m.From, m.To)
	}
}
Output:
{0 0} {0 3}
{1 0} {1 3}
{2 4} {2 7}
{0 0} {0 3}

func (*View) UpdateContent

func (v *View) UpdateContent(c Content)

UpdateContent replaces what is shown with a new version of it (a diff with lines pulled in, say), keeping the scroll position. The current content stays on screen until c's highlighting is ready, and then c takes its place whole, so text, labels and decorations never show out of step. Until then, whatever reads or addresses the view by line (SelectedLines, Selection, Select, SelectLines, ScrollToLine, LineCount, LineText) works on the old content. The selection is dropped, since its lines may no longer be where they were.

Directories

Path Synopsis
cmd
godemirror-demo command
Demo of godemirror: a file in a View, or a diff in a DiffView, in a window or, with -bench or -shot, headless.
Demo of godemirror: a file in a View, or a diff in a DiffView, in a window or, with -bench or -shot, headless.
internal
glyphs
Package glyphs rasterizes a monospace font family into per-character coverage masks, for Gio views that compose text into images on the CPU instead of drawing Gio text paths.
Package glyphs rasterizes a monospace font family into per-character coverage masks, for Gio views that compose text into images on the CPU instead of drawing Gio text paths.
lezer/css
Package css is @lezer/css's CSS grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the Go ports of its external tokenizers.
Package css is @lezer/css's CSS grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the Go ports of its external tokenizers.
lezer/golang
Package golang is @lezer/go's Go grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the ports of its external tokenizer and context tracker (tokens.go).
Package golang is @lezer/go's Go grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the ports of its external tokenizer and context tracker (tokens.go).
lezer/html
Package html is @lezer/html's HTML grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, the Go ports of its external tokenizers and context tracker, and the nesting of JavaScript and CSS that @codemirror/lang-html sets up by default.
Package html is @lezer/html's HTML grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, the Go ports of its external tokenizers and context tracker, and the nesting of JavaScript and CSS that @codemirror/lang-html sets up by default.
lezer/java
Package java is @lezer/java's Java grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package.
Package java is @lezer/java's Java grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package.
lezer/javascript
Package javascript is @lezer/javascript's JavaScript grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and ports of its external tokenizers and context tracker.
Package javascript is @lezer/javascript's JavaScript grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and ports of its external tokenizers and context tracker.
lezer/lezertest
Package lezertest checks a port against what the real Lezer gives: the goldens internal/lezer/gen/golden.mjs writes, one per fixture.
Package lezertest checks a port against what the real Lezer gives: the goldens internal/lezer/gen/golden.mjs writes, one per fixture.
lezer/python
Package python is @lezer/python's Python grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the ports of its external tokenizers and indentation tracker (tokens.go).
Package python is @lezer/python's Python grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the ports of its external tokenizers and indentation tracker (tokens.go).
lezer/rust
Package rust is @lezer/rust's Rust grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the ports of its external tokenizers (tokens.go).
Package rust is @lezer/rust's Rust grammar for internal/lezer: its parse tables and styleTags rules, generated from the npm package, and the ports of its external tokenizers (tokens.go).
shot
Package shot writes a PNG of a Gio frame, drawn again off screen, so a run can be checked visually without a screen grabber.
Package shot writes a PNG of a Gio frame, drawn again off screen, so a run can be checked visually without a screen grabber.
stream
Package stream highlights a document with a CodeMirror 5-style stream mode, as CodeMirror's StreamLanguage does for the languages @codemirror/legacy-modes covers and Lezer does not.
Package stream highlights a document with a CodeMirror 5-style stream mode, as CodeMirror's StreamLanguage does for the languages @codemirror/legacy-modes covers and Lezer does not.
stream/kotlin
Package kotlin is the Kotlin mode of @codemirror/legacy-modes, the one CodeMirror highlights Kotlin with.
Package kotlin is the Kotlin mode of @codemirror/legacy-modes, the one CodeMirror highlights Kotlin with.
stream/shell
Package shell is the shell mode of @codemirror/legacy-modes, the one CodeMirror highlights shell scripts with.
Package shell is the shell mode of @codemirror/legacy-modes, the one CodeMirror highlights shell scripts with.
stream/streamtest
Package streamtest checks a mode's port against what CodeMirror gives: the goldens internal/stream/gen/golden.mjs writes, one per fixture.
Package streamtest checks a mode's port against what CodeMirror gives: the goldens internal/stream/gen/golden.mjs writes, one per fixture.
Package tags is CodeMirror's vocabulary of highlighting tags, the one @lezer/highlight defines: a closed set of tags a highlighter maps to styles, arranged so that a style for a general tag covers its subtags.
Package tags is CodeMirror's vocabulary of highlighting tags, the one @lezer/highlight defines: a closed set of tags a highlighter maps to styles, arranged so that a style for a general tag covers its subtags.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL