Syntax Highlighting
SharpConsoleUI highlights code with TextMate grammars — the same grammar format Visual Studio Code uses — coloured by your application's theme rather than a bundled editor theme. Roughly 64 languages resolve out of the box with no initialization call.
Scope: This is lexical (grammar-based) highlighting — keywords, strings, comments, numbers, types, functions, variables. The library does not include LSP/semantic highlighting; that is intentionally out of scope. For LSP-powered IntelliSense, see external projects such as LazyDotIDE, which builds on top of SharpConsoleUI.
Quick start
Nothing to register — ask the registry for a language and use the result:
using SharpConsoleUI.Highlighting;
var editor = Controls.MultilineEdit()
.WithContent(sourceCode)
.WithSyntaxHighlighter(SyntaxHighlighters.For("csharp"))
.Build();
Markdown fenced code blocks highlight automatically from their language hint:
var doc = Controls.Markdown("""
```rust
fn main() { println!("hi"); }
```
""").Build();
Languages
All languages shipped by TextMateSharp.Grammars are available, including C#, JavaScript, TypeScript, Python, Rust, Go, Java, C/C++, Ruby, PHP, SQL, HTML, CSS, JSON, YAML, XML, Markdown, shell scripts, Dockerfiles, diffs, and many more.
Resolution accepts language ids, aliases, and file extensions, case-insensitively:
SyntaxHighlighters.For("csharp"); // id
SyntaxHighlighters.For("cs"); // extension
SyntaxHighlighters.For("sh"); // alias -> shellscript
SyntaxHighlighters.For("toml"); // null - no grammar ships for it
Grammars load lazily on first use, so an application that never highlights anything pays no startup cost.
Theming
Code colours come from the active theme, so highlighted code matches the rest of your UI and follows theme switches — including light themes, where a fixed editor palette would look wrong.
A theme supplies colours through ITheme.SyntaxColors, a SyntaxPalette with one colour per semantic role:
| Role | Applies to |
|---|---|
Default |
text matching no other role |
Keyword |
if, class, return, storage modifiers |
Operator |
=, +, => |
String |
string and character literals |
Number |
numeric literals |
Comment |
comments, including their delimiters |
Type |
type, class, struct, and interface names |
Function |
function and method names |
Variable |
variables, fields, parameters |
Constant |
true, null, other language constants |
Tag |
HTML/XML element names |
Attribute |
markup attribute names |
Punctuation |
braces, semicolons, commas |
Invalid |
text the grammar marks invalid |
ITheme.SyntaxColors returns null by default, in which case SyntaxPalette.DeriveFrom(theme) generates a readable palette from the theme's base colours. To customise, return your own:
public override SyntaxPalette? SyntaxColors => new()
{
Keyword = Color.DodgerBlue2,
String = Color.DarkSeaGreen,
Comment = Color.Grey,
// unset roles fall back to the derived defaults
};
Every resolved colour passes through a contrast floor against the code background, so a low-contrast palette cannot render unreadable code.
Using a bundled VS Code theme instead
To render code in Visual Studio Code's own colours rather than your app's:
using SharpConsoleUI.Highlighting.TextMate;
using TextMateSharp.Grammars;
TextMateHighlighting.RegisterAll(ThemeName.DarkPlus);
Registering a custom highlighter
An explicit registration always wins over the TextMate fallback:
SyntaxHighlighters.Register("mylang", new MyHighlighter());
Implement ISyntaxHighlighter to supply your own tokenizer. Tokenize receives the line, its index, and the parser state from the previous line, and returns coloured spans plus the state to carry forward — that state is what makes multi-line constructs such as block comments work.
How resolution works
The registry is the single source of truth, consulted by every consumer:
SyntaxHighlighters.For(lang)
1. explicit Register(lang, ...) -> that highlighter
2. a TextMate grammar for lang -> a cached TextMate highlighter
3. otherwise -> null
Markdown fenced code blocks
A fenced block with a language hint is highlighted automatically. Resolution order:
- The per-style
MarkdownStyle.CodeHighlightersoverride, keyed by language hint. SyntaxHighlighters.For(lang).- A flat, shaded code block — when the language is unknown or no hint is given.
Indented code blocks carry no language hint and always render flat.
See Markup Syntax → Syntax Highlighting in Code Blocks for the CodeHighlighters override.
MultilineEditControl
The editor colours its content with any ISyntaxHighlighter, assigned through the builder or the SyntaxHighlighter property. Its token cache is invalidated on content changes, re-tokenizing only affected lines and their successors. See MultilineEditControl → Syntax Highlighting.
Upgrading
Code blocks change appearance after upgrading to the TextMate engine: colours now come from your theme, more languages highlight than before, and grammars distinguish variables and function names that the previous regex highlighters could not. No API changed.
The thirteen former regex highlighter classes (CSharpSyntaxHighlighter, JsonSyntaxHighlighter, and so on) still exist and still work, but are [Obsolete]: they now delegate to the TextMate engine. Replace new CSharpSyntaxHighlighter() with SyntaxHighlighters.For("csharp").
SlnSyntaxHighlighter is the exception — no TextMate grammar covers .sln files, so it keeps its original implementation and is not obsolete.
Performance
Tokenized lines are cached, so repainting unchanged content does no grammar work — a repaint of a 50-line viewport drops from roughly 66 ms to well under 1 ms. Results are keyed by the line text and the incoming parser state, and are invalidated automatically when the theme changes.
MultilineEditControl keeps its own per-line cache on top of this, invalidated on edits.
If you mutate a highlighter in place — the usual case being an ISyntaxHighlighter decorator
that layers LSP semantic tokens over a lexical one — the editor cannot know its output changed.
Tell it explicitly:
myDecorator.UpdateTokens(semanticTokens, legend);
editor.RefreshSyntaxHighlighting();
Assigning editor.SyntaxHighlighter the instance it already holds is a no-op and will not
refresh; without the change guard, every such assignment would discard the whole file's tokens and
re-tokenize on the next paint.
Thread safety
SyntaxHighlighters.For(...) and the highlighters it returns are safe to use from multiple threads; tokenization is serialized per grammar internally. Highlighter instances are shared per language, so caching one and reusing it across windows is fine.