// Package syntax colours source code. // // It colours the languages every editor built on this library meets whatever it // is for — TOML, Markdown, JavaScript, HTML, shell scripts, YAML, XML and // Dockerfiles — and it is the place an editor registers the language it is // *for*: Turbo Go registers Go, Turbo Rust registers Rust, and neither is known // here. // // Every scanner turns its input into per-line spans the editor can draw, and // every one of them is deliberately tolerant: text under the cursor is broken // most of the time it is being typed, and a highlighter that gives up on // invalid input is a highlighter that flickers off. // // Three things make up the extension point. Definition and Register say what a // language is and teach it to this package; LineScanner and ScanLines are what // a scanner is written against; and LineIndex is for a scanner that works in // byte offsets instead, which is what a tokeniser like go/scanner gives you. // // A file is recognised by its extension, then by its name — which is how a // Dockerfile, which has no extension, is found — and failing both by a shebang. // // The package depends on theme only for the vocabulary of style keys, so that a // token class and the colour it resolves to are named in one place. package syntax import "rickub.com/turbo-editors/turbo-core/theme" // Class is what a run of source text is, in colouring terms. // // It is deliberately coarser than any one language's token set: the editor // needs to tell a keyword from a string, not an ADD from a SUB. Most classes // are shared across languages — a string is a string in all of them — and only // the last five belong to markup, because nothing in a programming language // reads as a heading. // // The set is fixed. A language registered from outside this package colours // itself with these classes and no others, which is what lets one theme colour // every language an editor built on this library will ever learn. type Class uint8 // The classes a token can fall into. const ( // ClassIdentifier is the zero value, so text nothing else claims is drawn // as a plain identifier. ClassIdentifier Class = iota ClassKeyword ClassType ClassBuiltin ClassConstant ClassFunction ClassString ClassChar ClassNumber ClassComment ClassOperator ClassPunctuation // ClassHeading is a Markdown heading, whole line included. ClassHeading // ClassTag is an HTML element name, and the angle brackets around it. ClassTag // ClassAttribute is an HTML attribute's name. ClassAttribute // ClassEmphasis is Markdown bold or italic text. ClassEmphasis // ClassLink is a Markdown link or image, both its text and its target. ClassLink ) // styleKeys maps each class onto the theme key that colours it. var styleKeys = [...]string{ ClassIdentifier: theme.KeySyntaxIdentifier, ClassKeyword: theme.KeySyntaxKeyword, ClassType: theme.KeySyntaxType, ClassBuiltin: theme.KeySyntaxBuiltin, ClassConstant: theme.KeySyntaxConstant, ClassFunction: theme.KeySyntaxFunction, ClassString: theme.KeySyntaxString, ClassChar: theme.KeySyntaxChar, ClassNumber: theme.KeySyntaxNumber, ClassComment: theme.KeySyntaxComment, ClassOperator: theme.KeySyntaxOperator, ClassPunctuation: theme.KeySyntaxPunctuation, ClassHeading: theme.KeySyntaxHeading, ClassTag: theme.KeySyntaxTag, ClassAttribute: theme.KeySyntaxAttribute, ClassEmphasis: theme.KeySyntaxEmphasis, ClassLink: theme.KeySyntaxLink, } // StyleKey returns the theme key that colours this class. // // style := th.Style(syntax.ClassKeyword.StyleKey()) func (c Class) StyleKey() string { if int(c) >= len(styleKeys) { return theme.KeySyntaxIdentifier } return styleKeys[c] } // String returns the class name, for tests and for debugging. func (c Class) String() string { names := [...]string{ ClassIdentifier: "identifier", ClassKeyword: "keyword", ClassType: "type", ClassBuiltin: "builtin", ClassConstant: "constant", ClassFunction: "function", ClassString: "string", ClassChar: "char", ClassNumber: "number", ClassComment: "comment", ClassOperator: "operator", ClassPunctuation: "punctuation", ClassHeading: "heading", ClassTag: "tag", ClassAttribute: "attribute", ClassEmphasis: "emphasis", ClassLink: "link", } if int(c) >= len(names) { return "unknown" } return names[c] } // Span is a run of runes on a single line that share one class. // // Columns are rune offsets into the line: Start is included, End excluded, // matching the convention used everywhere else in the editor. type Span struct { Start int End int Class Class }