Update kjol website with C documentation

This commit is contained in:
2026-07-14 13:05:12 -04:00
parent 02a6dc6c48
commit 7d7b7354df
66 changed files with 23884 additions and 2551 deletions

View File

@@ -3137,7 +3137,7 @@ const (
FORMULA_EDIT_BASE = "block w-full h-[30px] font-mono text-sm p-1 rounded-default box-border whitespace-pre"
FORMULA_OVERLAY_CLS = FORMULA_EDIT_BASE + " absolute inset-0 overflow-hidden pointer-events-none border border-transparent text-ink"
FORMULA_TEXTAREA_CLS = FORMULA_EDIT_BASE + " relative bg-transparent text-transparent caret-neutral-800 resize-none overflow-x-auto overflow-y-hidden shadow-xs border border-line-strong focus:border-sky-500 outline-hidden"
FORMULA_TEXTAREA_CLS = FORMULA_EDIT_BASE + " relative bg-transparent text-transparent caret-ink resize-none overflow-x-auto overflow-y-hidden shadow-xs border border-line-strong focus:border-sky-500 outline-hidden"
formulaPlaceholder = `<span class="text-ink-faint">e.g. [Revenue] / SUM({Revenue}) * 100</span>`

View File

@@ -48,7 +48,7 @@ func BorderCutCornerCard(class string, children ...*vdom.VNode) *vdom.VNode {
}
const cardHeader = "text-xl tracking-tight text-ink mb-5"
const cardHeaderHR = "text-neutral-200 mt-1 mb-3"
const cardHeaderHR = "text-line mt-1 mb-3"
// CardHeader renders a card title followed by a divider.
func CardHeader(class string, children ...*vdom.VNode) *vdom.VNode {

View File

@@ -1,174 +0,0 @@
package webui
import (
"html"
"strings"
)
// Go syntax highlighting, for the code samples a documentation page shows.
//
// It is a LEXER, not a parser: it classifies tokens and gives up gracefully on anything
// it does not understand, because a highlighter that can fail to render is worse than
// one that occasionally paints an identifier the wrong colour. Unterminated strings and
// comments run to the end of the input rather than throwing.
//
// Output is HTML, and every run of source text passes through html.EscapeString on the
// way out — the input is Go source, which is full of `<`, `>` and `&`, and one of the
// snippets this is meant to display is literally a block of HTML.
// A code block is dark in BOTH themes — a light code block on a light page is a
// different kind of thing, and switching it with the theme means the snippet you were
// reading changes colour under you. So these are fixed on-dark colours, not theme
// tokens: the surface they sit on never changes.
const (
goCommentClass = "text-neutral-400"
goStringClass = "text-emerald-300"
goKeywordClass = "text-sky-300"
goNumberClass = "text-amber-300"
goFuncClass = "text-violet-300"
)
var goKeywords = map[string]bool{
"break": true, "case": true, "chan": true, "const": true, "continue": true,
"default": true, "defer": true, "else": true, "fallthrough": true, "for": true,
"func": true, "go": true, "goto": true, "if": true, "import": true,
"interface": true, "map": true, "package": true, "range": true, "return": true,
"select": true, "struct": true, "switch": true, "type": true, "var": true,
// Not keywords to the Go spec — predeclared identifiers — but every editor colours
// them, and a reader looking for `nil` is looking for the same kind of thing.
"nil": true, "true": true, "false": true, "iota": true,
"string": true, "int": true, "int64": true, "float64": true, "bool": true,
"byte": true, "rune": true, "any": true, "error": true,
}
// HighlightGo turns Go source into HTML with the tokens wrapped in coloured spans.
//
// The result is meant for vdom.Raw inside a <pre>: it contains no block elements and
// preserves the source's whitespace exactly, so the <pre> does the layout.
func HighlightGo(src string) string {
var b strings.Builder
b.Grow(len(src) * 2)
i := 0
for i < len(src) {
c := src[i]
switch {
// Line comment — including the //gowasm: directives, which are the most
// important line in several of these snippets.
case c == '/' && i+1 < len(src) && src[i+1] == '/':
end := strings.IndexByte(src[i:], '\n')
if end < 0 {
end = len(src)
} else {
end += i
}
span(&b, goCommentClass, src[i:end])
i = end
// Block comment.
case c == '/' && i+1 < len(src) && src[i+1] == '*':
end := strings.Index(src[i+2:], "*/")
if end < 0 {
end = len(src)
} else {
end = i + 2 + end + 2
}
span(&b, goCommentClass, src[i:end])
i = end
// Interpreted string. Ends at the closing quote or the line's end — an
// unterminated string is a typo in a snippet, not a reason to paint the rest of
// the file green.
case c == '"':
i = quoted(&b, src, i, '"', true)
// Raw string: no escapes, and it may span lines.
case c == '`':
i = quoted(&b, src, i, '`', false)
// Rune literal.
case c == '\'':
i = quoted(&b, src, i, '\'', true)
case isDigit(c):
j := i
for j < len(src) && (isDigit(src[j]) || isHexish(src[j])) {
j++
}
span(&b, goNumberClass, src[i:j])
i = j
case isIdentStart(c):
j := i
for j < len(src) && isIdentPart(src[j]) {
j++
}
word := src[i:j]
switch {
case goKeywords[word]:
span(&b, goKeywordClass, word)
case callAhead(src, j):
// An identifier immediately followed by "(" is being called (or is a type
// being converted to). Colouring it is what makes the shape of a snippet
// readable at a glance.
span(&b, goFuncClass, word)
default:
b.WriteString(html.EscapeString(word))
}
i = j
default:
b.WriteString(html.EscapeString(string(c)))
i++
}
}
return b.String()
}
// quoted consumes a quoted literal starting at i and writes it as a string span.
// escapes reports whether a backslash escapes the next byte (false for raw strings).
func quoted(b *strings.Builder, src string, i int, quote byte, escapes bool) int {
j := i + 1
for j < len(src) {
if escapes && src[j] == '\\' && j+1 < len(src) {
j += 2
continue
}
if src[j] == quote {
j++
break
}
if escapes && src[j] == '\n' {
break // unterminated: stop at the line end rather than eating the file
}
j++
}
span(b, goStringClass, src[i:j])
return j
}
// callAhead reports whether the next non-space byte at or after i is an opening paren.
func callAhead(src string, i int) bool {
for i < len(src) && (src[i] == ' ' || src[i] == '\t') {
i++
}
return i < len(src) && src[i] == '('
}
func span(b *strings.Builder, class, text string) {
b.WriteString(`<span class="`)
b.WriteString(class)
b.WriteString(`">`)
b.WriteString(html.EscapeString(text))
b.WriteString(`</span>`)
}
// isDigit already exists in the package (autotable.go) — reused rather than shadowed.
func isHexish(c byte) bool {
return c == '.' || c == 'x' || c == 'X' || c == '_' ||
(c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
}
func isIdentStart(c byte) bool { return c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') }
func isIdentPart(c byte) bool { return isIdentStart(c) || isDigit(c) }

View File

@@ -1,99 +0,0 @@
package webui
import (
"strings"
"testing"
)
// The highlighter emits HTML, and its input is Go source — which is full of <, > and &.
// Anything that reaches the page unescaped is markup injection into your own docs page:
// a snippet containing `<div>` would render a div.
func TestHighlightGoEscapes(t *testing.T) {
got := HighlightGo(`s := "<div class=\"x\">" // a & b`)
if strings.Contains(got, "<div") {
t.Errorf("a `<div>` in the SOURCE reached the output as markup:\n%s", got)
}
if !strings.Contains(got, "&lt;div") {
t.Errorf("the angle bracket was not escaped:\n%s", got)
}
if !strings.Contains(got, "&amp;") {
t.Errorf("the ampersand was not escaped:\n%s", got)
}
}
func TestHighlightGoClassifies(t *testing.T) {
got := HighlightGo("func main() { x := 42 // note\n}")
for _, want := range []struct{ what, class, text string }{
{"keyword", goKeywordClass, "func"},
{"call", goFuncClass, "main"},
{"number", goNumberClass, "42"},
{"comment", goCommentClass, "// note"},
} {
if !strings.Contains(got, `<span class="`+want.class+`">`+want.text+`</span>`) {
t.Errorf("%s %q was not highlighted:\n%s", want.what, want.text, got)
}
}
}
// The //gowasm: directives are the most important line in half these snippets. They are
// comments, and must survive as such.
func TestHighlightGoKeepsDirectives(t *testing.T) {
got := HighlightGo("//gowasm:page / static layout=public\nfunc HomePage() {}")
if !strings.Contains(got, `<span class="`+goCommentClass+`">//gowasm:page / static layout=public</span>`) {
t.Errorf("the directive was not kept whole as a comment:\n%s", got)
}
}
// A lexer that can hang or eat the rest of the file on malformed input would take the
// whole page down with it. Unterminated literals stop; they do not run away.
func TestHighlightGoSurvivesMalformedInput(t *testing.T) {
for _, src := range []string{
`x := "unterminated`,
"y := `unterminated raw",
"/* unterminated block",
`z := '`,
"",
} {
got := HighlightGo(src)
// The text must all still be there — mangling is not an acceptable failure mode
// either. Compare on the visible characters, ignoring the spans.
if plain := stripTags(got); plain != src {
t.Errorf("input %q came out as %q", src, plain)
}
}
}
// Nothing is dropped: every byte of the source is still on the page, in order.
func TestHighlightGoIsLossless(t *testing.T) {
src := "package app\n\nimport \"strings\"\n\nfunc f(n int) string {\n\treturn strings.Repeat(\"x\", n) // pad\n}\n"
if plain := stripTags(HighlightGo(src)); plain != src {
t.Errorf("the highlighter changed the source.\n got: %q\nwant: %q", plain, src)
}
}
// stripTags removes the spans and unescapes, recovering the original source.
func stripTags(s string) string {
var b strings.Builder
for i := 0; i < len(s); {
if s[i] == '<' {
j := strings.IndexByte(s[i:], '>')
if j < 0 {
break
}
i += j + 1
continue
}
b.WriteByte(s[i])
i++
}
out := b.String()
// Reverse html.EscapeString, innermost last.
out = strings.ReplaceAll(out, "&lt;", "<")
out = strings.ReplaceAll(out, "&gt;", ">")
out = strings.ReplaceAll(out, "&#34;", `"`)
out = strings.ReplaceAll(out, "&#39;", "'")
out = strings.ReplaceAll(out, "&amp;", "&")
return out
}

View File

@@ -11,7 +11,7 @@ func PageContainer(children ...*vdom.VNode) *vdom.VNode {
}
// Divider is a thin horizontal rule.
func Divider() *vdom.VNode { return vdom.Hr(vdom.Attr("class", "text-neutral-200 mt-1 mb-3")) }
func Divider() *vdom.VNode { return vdom.Hr(vdom.Attr("class", "text-line mt-1 mb-3")) }
// CodeBox renders a dark monospace code block.
func CodeBox(code, class string) *vdom.VNode {
@@ -25,7 +25,7 @@ func PageHeader(text, class string) *vdom.VNode {
return vdom.Header(vdom.Attr("class", class),
vdom.Div(vdom.Attr("class", "mt-1"),
vdom.H1(vdom.Attr("class", "text-center text-2xl font-light text-ink mb-2"), vdom.Text(text)),
vdom.Hr(vdom.Attr("class", "text-neutral-200 mb-2")),
vdom.Hr(vdom.Attr("class", "text-line mb-2")),
),
)
}